mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
perf(desktop): skip off-screen paint on non-virtualized transcript rows (WebKit)
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) <noreply@anthropic.com>
This commit is contained in:
parent
ed574777e3
commit
9b3e58b60b
@ -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()
|
||||
})
|
||||
|
||||
@ -1841,7 +1841,7 @@ export function MessageList({ sessionId, compact = false }: MessageListProps = {
|
||||
{content}
|
||||
</MeasuredRenderItem>
|
||||
) : (
|
||||
<div key={itemKey} className={CHAT_RENDER_ITEM_CLASS}>
|
||||
<div key={itemKey} className={`${CHAT_RENDER_ITEM_CLASS} chat-render-item--cv`}>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user