auq-mcp-server 3.2.11 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/tui-app.js +1 -1
- package/dist/package.json +1 -1
- package/dist/src/tui/components/StepperView.js +7 -0
- package/dist/src/tui/components/__tests__/StepperView.state.test.js +75 -0
- package/dist/src/tui-opentui/components/Footer.js +7 -1
- package/dist/src/tui-opentui/components/QuestionDisplay.js +2 -2
- package/dist/src/tui-opentui/components/StepperView.js +89 -8
- package/package.json +1 -1
package/dist/bin/tui-app.js
CHANGED
|
@@ -128,7 +128,7 @@ const App = ({ config }) => {
|
|
|
128
128
|
watcherInstance.stop();
|
|
129
129
|
}
|
|
130
130
|
if (notificationBatcherRef.current) {
|
|
131
|
-
notificationBatcherRef.current.
|
|
131
|
+
notificationBatcherRef.current.flush();
|
|
132
132
|
}
|
|
133
133
|
// Clear progress bar on unmount
|
|
134
134
|
clearProgress(notificationConfig);
|
package/dist/package.json
CHANGED
|
@@ -164,6 +164,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
164
164
|
// Track mount status to avoid state updates after unmount
|
|
165
165
|
const isMountedRef = useRef(true);
|
|
166
166
|
const skipSnapshotRef = useRef(true);
|
|
167
|
+
const sessionIdRef = useRef(null);
|
|
167
168
|
useEffect(() => {
|
|
168
169
|
isMountedRef.current = true;
|
|
169
170
|
return () => {
|
|
@@ -172,6 +173,12 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
172
173
|
}, []);
|
|
173
174
|
// Reset internal stepper state when the session changes (safety in case component isn't remounted)
|
|
174
175
|
useEffect(() => {
|
|
176
|
+
// Only run full initialization when the session actually changes.
|
|
177
|
+
// When a snapshot is saved (cursor movement), initialState prop changes but
|
|
178
|
+
// sessionId stays the same — skip the reset to avoid resetting the timer.
|
|
179
|
+
if (sessionId === sessionIdRef.current)
|
|
180
|
+
return;
|
|
181
|
+
sessionIdRef.current = sessionId;
|
|
175
182
|
const maxQuestionIndex = Math.max(0, sessionRequest.questions.length - 1);
|
|
176
183
|
if (initialState) {
|
|
177
184
|
const hydratedQuestionIndex = Math.min(Math.max(initialState.currentQuestionIndex, 0), maxQuestionIndex);
|
|
@@ -99,4 +99,79 @@ describe("StepperView SessionUIState boundary", () => {
|
|
|
99
99
|
showAbandonedConfirm: false,
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
|
+
it("should NOT reset answers when initialState changes for the same sessionId", async () => {
|
|
103
|
+
const onStateSnapshot = vi.fn();
|
|
104
|
+
const initialState = {
|
|
105
|
+
currentQuestionIndex: 0,
|
|
106
|
+
answers: new Map([[0, { selectedOption: "TypeScript" }]]),
|
|
107
|
+
elaborateMarks: new Map(),
|
|
108
|
+
focusContext: "option",
|
|
109
|
+
focusedOptionIndex: 0,
|
|
110
|
+
showReview: false,
|
|
111
|
+
};
|
|
112
|
+
const refreshedInitialState = {
|
|
113
|
+
currentQuestionIndex: 0,
|
|
114
|
+
answers: new Map([[0, { selectedOption: "Python" }]]),
|
|
115
|
+
elaborateMarks: new Map(),
|
|
116
|
+
focusContext: "option",
|
|
117
|
+
focusedOptionIndex: 0,
|
|
118
|
+
showReview: false,
|
|
119
|
+
};
|
|
120
|
+
const instance = renderStepper({ initialState, onStateSnapshot });
|
|
121
|
+
instance.stdin.write("\t");
|
|
122
|
+
await vi.waitFor(() => {
|
|
123
|
+
expect(onStateSnapshot).toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
instance.rerender(React.createElement(ThemeContext.Provider, { value: mockThemeValue },
|
|
126
|
+
React.createElement(ConfigProvider, null,
|
|
127
|
+
React.createElement(StepperView, { sessionId: sessionRequest.sessionId, sessionRequest: sessionRequest, initialState: refreshedInitialState, onStateSnapshot: onStateSnapshot }))));
|
|
128
|
+
instance.stdin.write("\t");
|
|
129
|
+
await vi.waitFor(() => {
|
|
130
|
+
expect(onStateSnapshot.mock.calls.length).toBeGreaterThanOrEqual(2);
|
|
131
|
+
});
|
|
132
|
+
const [, snapshotState] = onStateSnapshot.mock.lastCall;
|
|
133
|
+
expect(snapshotState.currentQuestionIndex).toBe(2);
|
|
134
|
+
expect(snapshotState.answers.get(0)?.selectedOption).toBe("TypeScript");
|
|
135
|
+
expect(snapshotState.answers.get(0)?.selectedOption).not.toBe("Python");
|
|
136
|
+
});
|
|
137
|
+
it("should reset answers when sessionId changes", async () => {
|
|
138
|
+
const onStateSnapshot = vi.fn();
|
|
139
|
+
const initialState = {
|
|
140
|
+
currentQuestionIndex: 0,
|
|
141
|
+
answers: new Map([[0, { selectedOption: "TypeScript" }]]),
|
|
142
|
+
elaborateMarks: new Map(),
|
|
143
|
+
focusContext: "option",
|
|
144
|
+
focusedOptionIndex: 0,
|
|
145
|
+
showReview: false,
|
|
146
|
+
};
|
|
147
|
+
const newSessionInitialState = {
|
|
148
|
+
currentQuestionIndex: 0,
|
|
149
|
+
answers: new Map([[0, { selectedOption: "Python" }]]),
|
|
150
|
+
elaborateMarks: new Map(),
|
|
151
|
+
focusContext: "option",
|
|
152
|
+
focusedOptionIndex: 0,
|
|
153
|
+
showReview: false,
|
|
154
|
+
};
|
|
155
|
+
const instance = renderStepper({
|
|
156
|
+
sessionId: "test-1",
|
|
157
|
+
initialState,
|
|
158
|
+
onStateSnapshot,
|
|
159
|
+
});
|
|
160
|
+
instance.stdin.write("\t");
|
|
161
|
+
await vi.waitFor(() => {
|
|
162
|
+
expect(onStateSnapshot).toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
instance.rerender(React.createElement(ThemeContext.Provider, { value: mockThemeValue },
|
|
165
|
+
React.createElement(ConfigProvider, null,
|
|
166
|
+
React.createElement(StepperView, { sessionId: "test-2", sessionRequest: sessionRequest, initialState: newSessionInitialState, onStateSnapshot: onStateSnapshot }))));
|
|
167
|
+
instance.stdin.write("\t");
|
|
168
|
+
await vi.waitFor(() => {
|
|
169
|
+
expect(onStateSnapshot.mock.calls.length).toBeGreaterThanOrEqual(2);
|
|
170
|
+
});
|
|
171
|
+
const [snapshotSessionId, snapshotState] = onStateSnapshot.mock.lastCall;
|
|
172
|
+
expect(snapshotSessionId).toBe("test-2");
|
|
173
|
+
expect(snapshotState.currentQuestionIndex).toBe(1);
|
|
174
|
+
expect(snapshotState.answers.get(0)?.selectedOption).toBe("Python");
|
|
175
|
+
expect(snapshotState.answers.get(0)?.selectedOption).not.toBe("TypeScript");
|
|
176
|
+
});
|
|
102
177
|
});
|
|
@@ -8,7 +8,7 @@ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧",
|
|
|
8
8
|
* Footer component — displays context-aware keybinding hints.
|
|
9
9
|
* Shows different shortcuts based on current focus context and question type.
|
|
10
10
|
*/
|
|
11
|
-
export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, showSessionSwitching = false, customInputValue: _customInputValue = "", hasRecommendedOptions = false, hasAnyRecommendedInSession = false, isSubmitting = false, hasUpdate = false, }) => {
|
|
11
|
+
export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, showSessionSwitching = false, customInputValue: _customInputValue = "", hasRecommendedOptions = false, hasAnyRecommendedInSession = false, isSubmitting = false, hasUpdate = false, showMultiToggleHint = false, isForceMultiActive = false, }) => {
|
|
12
12
|
const { theme } = useTheme();
|
|
13
13
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
14
14
|
// Animate spinner when submitting
|
|
@@ -63,6 +63,12 @@ export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, show
|
|
|
63
63
|
bindings.push({ key: KEY_LABELS.SELECT, action: t("footer.select") });
|
|
64
64
|
bindings.push({ key: KEY_LABELS.SELECT_NEXT, action: t("footer.selectNext") });
|
|
65
65
|
}
|
|
66
|
+
if (showMultiToggleHint) {
|
|
67
|
+
bindings.push({
|
|
68
|
+
key: "M",
|
|
69
|
+
action: isForceMultiActive ? "Disable Multi-select" : "Toggle Multi-select",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
66
72
|
if (hasRecommendedOptions) {
|
|
67
73
|
bindings.push({ key: KEY_LABELS.RECOMMEND, action: t("footer.recommended") });
|
|
68
74
|
}
|
|
@@ -9,7 +9,7 @@ import { TabBar } from "./TabBar.js";
|
|
|
9
9
|
* QuestionDisplay shows a single question with its options.
|
|
10
10
|
* Composes TabBar, MarkdownPrompt, OptionsList, and Footer.
|
|
11
11
|
*/
|
|
12
|
-
export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customAnswer = "", showSessionSwitching, elapsedLabel, onChangeCustomAnswer, onSelectOption, questions, selectedOption, onAdvanceToNext, answers, onToggleOption, multiSelect, focusContext, onFocusContextChange, focusedOptionIndex, onFocusedOptionIndexChange, workingDirectory, onRecommendedDetected, hasRecommendedOptions, hasAnyRecommendedInSession, elaborateMarks, onElaborateSelect, elaborateText = "", onElaborateTextChange, onSelectIndex, }) => {
|
|
12
|
+
export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customAnswer = "", showSessionSwitching, elapsedLabel, onChangeCustomAnswer, onSelectOption, questions, selectedOption, onAdvanceToNext, answers, onToggleOption, multiSelect, focusContext, onFocusContextChange, focusedOptionIndex, onFocusedOptionIndexChange, workingDirectory, onRecommendedDetected, hasRecommendedOptions, hasAnyRecommendedInSession, elaborateMarks, onElaborateSelect, elaborateText = "", onElaborateTextChange, onSelectIndex, showMultiToggleHint = false, isForceMultiActive = false, }) => {
|
|
13
13
|
const { theme } = useTheme();
|
|
14
14
|
// Handle option selection
|
|
15
15
|
const handleSelectOption = (label) => {
|
|
@@ -19,5 +19,5 @@ export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customA
|
|
|
19
19
|
const handleCustomAnswerChange = (text) => {
|
|
20
20
|
onChangeCustomAnswer(text);
|
|
21
21
|
};
|
|
22
|
-
return (_jsxs("box", { style: { flexDirection: "column" }, children: [workingDirectory && (_jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.directory.label, children: "📁" }), _jsx("text", { style: { fg: theme.components.directory.path }, children: ` ${workingDirectory}` })] })), _jsx(TabBar, { currentIndex: currentQuestionIndex, questions: questions, answers: answers, elaborateMarks: elaborateMarks, onSelectIndex: onSelectIndex }), _jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("box", { children: _jsx(MarkdownPrompt, { text: currentQuestion.prompt }) }), _jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.questionDisplay.elapsed, children: elapsedLabel }), _jsx("text", { style: { fg: theme.components.questionDisplay.typeIndicator }, children: ` [${multiSelect ? t("question.multipleChoice") : t("question.singleChoice")}]` })] })] }), _jsx(OptionsList, { customValue: customAnswer, isFocused: true, onAdvance: onAdvanceToNext, onCustomChange: handleCustomAnswerChange, onSelect: handleSelectOption, options: currentQuestion.options, selectedOption: selectedOption, showCustomInput: true, onToggle: onToggleOption, multiSelect: multiSelect, selectedOptions: answers.get(currentQuestionIndex)?.selectedOptions, onFocusContextChange: onFocusContextChange, focusedIndex: focusedOptionIndex, onFocusedIndexChange: onFocusedOptionIndexChange, onRecommendedDetected: onRecommendedDetected, questionKey: currentQuestionIndex, isElaborateMarked: elaborateMarks?.has(currentQuestionIndex), onElaborateSelect: onElaborateSelect, elaborateText: elaborateText, onElaborateTextChange: onElaborateTextChange }), _jsx("box", { style: { marginTop: 1 }, children: _jsx(Footer, { focusContext: focusContext, multiSelect: multiSelect ?? false, customInputValue: customAnswer, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, showSessionSwitching: showSessionSwitching }) })] }));
|
|
22
|
+
return (_jsxs("box", { style: { flexDirection: "column" }, children: [workingDirectory && (_jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.directory.label, children: "📁" }), _jsx("text", { style: { fg: theme.components.directory.path }, children: ` ${workingDirectory}` })] })), _jsx(TabBar, { currentIndex: currentQuestionIndex, questions: questions, answers: answers, elaborateMarks: elaborateMarks, onSelectIndex: onSelectIndex }), _jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("box", { children: _jsx(MarkdownPrompt, { text: currentQuestion.prompt }) }), _jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.questionDisplay.elapsed, children: elapsedLabel }), _jsx("text", { style: { fg: theme.components.questionDisplay.typeIndicator }, children: ` [${multiSelect ? t("question.multipleChoice") : t("question.singleChoice")}]` })] })] }), _jsx(OptionsList, { customValue: customAnswer, isFocused: true, onAdvance: onAdvanceToNext, onCustomChange: handleCustomAnswerChange, onSelect: handleSelectOption, options: currentQuestion.options, selectedOption: selectedOption, showCustomInput: true, onToggle: onToggleOption, multiSelect: multiSelect, selectedOptions: answers.get(currentQuestionIndex)?.selectedOptions, onFocusContextChange: onFocusContextChange, focusedIndex: focusedOptionIndex, onFocusedIndexChange: onFocusedOptionIndexChange, onRecommendedDetected: onRecommendedDetected, questionKey: currentQuestionIndex, isElaborateMarked: elaborateMarks?.has(currentQuestionIndex), onElaborateSelect: onElaborateSelect, elaborateText: elaborateText, onElaborateTextChange: onElaborateTextChange }), _jsx("box", { style: { marginTop: 1 }, children: _jsx(Footer, { focusContext: focusContext, multiSelect: multiSelect ?? false, customInputValue: customAnswer, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, showSessionSwitching: showSessionSwitching, showMultiToggleHint: showMultiToggleHint, isForceMultiActive: isForceMultiActive }) })] }));
|
|
23
23
|
};
|
|
@@ -35,8 +35,14 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
35
35
|
const [hasRecommendedOptions, setHasRecommendedOptions] = useState(false);
|
|
36
36
|
const [hasAnyRecommendedInSession, setHasAnyRecommendedInSession] = useState(false);
|
|
37
37
|
const [elaborateMarks, setElaborateMarks] = useState(new Map());
|
|
38
|
+
const [forceMultiByQuestion, setForceMultiByQuestion] = useState(new Set());
|
|
38
39
|
const safeIndex = Math.min(currentQuestionIndex, sessionRequest.questions.length - 1);
|
|
39
40
|
const currentQuestion = sessionRequest.questions[safeIndex];
|
|
41
|
+
const isQuestionMultiSelect = (questionIndex) => {
|
|
42
|
+
const question = sessionRequest.questions[questionIndex];
|
|
43
|
+
return Boolean(question?.multiSelect || forceMultiByQuestion.has(questionIndex));
|
|
44
|
+
};
|
|
45
|
+
const currentQuestionMultiSelect = isQuestionMultiSelect(currentQuestionIndex);
|
|
40
46
|
const sessionCreatedAt = useMemo(() => {
|
|
41
47
|
const parsed = Date.parse(sessionRequest.timestamp);
|
|
42
48
|
return Number.isNaN(parsed) ? Date.now() : parsed;
|
|
@@ -82,12 +88,14 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
82
88
|
newAnswers.set(currentQuestionIndex, {
|
|
83
89
|
...existingAnswer,
|
|
84
90
|
selectedOption: undefined,
|
|
91
|
+
selectedOptions: undefined,
|
|
85
92
|
});
|
|
86
93
|
}
|
|
87
94
|
else {
|
|
88
95
|
newAnswers.set(currentQuestionIndex, {
|
|
89
96
|
...existingAnswer,
|
|
90
97
|
selectedOption: label,
|
|
98
|
+
selectedOptions: undefined,
|
|
91
99
|
customText: undefined,
|
|
92
100
|
});
|
|
93
101
|
}
|
|
@@ -115,6 +123,8 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
115
123
|
? [...currentSelections, label]
|
|
116
124
|
: currentSelections.filter((l) => l !== label);
|
|
117
125
|
newAnswers.set(currentQuestionIndex, {
|
|
126
|
+
...existing,
|
|
127
|
+
selectedOption: undefined,
|
|
118
128
|
selectedOptions: newSelections,
|
|
119
129
|
customText: existing.customText,
|
|
120
130
|
});
|
|
@@ -136,12 +146,12 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
136
146
|
setAnswers((prev) => {
|
|
137
147
|
const newAnswers = new Map(prev);
|
|
138
148
|
const existing = newAnswers.get(currentQuestionIndex) || {};
|
|
139
|
-
const
|
|
149
|
+
const isMultiSelect = isQuestionMultiSelect(currentQuestionIndex);
|
|
140
150
|
newAnswers.set(currentQuestionIndex, {
|
|
141
151
|
...existing,
|
|
142
152
|
customText: text,
|
|
143
153
|
// Single-choice: clear selectedOption when typing custom text
|
|
144
|
-
...(text.trim().length > 0 && !
|
|
154
|
+
...(text.trim().length > 0 && !isMultiSelect ? { selectedOption: undefined } : {}),
|
|
145
155
|
});
|
|
146
156
|
return newAnswers;
|
|
147
157
|
});
|
|
@@ -193,6 +203,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
193
203
|
setShowReview(false);
|
|
194
204
|
}
|
|
195
205
|
setSubmitting(false);
|
|
206
|
+
setForceMultiByQuestion(new Set());
|
|
196
207
|
setShowRejectionConfirm(false);
|
|
197
208
|
setElapsedSeconds(0);
|
|
198
209
|
skipSnapshotRef.current = true;
|
|
@@ -266,14 +277,34 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
266
277
|
const sessionManager = new SessionManager({
|
|
267
278
|
baseDir: getSessionDirectory(),
|
|
268
279
|
});
|
|
269
|
-
//
|
|
270
|
-
const
|
|
280
|
+
// Apply forced single/multi override before persisting answers
|
|
281
|
+
const normalizedAnswers = userAnswers.map((answer) => {
|
|
282
|
+
const isMultiSelect = isQuestionMultiSelect(answer.questionIndex);
|
|
283
|
+
if (isMultiSelect) {
|
|
284
|
+
const selectedOptions = answer.selectedOptions ??
|
|
285
|
+
(answer.selectedOption ? [answer.selectedOption] : undefined);
|
|
286
|
+
return {
|
|
287
|
+
...answer,
|
|
288
|
+
selectedOption: undefined,
|
|
289
|
+
selectedOptions,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const selectedOption = answer.selectedOption ?? answer.selectedOptions?.[0];
|
|
293
|
+
return {
|
|
294
|
+
...answer,
|
|
295
|
+
selectedOption,
|
|
296
|
+
selectedOptions: undefined,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
const allAnswers = [...normalizedAnswers];
|
|
271
300
|
elaborateMarks.forEach((customExplanation, questionIndex) => {
|
|
272
301
|
const question = sessionRequest.questions[questionIndex];
|
|
273
302
|
if (question) {
|
|
274
303
|
const elaborateRequest = ResponseFormatter.formatElaborateRequest(questionIndex, question.title, question.prompt, customExplanation || undefined);
|
|
275
304
|
allAnswers.push({
|
|
276
305
|
questionIndex,
|
|
306
|
+
selectedOption: undefined,
|
|
307
|
+
selectedOptions: undefined,
|
|
277
308
|
customText: elaborateRequest,
|
|
278
309
|
timestamp: new Date().toISOString(),
|
|
279
310
|
});
|
|
@@ -340,7 +371,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
340
371
|
return newMarks;
|
|
341
372
|
});
|
|
342
373
|
// In single-select mode, clear selected option when marking elaborate
|
|
343
|
-
if (isMarking && !
|
|
374
|
+
if (isMarking && !currentQuestionMultiSelect) {
|
|
344
375
|
setAnswers((prev) => {
|
|
345
376
|
const existing = prev.get(currentQuestionIndex);
|
|
346
377
|
if (existing?.selectedOption || existing?.customText) {
|
|
@@ -377,6 +408,48 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
377
408
|
});
|
|
378
409
|
}
|
|
379
410
|
};
|
|
411
|
+
const handleToggleForceMulti = () => {
|
|
412
|
+
if (currentQuestion.multiSelect)
|
|
413
|
+
return;
|
|
414
|
+
const wasForced = forceMultiByQuestion.has(currentQuestionIndex);
|
|
415
|
+
setForceMultiByQuestion((prev) => {
|
|
416
|
+
const next = new Set(prev);
|
|
417
|
+
if (next.has(currentQuestionIndex)) {
|
|
418
|
+
next.delete(currentQuestionIndex);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
next.add(currentQuestionIndex);
|
|
422
|
+
}
|
|
423
|
+
return next;
|
|
424
|
+
});
|
|
425
|
+
setAnswers((prev) => {
|
|
426
|
+
const existing = prev.get(currentQuestionIndex);
|
|
427
|
+
if (!existing)
|
|
428
|
+
return prev;
|
|
429
|
+
const nextAnswers = new Map(prev);
|
|
430
|
+
if (wasForced) {
|
|
431
|
+
const firstSelected = existing.selectedOptions?.[0] ?? existing.selectedOption;
|
|
432
|
+
nextAnswers.set(currentQuestionIndex, {
|
|
433
|
+
...existing,
|
|
434
|
+
selectedOption: firstSelected,
|
|
435
|
+
selectedOptions: undefined,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
const promotedSelections = existing.selectedOptions?.length
|
|
440
|
+
? existing.selectedOptions
|
|
441
|
+
: existing.selectedOption
|
|
442
|
+
? [existing.selectedOption]
|
|
443
|
+
: [];
|
|
444
|
+
nextAnswers.set(currentQuestionIndex, {
|
|
445
|
+
...existing,
|
|
446
|
+
selectedOption: undefined,
|
|
447
|
+
selectedOptions: promotedSelections,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return nextAnswers;
|
|
451
|
+
});
|
|
452
|
+
};
|
|
380
453
|
// Keyboard handling for abandoned confirmation dialog
|
|
381
454
|
useKeyboard((key) => {
|
|
382
455
|
if (!showAbandonedConfirm)
|
|
@@ -430,7 +503,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
430
503
|
}
|
|
431
504
|
const recommendedOptions = question.options.filter((opt) => isRecommendedOption(opt.label));
|
|
432
505
|
if (recommendedOptions.length > 0) {
|
|
433
|
-
if (
|
|
506
|
+
if (isQuestionMultiSelect(i)) {
|
|
434
507
|
newAnswers.set(i, {
|
|
435
508
|
selectedOptions: recommendedOptions.map((opt) => opt.label),
|
|
436
509
|
});
|
|
@@ -454,11 +527,12 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
454
527
|
const question = currentQuestion;
|
|
455
528
|
const recommendedOptions = question.options.filter((opt) => isRecommendedOption(opt.label));
|
|
456
529
|
if (recommendedOptions.length > 0) {
|
|
457
|
-
if (
|
|
530
|
+
if (currentQuestionMultiSelect) {
|
|
458
531
|
setAnswers((prev) => {
|
|
459
532
|
const newAnswers = new Map(prev);
|
|
460
533
|
newAnswers.set(currentQuestionIndex, {
|
|
461
534
|
...newAnswers.get(currentQuestionIndex),
|
|
535
|
+
selectedOption: undefined,
|
|
462
536
|
selectedOptions: recommendedOptions.map((opt) => opt.label),
|
|
463
537
|
});
|
|
464
538
|
return newAnswers;
|
|
@@ -470,6 +544,13 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
470
544
|
}
|
|
471
545
|
return;
|
|
472
546
|
}
|
|
547
|
+
if (key.name?.toLowerCase() === "m" &&
|
|
548
|
+
!key.ctrl &&
|
|
549
|
+
!isInTextInput &&
|
|
550
|
+
!currentQuestion.multiSelect) {
|
|
551
|
+
handleToggleForceMulti();
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
473
554
|
// Tab/Shift+Tab: Global question navigation
|
|
474
555
|
if (key.name === "tab" && !isInTextInput) {
|
|
475
556
|
if (key.shift) {
|
|
@@ -519,5 +600,5 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
|
|
|
519
600
|
return (_jsx("box", { style: { flexDirection: "column", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1 }, children: _jsx(ReviewScreen, { isSubmitting: submitting, answers: answers, elapsedLabel: elapsedLabel, onConfirm: handleConfirm, onGoBack: handleGoBack, questions: sessionRequest.questions, sessionId: sessionId, elaborateMarks: elaborateMarks }) }));
|
|
520
601
|
}
|
|
521
602
|
// Show question display (default)
|
|
522
|
-
return (_jsx("box", { style: { flexDirection: "column", paddingLeft: 2, paddingRight: 2 }, children: _jsx(QuestionDisplay, { currentQuestion: currentQuestion, currentQuestionIndex: currentQuestionIndex, customAnswer: currentAnswer?.customText, elapsedLabel: elapsedLabel, onAdvanceToNext: handleAdvanceToNext, onChangeCustomAnswer: handleChangeCustomAnswer, onSelectOption: handleSelectOption, onToggleOption: handleToggleOption, multiSelect: currentQuestion.multiSelect,
|
|
603
|
+
return (_jsx("box", { style: { flexDirection: "column", paddingLeft: 2, paddingRight: 2 }, children: _jsx(QuestionDisplay, { currentQuestion: currentQuestion, currentQuestionIndex: currentQuestionIndex, customAnswer: currentAnswer?.customText, elapsedLabel: elapsedLabel, onAdvanceToNext: handleAdvanceToNext, onChangeCustomAnswer: handleChangeCustomAnswer, onSelectOption: handleSelectOption, questions: sessionRequest.questions, onToggleOption: handleToggleOption, multiSelect: currentQuestionMultiSelect, showMultiToggleHint: !currentQuestion.multiSelect, isForceMultiActive: forceMultiByQuestion.has(currentQuestionIndex), selectedOption: currentAnswer?.selectedOption, answers: answers, focusContext: focusContext, onFocusContextChange: setFocusContext, focusedOptionIndex: focusedOptionIndex, onFocusedOptionIndexChange: setFocusedOptionIndex, workingDirectory: sessionRequest.workingDirectory, onRecommendedDetected: setHasRecommendedOptions, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, elaborateMarks: elaborateMarks, onElaborateSelect: handleElaborateSelect, elaborateText: elaborateMarks.get(currentQuestionIndex) || "", onElaborateTextChange: handleElaborateTextChange, onSelectIndex: (idx) => setCurrentQuestionIndex(Math.max(0, Math.min(idx, sessionRequest.questions.length - 1))), showSessionSwitching: !showReview && !showRejectionConfirm }) }));
|
|
523
604
|
};
|