fix: multi-select Other coexistence + the malformed-assessment probe-done crash
Two live bugs from the modal-realism stress runs: 1. Multi-select ask: picking the custom 'Other…' row cleared every already-picked option, and picking any option cleared a typed custom answer — single-select displacement logic applied to both modes. The dock's selection is now a pure state machine in question-core (toggleOption/toggleOther over DockSelection, unit-tested): single displaces (unchanged), multi coexists — neither channel clears the other; the typed text always survives. 2. ProbeDoneCard crashed on a REJECTED probe_done call: the model stringified 'assessment' (the same double-encoding habit as the options bug), zod rejected the call, the model retried — but the card's input-preview fallback trusted the errored part's unvalidated input shape and died on assessment.strands.map. The preview now reads input only while the call is pending or streaming (guarded with Array.isArray); an output-error part renders a quiet malformed-and-retried note instead of the garbage. Pinned: question-core.test.ts (3 dock-selection machine cases), render-cards.test.tsx (4 probe-done card cases incl. the exact live stringified shape, plus the multi-select ask markup pin). Gates: tsc, lint, build, 315/315.
This commit is contained in:
@@ -19,6 +19,9 @@ import {
|
||||
quizInputProblems,
|
||||
quizInputSchema,
|
||||
resolveCorrect,
|
||||
toggleOption,
|
||||
toggleOther,
|
||||
emptyDockSelection,
|
||||
shuffleOptions,
|
||||
type QuestionData,
|
||||
toClientQuestion,
|
||||
@@ -434,3 +437,50 @@ describe("makeId", () => {
|
||||
assert.equal(ids.size, 200);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── the dock selection state machine (multi-select Other coexistence) ──
|
||||
// The live bug: in a multi-select ask, picking the custom "Other…" row
|
||||
// cleared every already-picked option (single-select displacement logic
|
||||
// applied to both modes). The machine lives here, pure and pinned.
|
||||
describe("dock selection (toggleOption / toggleOther)", () => {
|
||||
it("multi-select: options and Other coexist — neither clears the other", () => {
|
||||
let sel = emptyDockSelection();
|
||||
sel = toggleOption("multi-select", sel, 0);
|
||||
sel = toggleOption("multi-select", sel, 1);
|
||||
assert.deepEqual([...sel.selected], [0, 1]);
|
||||
// Picking Other keeps the sibling selections.
|
||||
sel = toggleOther("multi-select", sel);
|
||||
assert.deepEqual([...sel.selected], [0, 1], "Other never clears picked options");
|
||||
assert.equal(sel.otherSelected, true);
|
||||
// Picking another option keeps Other selected.
|
||||
sel = toggleOption("multi-select", sel, 2);
|
||||
assert.equal(sel.otherSelected, true, "options never clear a typed custom answer");
|
||||
assert.deepEqual([...sel.selected], [0, 1, 2]);
|
||||
// Deselecting an option toggles it alone.
|
||||
sel = toggleOption("multi-select", sel, 0);
|
||||
assert.deepEqual([...sel.selected], [1, 2]);
|
||||
// Other toggles off alone; the siblings survive.
|
||||
sel = toggleOther("multi-select", sel);
|
||||
assert.equal(sel.otherSelected, false);
|
||||
assert.deepEqual([...sel.selected], [1, 2]);
|
||||
});
|
||||
|
||||
it("multi-select: any selection clears dont-know", () => {
|
||||
let sel = { selected: new Set<number>(), otherSelected: false, dontKnow: true };
|
||||
sel = toggleOption("multi-select", sel, 0);
|
||||
assert.equal(sel.dontKnow, false);
|
||||
sel = toggleOther("multi-select", sel);
|
||||
assert.equal(sel.dontKnow, false);
|
||||
});
|
||||
|
||||
it("single-select: displacement semantics are unchanged", () => {
|
||||
let sel = emptyDockSelection();
|
||||
sel = toggleOption("single-select", sel, 0);
|
||||
sel = toggleOther("single-select", sel);
|
||||
assert.deepEqual([...sel.selected], [], "Other displaces the picked option");
|
||||
assert.equal(sel.otherSelected, true);
|
||||
sel = toggleOption("single-select", sel, 1);
|
||||
assert.equal(sel.otherSelected, false, "an option displaces Other");
|
||||
assert.deepEqual([...sel.selected], [1]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user