From b8da4f6ee92e5ab2c095eff6ddeebf7db12c1d3d Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Sat, 2 May 2026 22:06:53 +0800 Subject: [PATCH 1/2] fix(app): dismiss question dock after skipping when all questions are settled When skipCurrent was called on the last question (or when there is only one question), it cleared the answer but stayed on the same page without any visible action. The user was stuck and needed to click Submit separately. Extract resolveSkipAction as a pure decision function and make skipCurrent call submit() when no unsettled questions remain, consistent with how next() already auto-submits on the last question. --- .../composer/session-question-dock.test.ts | 39 +++++++++++++++++++ .../composer/session-question-dock.tsx | 32 ++++++++++++--- 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 packages/app/src/pages/session/composer/session-question-dock.test.ts diff --git a/packages/app/src/pages/session/composer/session-question-dock.test.ts b/packages/app/src/pages/session/composer/session-question-dock.test.ts new file mode 100644 index 000000000..5821bee68 --- /dev/null +++ b/packages/app/src/pages/session/composer/session-question-dock.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from "bun:test" +import { resolveSkipAction } from "./session-question-dock" + +describe("resolveSkipAction", () => { + test("navigates to next unsettled question when one exists after current", () => { + // 3 questions: Q0 settled, Q1 unsettled, Q2 (current) just skipped → now settled + const isSettled = (i: number) => i !== 1 + const result = resolveSkipAction(2, isSettled, 3) + expect(result).toEqual({ type: "navigate", tab: 1 }) + }) + + test("navigates to first unsettled overall when nothing after current", () => { + // 3 questions: Q0 unsettled, Q1 settled, Q2 (current) just skipped → settled + const isSettled = (i: number) => i !== 0 + const result = resolveSkipAction(2, isSettled, 3) + expect(result).toEqual({ type: "navigate", tab: 0 }) + }) + + test("submits when there is only one question and it was just skipped", () => { + // Single question: Q0 just skipped → settled + const isSettled = () => true + const result = resolveSkipAction(0, isSettled, 1) + expect(result).toEqual({ type: "submit" }) + }) + + test("submits when all questions are settled after skipping the last one", () => { + // 3 questions: all settled (Q0 and Q1 answered, Q2 just skipped) + const isSettled = () => true + const result = resolveSkipAction(2, isSettled, 3) + expect(result).toEqual({ type: "submit" }) + }) + + test("navigates to next unsettled before current when current is not the last", () => { + // 3 questions: Q0 settled, Q1 (current) just skipped → settled, Q2 unsettled + const isSettled = (i: number) => i !== 2 + const result = resolveSkipAction(1, isSettled, 3) + expect(result).toEqual({ type: "navigate", tab: 2 }) + }) +}) diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx index be6fb37c7..5ff70d5f6 100644 --- a/packages/app/src/pages/session/composer/session-question-dock.tsx +++ b/packages/app/src/pages/session/composer/session-question-dock.tsx @@ -13,6 +13,27 @@ type DraftAnswer = QuestionAnswer | undefined const cache = new Map() +/** + * After skipping a question (setting its answer to []), decide the next action. + * Returns either the tab to navigate to, or a submit signal when all questions are settled. + */ +export function resolveSkipAction( + currentTab: number, + isSettled: (i: number) => boolean, + total: number, +): { type: "navigate"; tab: number } | { type: "submit" } { + // First, look for an unsettled question after the current tab. + for (let i = currentTab + 1; i < total; i++) { + if (!isSettled(i)) return { type: "navigate", tab: i } + } + // Then, look for any unsettled question from the start. + for (let i = 0; i < total; i++) { + if (!isSettled(i)) return { type: "navigate", tab: i } + } + // All settled — time to submit. + return { type: "submit" } +} + function Mark(props: { multi: boolean; picked: boolean; onClick?: (event: MouseEvent) => void }) { return (