From be7eefec8dd33b060a03cfdffdda90d2dbbcb1a9 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 26 Aug 2026 13:40:35 -0500 Subject: [PATCH] fix(desktop): restore scroll on the capped thinking preview The live thinking body pins to the newest tokens and keeps max-h-40 after the turn settles so the transcript doesn't jump. overflow-hidden let that pin work in JS but clipped the rest of the thought. overflow-auto makes the cap a real scroller; overscroll-contain keeps the wheel from chaining into the transcript. Supersedes #73757. Co-authored-by: Dan Latimer --- .../assistant-ui/thread/message-parts.tsx | 6 +++-- .../assistant-ui/thread/streaming.test.tsx | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/thread/message-parts.tsx b/apps/desktop/src/components/assistant-ui/thread/message-parts.tsx index e50718b189..5fbd46eedd 100644 --- a/apps/desktop/src/components/assistant-ui/thread/message-parts.tsx +++ b/apps/desktop/src/components/assistant-ui/thread/message-parts.tsx @@ -237,10 +237,12 @@ const ThinkingDisclosure: FC<{ className={cn( // Body sits flush with the "Thinking" header — no left indent — // and inherits the disclosure-level opacity fade defined in - // styles.css (~0.67 at rest, 1 on hover/focus). - 'mt-0.5 w-full min-w-0 max-w-full overflow-hidden wrap-anywhere pb-1', + // styles.css (~0.67 at rest, 1 on hover/focus). overflow-auto so + // the max-h-40 preview is a real scroller, not a clip. + 'mt-0.5 w-full min-w-0 max-w-full overflow-auto overscroll-contain wrap-anywhere pb-1', isPreview && 'max-h-40' )} + data-slot="aui_thinking-body" ref={scrollRef} >
{children}
diff --git a/apps/desktop/src/components/assistant-ui/thread/streaming.test.tsx b/apps/desktop/src/components/assistant-ui/thread/streaming.test.tsx index 8d2d386ec4..4f82314c48 100644 --- a/apps/desktop/src/components/assistant-ui/thread/streaming.test.tsx +++ b/apps/desktop/src/components/assistant-ui/thread/streaming.test.tsx @@ -621,6 +621,28 @@ describe('assistant-ui streaming renderer', () => { expect(container.textContent).not.toContain('```ts') }) + it('keeps the height-capped thinking preview scrollable after the turn settles', async () => { + const { container, settle } = renderSettlingReasoning() + + const live = container.querySelector('[data-slot="aui_thinking-body"]')?.className ?? '' + + expect(live).toContain('max-h-40') + expect(live).toMatch(/\boverflow-auto\b/) + expect(live).not.toMatch(/\boverflow-hidden\b/) + + settle() + + await waitFor(() => { + expect(within(container).getByRole('button', { name: /thought/i })).toBeTruthy() + }) + + const settled = container.querySelector('[data-slot="aui_thinking-body"]')?.className ?? '' + + expect(settled).toContain('max-h-40') + expect(settled).toMatch(/\boverflow-auto\b/) + expect(settled).not.toMatch(/\boverflow-hidden\b/) + }) + it('does not collapse a live thinking preview when the turn settles', async () => { const { container, settle } = renderSettlingReasoning() const toggle = within(container).getByRole('button', { name: /thinking/i })