From a4c9b069dcebe59fa0a2e08c42349083c803bb75 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 15 Jul 2026 14:35:15 -0400 Subject: [PATCH] fix(desktop): stop reasoning text flashing/re-typing on every delta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reasoning ("Thinking") rendered through MarkdownTextContent with a smooth typewriter reveal. TextMessagePartProvider mints a fresh part object on every text change, and useSmooth resets its reveal to empty whenever the part identity changes — so each reasoning delta restarted the animation from the first character (h / hell / hello wo ...). Token-streaming reasoners (R1/Qwen/GLM/Claude thinking) fire a delta per token and flash hard; GPT-5's coarse reasoning summary updates too rarely to notice, which is why it looked fine. Drop smooth on the reasoning path so it plain-appends, exactly like the assistant answer already does. Removes the now-dead smooth plumbing too. --- .../components/assistant-ui/markdown-text.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.tsx index f9eeae55b63e..8f17685108ff 100644 --- a/apps/desktop/src/components/assistant-ui/markdown-text.tsx +++ b/apps/desktop/src/components/assistant-ui/markdown-text.tsx @@ -1,6 +1,6 @@ 'use client' -import { type SmoothOptions, TextMessagePartProvider, useMessagePartText } from '@assistant-ui/react' +import { TextMessagePartProvider, useMessagePartText } from '@assistant-ui/react' import { parseMarkdownIntoBlocks, type StreamdownTextComponents, @@ -342,7 +342,6 @@ interface MarkdownTextSurfaceProps { containerClassName?: string containerProps?: ComponentProps<'div'> defer?: boolean - smooth?: boolean | SmoothOptions } // Headings shrink to chat scale rather than the prose default (h1≈xl). Kept @@ -391,7 +390,7 @@ function HugeTextFallback({ containerClassName, text }: { containerClassName?: s ) } -function MarkdownTextSurface({ containerClassName, containerProps, defer, smooth }: MarkdownTextSurfaceProps) { +function MarkdownTextSurface({ containerClassName, containerProps, defer }: MarkdownTextSurfaceProps) { const { status, text } = useMessagePartText() const isStreaming = status.type === 'running' @@ -531,29 +530,25 @@ function MarkdownTextSurface({ containerClassName, containerProps, defer, smooth parseMarkdownIntoBlocksFn={parseMarkdownIntoBlocksCached} plugins={plugins} preprocess={preprocessWithTailRepair} - smooth={smooth} /> ) } -const SMOOTH_OPTIONS: SmoothOptions = { - drainMs: 500, - maxCharsPerFrame: 30, - minCommitMs: 33 -} - interface MarkdownTextContentProps extends MarkdownTextSurfaceProps { isRunning: boolean text: string } export function MarkdownTextContent({ isRunning, text, ...surfaceProps }: MarkdownTextContentProps) { - // Same path as the assistant answer. A reasoning-only smoothing wrapper used - // to sit here but stalled its char-reveal at empty (the part stays running - // the whole message), blanking the Thinking widget. + // No `smooth` on purpose — same as the assistant answer. `TextMessagePartProvider` + // mints a fresh part object on every `text` change, and useSmooth resets its + // reveal to empty whenever the part identity changes, so a smoothed reasoning + // stream re-types from the first character on every delta (the flash). Token- + // streaming reasoners (R1/Qwen/GLM/Claude thinking) hit it hardest; GPT-5's + // coarse summary updates too rarely to notice. Plain append matches the answer. return ( - + ) }