You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(mothership): virtualize chat transcript and isolate input from stream re-renders (#5019)
* perf(mothership): virtualize chat transcript and isolate input from stream re-renders
Long chats rendered every message into the DOM with no windowing — a custom
rAF "progressive list" only smeared the mount cost across frames without
capping it. At ~1000 messages this was 52k DOM nodes and a 21s main-thread
block on open, and the input toolbar re-rendered on every streamed token.
- Virtualize the message list with @tanstack/react-virtual using dynamic
measureElement, stable per-row keys, and a tuned size estimate. Only the
visible window mounts, so load cost is now flat regardless of transcript
length. Remove the now-redundant useProgressiveList hook.
- Memoize UserInput and stabilize its callbacks (useCallback in MothershipChat
and home) so streaming ticks no longer re-render the entire input toolbar.
- Keep the existing useAutoScroll for streaming stick-to-bottom (it reads the
virtualizer's real scrollHeight) and add a per-chat scrollToIndex for initial
positioning before paint.
Measured on a cloned 1032-message chat: time-to-rendered 26.3s -> 1.7s,
main-thread blocked 21.4s -> 0.8s, DOM nodes 52k -> 1.4k, typing-while-
streaming p-max 104ms -> 26ms. Adds scripts/perf/ harness used to validate.
* address review: pending-chat scroll, flash on tail unmount, empty-row gap, per-role estimate
- Pending-chat initial scroll (Cursor, high): seed the scrolled-chat guard with
a unique sentinel so a not-yet-persisted chat (undefined chatId) with messages
still scrolls to bottom instead of being treated as already-scrolled.
- Streaming-row flash (review of virtualization): pin the last row in the
rendered window via rangeExtractor so scrolling it out of the overscan window
and back mid-stream can't unmount/remount it and re-fire the reveal fade.
- Empty assistant row gap (Greptile): move the row gap from the virtual-item
wrapper into the row content so a null-rendering (finalised, empty) assistant
turn collapses to zero height instead of leaving a pb-6 blank slot.
- Per-role row-height estimate instead of a single blended constant, so the
scrollbar drifts less as off-screen rows resolve.
- Drop the scripts/perf harness from the PR.
* fix(mothership): preserve user-row top spacing in virtualized layout
Restoring pt-3/pt-2 on the user row keeps the exact inter-row rhythm from the
old space-y-6 layout: assistant→user gaps stay 24+12px and user→assistant stay
24px, instead of becoming uniform when the gap moved to per-row pb.
* fix(mothership): don't re-scroll when a pending chat persists its id
The per-chat initial-scroll guard treated undefined→persisted-id as a chat
switch and scrolled to the bottom again, yanking the viewport down if the user
had scrolled up mid-stream. Treat that transition as the same conversation:
adopt the id without re-scrolling. Genuine chat switches (id→different id) still
re-scroll.
0 commit comments