perf(site): defer off-screen messages and cache message parsing#23721
Draft
DanielleMaywood wants to merge 1 commit intomainfrom
Draft
perf(site): defer off-screen messages and cache message parsing#23721DanielleMaywood wants to merge 1 commit intomainfrom
DanielleMaywood wants to merge 1 commit intomainfrom
Conversation
Follows up on #23720 with two additional performance optimizations for chat navigation, measured at 43-60% reduction in long-task blocking time via PerformanceObserver. Batch layout reads in StickyUserMessage.update() — moves all getBoundingClientRect/offsetHeight reads before any style writes, eliminating forced reflow violations. Add WeakMap parse cache for parseMessageContent — the store preserves ChatMessage object identity for unchanged messages, so only the actively-changing message gets re-parsed per store tick. Avoids mutating cached objects by spreading a new entry with merged tools. Defer off-screen message rendering — on chat navigation scroll starts at the bottom, so messages above the fold get a lightweight placeholder. IntersectionObserver with 600px rootMargin triggers real rendering before the user scrolls to them. Only activates for conversations with 8+ messages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows up on #23720 with three additional performance optimizations for chat navigation, measured at 43–60% reduction in long-task blocking time via PerformanceObserver.
Changes
Batch layout reads in
StickyUserMessage.update()(ConversationTimeline.tsx): Moves allgetBoundingClientRect/offsetHeightreads before any style writes, eliminating forced reflow violations. Previously each read after a write forced the browser to flush pending layout.Add
WeakMapparse cache forparseMessageContent(messageParsing.ts): The store preservesChatMessageobject identity for unchanged messages, so only the actively-changing message gets re-parsed per store tick. Avoids mutating cached objects by spreading a new entry with merged tools. This was explicitly deferred as "Phase 2" in #23720.Defer off-screen message rendering (
ConversationTimeline.tsx): On chat navigation scroll starts at the bottom, so messages above the fold get a lightweight placeholder instead of the full Streamdown pipeline.IntersectionObserverwith 600pxrootMargintriggers real rendering before the user scrolls to them. Only activates for conversations with 8+ messages. The existingResizeObserverinScrollAnchoredContainerhandles scroll compensation when placeholders expand.Profiling data & analysis
React DevTools Profiler (pre-fix baseline)
Top components by self-time from 123-commit profiling session:
Navigation commits (#34, #81, #94) dominated by
Ctat 25–182ms self-time — all from synchronous Streamdown markdown parsing during mount. The deferred rendering eliminates this for off-screen messages.Long Task measurement methodology
Used
PerformanceObserver({ entryTypes: ["longtask"] })viaagent-browserto measure blocking time during identical navigation sequences on the same dataset, with and without fixes applied.Decision log
WeakMapoveruseMemofor parse cache becauseparseMessagesWithMergedToolsis a plain function, not a hook, and the cache benefits all callers includinggetEditableUserMessagePayload.IntersectionObserverdeferral over full virtualization — perf(site): memoize chat rendering hot path #23720 noted sticky messages + scroll anchoring make virtualization complex.DeferredMessagesidesteps this entirely.IMMEDIATE_RENDER_COUNT = 5andDEFERRED_THRESHOLD = 8chosen empirically — 5 messages fill the viewport, and conversations under 8 messages have negligible mount cost.