From 7953c18967d6fe7dbd5ee62365ada203d4ac36f4 Mon Sep 17 00:00:00 2001 From: Pascal Date: Sun, 1 Feb 2026 20:34:08 +0100 Subject: [PATCH] webui: fix UI freeze at high token rates with RAF yield The markdown coalescing loop was processing chunks back-to-back without yielding to the browser's paint cycle. At high token rates (250+ tok/s), this caused complete UI freeze as the main thread was perpetually busy. Add a requestAnimationFrame yield between processing batches. This allows the browser to paint at screen FPS regardless of token throughput. Chunks arriving during the yield are coalesced and processed together, so we skip intermediate states and jump straight to the latest content. Before: Chunk->process->Chunk->process->... (browser never paints = freeze) After: Chunk->process->[RAF]->coalesced chunks->process->[RAF]->... (screen FPS) Tested with 250 tok/s streams on 50K+ token contexts: smooth scrolling and responsive UI throughout. --- .../src/lib/components/app/content/MarkdownContent.svelte | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/server/webui/src/lib/components/app/content/MarkdownContent.svelte b/tools/server/webui/src/lib/components/app/content/MarkdownContent.svelte index c7fada7c66..48196d96e6 100644 --- a/tools/server/webui/src/lib/components/app/content/MarkdownContent.svelte +++ b/tools/server/webui/src/lib/components/app/content/MarkdownContent.svelte @@ -499,6 +499,7 @@ /** * Queues markdown for processing with coalescing support. * Only processes the latest markdown when multiple updates arrive quickly. + * Uses requestAnimationFrame to yield to browser paint between batches. * @param markdown - The markdown content to render */ async function updateRenderedBlocks(markdown: string) { @@ -516,6 +517,12 @@ pendingMarkdown = null; await processMarkdown(nextMarkdown); + + // Yield to browser for paint before processing next batch + // This prevents UI freeze at high token rates (250+ tok/s) + if (pendingMarkdown !== null) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + } } } catch (error) { console.error('Failed to process markdown:', error);