fix(client): restore the released text for non-text answers too

Only the free-text path sets consumedComposerText, so gating the restore on it
discarded the stashed ordinary message whenever the user answered by clicking an
option or skipping, while the deletion below ran regardless. The stash is now
restored whenever one exists, independently of the reset condition, and it
outranks whatever the composer holds since that is the answer draft the
submission just consumed.
This commit is contained in:
Marco Beretta 2026-08-29 00:20:14 +02:00
parent 73af634763
commit 7e15aa750c
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
2 changed files with 46 additions and 10 deletions

View file

@ -266,6 +266,39 @@ describe('useAskAnswerMode', () => {
expect(mockReleasedComposerText).toEqual({});
});
it('hands the released text back for an option answer too', () => {
/** Only the free-text path sets `consumedComposerText`, so gating the
* restore on it discarded the stash whenever the user answered by
* clicking an option (or skipping) instead of typing. */
mockReleasedComposerText = { a1: 'an ordinary unsent message' };
const askWithOptions = {
actionId: 'a1',
question: {
question: 'Pick one',
options: [
{ label: 'Blue', value: 'blue' },
{ label: 'Green', value: 'green' },
],
multiSelect: false,
},
} as unknown as typeof liveAsk;
mockUseGetMessages.mockReturnValue({ data: askWithOptions });
mockSubmitAskAnswer.mockImplementation(
(_actionId: string, _answer: string, options?: { onSuccess?: () => void }) => {
options?.onSuccess?.();
},
);
const { result } = renderHook(() => useAskAnswerMode('conversation-1'));
act(() => {
result.current.submitOption(0);
});
expect(mockSubmitAskAnswer).toHaveBeenCalledWith('a1', 'blue', expect.anything());
expect(mockSetComposerText).toHaveBeenLastCalledWith('text', 'an ordinary unsent message');
expect(mockReleasedComposerText).toEqual({});
});
it('does not let a delayed answer success clear the composer or selection after navigation', () => {
let finishAnswer: (() => void) | undefined;
mockUseGetMessages.mockReturnValue({ data: liveAsk });

View file

@ -369,20 +369,23 @@ export default function useAskAnswerMode(conversationId?: string | null) {
return next;
});
/** A successful answer never goes through `collapse`, so the released
* text has to be handed back here too or the user's ordinary
* message dies with the question. Restored in place of the reset
* when one is stashed; the entry is dropped either way so the map
* stays bounded. */
* text has to be handed back here too or the user's ordinary message
* dies with the question.
*
* Restored whenever one is stashed, independently of the reset
* condition below: only the free-text path sets
* `consumedComposerText`, so gating on it discarded the stash on an
* option click or Skip. The stash also outranks whatever the
* composer currently holds, which is the answer draft this
* submission just consumed. */
const released = releasedComposerTextRef.current[submittedActionId];
if (
if (released) {
currentScope.formContext?.setValue('text', released);
} else if (
(consumedComposerText || (wasActive && saveDrafts)) &&
currentScope.formContext?.getValues('text') === submittedComposerText
) {
if (released) {
currentScope.formContext.setValue('text', released);
} else {
currentScope.formContext.reset();
}
currentScope.formContext.reset();
}
setReleasedComposerText((current) => {
if (current[submittedActionId] == null) {