questionnaire: Guard empty options and dedup IDs

Amolith created

When a question has no options and allowOther is false, the opts array
is empty but optionIndex is 0. Accessing opts[0].isOther crashes with a
TypeError on undefined. Add an early return when the selected option
does not exist.

Deduplicate question IDs at normalization time by appending _2, _3, etc.
to collisions. Duplicate IDs caused answer map overwrites where only the
last answer for a given ID survived.

Change summary

packages/questionnaire/src/index.ts | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)

Detailed changes

packages/questionnaire/src/index.ts 🔗

@@ -101,12 +101,23 @@ export default function questionnaire(pi: ExtensionAPI) {
 				return errorResult("Error: No questions provided");
 			}
 
-			// Normalize questions with defaults
-			const questions: Question[] = params.questions.map((q, i) => ({
-				...q,
-				label: q.label || `Q${i + 1}`,
-				allowOther: q.allowOther !== false,
-			}));
+			// Normalize questions with defaults and deduplicate IDs
+			const seenIds = new Set<string>();
+			const questions: Question[] = params.questions.map((q, i) => {
+				let id = q.id;
+				if (seenIds.has(id)) {
+					let suffix = 2;
+					while (seenIds.has(`${id}_${suffix}`)) suffix++;
+					id = `${id}_${suffix}`;
+				}
+				seenIds.add(id);
+				return {
+					...q,
+					id,
+					label: q.label || `Q${i + 1}`,
+					allowOther: q.allowOther !== false,
+				};
+			});
 
 			const isMulti = questions.length > 1;
 			const totalTabs = questions.length + 1; // questions + Submit
@@ -284,6 +295,7 @@ export default function questionnaire(pi: ExtensionAPI) {
 					// Select option
 					if (matchesKey(data, Key.enter) && q) {
 						const opt = opts[optionIndex];
+						if (!opt) return; // no options available
 						if (opt.isOther) {
 							inputMode = true;
 							inputQuestionId = q.id;