fix question options rendering permanently disabled
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 <noreply@anthropic.com>
This commit is contained in:
@@ -106,8 +106,14 @@ describe('the escape hatch', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('a question the harness already resolved', () => {
|
// The regression that made this component dead on arrival, pinned from both sides.
|
||||||
test('is read-only — its answer cannot be changed', () => {
|
//
|
||||||
|
// 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 answers: string[] = [];
|
||||||
const resolved = {
|
const resolved = {
|
||||||
...ask([{ question: 'Which?', header: 'Store', options: OPTIONS }]),
|
...ask([{ question: 'Which?', header: 'Store', options: OPTIONS }]),
|
||||||
@@ -115,6 +121,19 @@ describe('a question the harness already resolved', () => {
|
|||||||
} as never;
|
} as never;
|
||||||
render(<QuestionActivity message={resolved} onAnswer={(t) => answers.push(t)} />);
|
render(<QuestionActivity message={resolved} onAnswer={(t) => answers.push(t)} />);
|
||||||
fireEvent.click(screen.getByText('Postgres'));
|
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(
|
||||||
|
<QuestionActivity
|
||||||
|
message={{ ...ask([{ question: 'Which?', header: 'Store', options: OPTIONS }]), output: 'x' } as never}
|
||||||
|
onAnswer={(t) => answers.push(t)}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByText('Postgres'));
|
||||||
|
fireEvent.click(screen.getByText('SQLite'));
|
||||||
|
expect(answers).toEqual(['Postgres']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -67,9 +67,16 @@ export const QuestionActivity = ({ message, onAnswer }: QuestionActivityProps) =
|
|||||||
const questions = input.questions;
|
const questions = input.questions;
|
||||||
if (!questions || questions.length === 0) return null;
|
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.
|
// Deliberately NOT gated on `message.output`.
|
||||||
const pending = message.output === undefined;
|
//
|
||||||
const isDisabled = sent || !pending;
|
// 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.
|
* Record one answer, and send once EVERY question has one.
|
||||||
|
|||||||
Reference in New Issue
Block a user