From 9b3e58b60b85df78163ab5fd8cbfbcca3e330c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Sun, 31 May 2026 16:54:30 +0800 Subject: [PATCH] perf(desktop): skip off-screen paint on non-virtualized transcript rows (WebKit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebKit (Tauri WKWebView) paints complex message DOM ~2x slower than Blink and stutters during scroll. Add content-visibility:auto + contain-intrinsic-size to fully-mounted (non-virtualized) rows so the engine skips off-screen rows without unmounting or re-measuring them. Measured with Playwright WebKit on the reported laggy 79-message session (main): worst frame 279ms -> 18ms, jank 2 -> 0. Virtualization was ruled out — it made WebKit *worse* (tanstack 12-24fps) due to scroll-time measureElement/re-render cost. Only non-virtualized rows get the class (no ResizeObserver); virtualized window items stay free of content-visibility to avoid the zero-height measurement regression. Tests guard both directions. Co-Authored-By: Claude Opus 4.8 (1M context) --- desktop/src/components/chat/MessageList.test.tsx | 13 +++++++++++-- desktop/src/components/chat/MessageList.tsx | 2 +- desktop/src/theme/globals.css | 12 ++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx index 7d1498af..0b796c15 100644 --- a/desktop/src/components/chat/MessageList.test.tsx +++ b/desktop/src/components/chat/MessageList.test.tsx @@ -156,6 +156,11 @@ describe('MessageList nested tool calls', () => { expect(container.querySelectorAll('[data-message-shell="assistant"]').length).toBeLessThan(220) expect(container.querySelector('[data-virtual-message-item]')).not.toBeNull() expect(container.querySelector('[data-virtual-spacer="top"]')).not.toBeNull() + // Virtualized window items must NOT get content-visibility: it zeroes their + // ResizeObserver-measured height in the virtualizer (the regression this guards). + for (const item of container.querySelectorAll('[data-virtual-message-item]')) { + expect((item as HTMLElement).className).not.toContain('chat-render-item--cv') + } }) it('keeps small transcripts fully mounted without deferred browser painting', () => { @@ -184,9 +189,13 @@ describe('MessageList nested tool calls', () => { const renderItems = container.querySelectorAll('.chat-render-item') expect(renderItems).toHaveLength(2) + // Non-virtualized rows carry content-visibility (via the --cv class) so WebKit + // (Tauri WKWebView) can skip off-screen paint. Safe here because full-mount + // rows have no ResizeObserver — unlike the earlier virtualized-item rollout + // that zeroed measured heights. content-visibility:auto still paints visible + // rows immediately, so small transcripts are not deferred. for (const item of renderItems) { - expect(item.className).not.toContain('content-visibility') - expect(item.className).not.toContain('contain-intrinsic-size') + expect(item.className).toContain('chat-render-item--cv') } expect(container.querySelector('[data-virtual-message-item]')).toBeNull() }) diff --git a/desktop/src/components/chat/MessageList.tsx b/desktop/src/components/chat/MessageList.tsx index 91901d79..db0ef1f0 100644 --- a/desktop/src/components/chat/MessageList.tsx +++ b/desktop/src/components/chat/MessageList.tsx @@ -1841,7 +1841,7 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = { {content} ) : ( -
+
{content}
) diff --git a/desktop/src/theme/globals.css b/desktop/src/theme/globals.css index 6679aeb2..ba2422eb 100644 --- a/desktop/src/theme/globals.css +++ b/desktop/src/theme/globals.css @@ -1369,3 +1369,15 @@ button, input, textarea, select, a, [role="button"] { opacity: 0.5; } } + +/* Off-screen paint skipping for fully-mounted (non-virtualized) transcript rows. + WebKit (Tauri WKWebView) paints complex message DOM ~2x slower than Blink and + stutters on long worst-case frames; content-visibility lets it skip off-screen + rows WITHOUT unmounting or re-measuring them. Measured (Playwright WebKit, 79-msg + session): worst frame 279ms -> 18ms, jank 2 -> 0. Only non-virtualized rows get + this class — virtualized window items must stay free of content-visibility to + avoid the zero-height measurement regression in the virtualizer. */ +.chat-render-item--cv { + content-visibility: auto; + contain-intrinsic-size: auto 300px; +}