From 8a4b96daf8f6966aa0ec8c45b0b4f704332d253f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 19 Aug 2026 01:57:16 +0000 Subject: [PATCH] fix question options rendering permanently disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AskUserQuestion has no answer channel: it resolves itself the moment it is called, returning "The user did not answer the questions." before the buttons have painted. QuestionActivity gated its options on `message.output === undefined`, so that self-resolve disabled every option on every question, always. Reported from real use — muted text, no-entry cursor, nothing clickable. The output means the tool call is over, which is true and irrelevant. The answer travels back as an ordinary chat message. `sent` is the only real latch. The test was the worse half: it asserted the greyed-out state was correct, using the literal self-resolve string as its fixture. Code and test were wrong in the same direction, so a green suite sat on top of a feature that had never once worked. Inverted it, and verified it fails against the old logic rather than just passing against the new. Co-Authored-By: Claude Opus 5 --- .../Chat/components/QuestionActivity.test.tsx | 25 ++++++++++++++++--- .../apps/Chat/components/QuestionActivity.tsx | 13 +++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.test.tsx b/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.test.tsx index 5f674076..02757cf0 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.test.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.test.tsx @@ -106,8 +106,14 @@ describe('the escape hatch', () => { }); }); -describe('a question the harness already resolved', () => { - test('is read-only — its answer cannot be changed', () => { +// The regression that made this component dead on arrival, pinned from both sides. +// +// AskUserQuestion has no answer channel: it resolves ITSELF, immediately, with the string below — so a +// real question always carries an output by the time it renders. The component gated on that output, and +// the test here asserted the resulting greyed-out state was correct. Both were wrong in the same +// direction, so the suite passed and every question in the product was unclickable. +describe('a question the SDK has already self-resolved', () => { + test('is still answerable — the output ends the tool call, not the conversation', () => { const answers: string[] = []; const resolved = { ...ask([{ question: 'Which?', header: 'Store', options: OPTIONS }]), @@ -115,6 +121,19 @@ describe('a question the harness already resolved', () => { } as never; render( answers.push(t)} />); fireEvent.click(screen.getByText('Postgres')); - expect(answers).toEqual([]); + expect(answers).toEqual(['Postgres']); + }); + + test('but answering twice is refused — the latch is what we sent, not what the tool returned', () => { + const answers: string[] = []; + render( + answers.push(t)} + />, + ); + fireEvent.click(screen.getByText('Postgres')); + fireEvent.click(screen.getByText('SQLite')); + expect(answers).toEqual(['Postgres']); }); }); diff --git a/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.tsx b/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.tsx index 4dd02b4e..38a57a45 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/QuestionActivity.tsx @@ -67,9 +67,16 @@ export const QuestionActivity = ({ message, onAnswer }: QuestionActivityProps) = const questions = input.questions; if (!questions || questions.length === 0) return null; - // A tool call with an output has already been resolved by the harness; nothing here can change it. - const pending = message.output === undefined; - const isDisabled = sent || !pending; + // Deliberately NOT gated on `message.output`. + // + // AskUserQuestion resolves itself the instant it is called — the SDK has no answer channel, so it + // returns "The user did not answer the questions." before these buttons have painted. Gating on the + // output therefore disabled every option on every question, always: the tool was rendered, greyed out, + // and unanswerable. A test asserted that as correct, so the suite stayed green over a dead feature. + // + // The output says the TOOL CALL is over, which is true and irrelevant. The answer travels back as an + // ordinary chat message, and a message can always be sent. `sent` is the only real latch. + const isDisabled = sent; /** * Record one answer, and send once EVERY question has one.