From 258d24039fe5edc7f90ff7580562f966c4702c22 Mon Sep 17 00:00:00 2001 From: Gille <4317663+helix4u@users.noreply.github.com> Date: Tue, 9 Jun 2026 19:16:20 -0600 Subject: [PATCH] fix(desktop): scope thinking disclosure pending state (#43197) --- .../assistant-ui/streaming.test.tsx | 33 +++++++++++++++++++ .../src/components/assistant-ui/thread.tsx | 4 ++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/assistant-ui/streaming.test.tsx b/apps/desktop/src/components/assistant-ui/streaming.test.tsx index c15b4696a21..08dba733ae1 100644 --- a/apps/desktop/src/components/assistant-ui/streaming.test.tsx +++ b/apps/desktop/src/components/assistant-ui/streaming.test.tsx @@ -164,6 +164,27 @@ function assistantMultiReasoningMessage(texts: string[]): ThreadMessage { } as ThreadMessage } +function assistantSeparatedReasoningMessage(): ThreadMessage { + return { + id: 'assistant-reasoning-separated-1', + role: 'assistant', + content: [ + { type: 'reasoning', text: ' Complete first thought.', status: { type: 'complete' } }, + { type: 'text', text: 'Interim answer.' }, + { type: 'reasoning', text: ' Streaming second thought.', status: { type: 'running' } } + ], + status: { type: 'running' }, + createdAt, + metadata: { + unstable_state: null, + unstable_annotations: [], + unstable_data: [], + steps: [], + custom: {} + } + } as ThreadMessage +} + function assistantTodoMessage( todos: Array<{ content: string; id: string; status: 'cancelled' | 'completed' | 'in_progress' | 'pending' }>, running = true @@ -685,6 +706,18 @@ describe('assistant-ui streaming renderer', () => { expect(reasoningParts[1]?.textContent).toBe('Second thought.') }) + it('does not reopen an earlier completed thinking group when a later group is running', () => { + const { container } = render() + + const disclosures = container.querySelectorAll('[data-slot="aui_thinking-disclosure"]') + expect(disclosures.length).toBe(2) + + expect(disclosures[0].querySelector('button')?.getAttribute('aria-expanded')).toBe('false') + expect(disclosures[1].querySelector('button')?.getAttribute('aria-expanded')).toBe('true') + expect(container.textContent).not.toContain('Complete first thought.') + expect(container.textContent).toContain('Interim answer.') + }) + it('renders live todo rows during a running turn', () => { const { container } = render( s.thread.isRunning && s.message.status?.type === 'running' && - s.message.parts.slice(Math.max(0, startIndex)).some(p => p?.type === 'reasoning' && p.status?.type !== 'complete') + s.message.parts + .slice(Math.max(0, startIndex), endIndex + 1) + .some(p => p?.type === 'reasoning' && p.status?.type !== 'complete') ) // A reasoning group with no actual text is pure noise — drop the whole