Skip to content

[frontend] P1 perf: streaming text embedded in message array rebuilds full chat list every animation frame (dual rAF smoothers) #212

@Interstellar-code

Description

@Interstellar-code

Problem

During SSE streaming, the live streaming text is baked into the same array that holds all historical messages, so every animation frame rebuilds the entire message list and re-runs the full filter/dedup/sort/group pipeline (~60×/sec). Cost scales with thread length — visible jank on long sessions.

Evidence

  1. src/screens/chat/chat-screen.tsx:1469finalDisplayMessages useMemo builds a NEW array embedding __streamingText: stableActiveStreamingText (line ~1573), with activeRealtimeStreamingText in its deps (~1605). Every rAF tick → new array identity.
  2. src/screens/chat/components/chat-message-list.tsx:2143areChatMessageListEqual short-circuits on prev.messages === next.messages and :2160 on prev.streamingText === next.streamingText — both fail every frame → full list re-render.
  3. Inside the list, displayMessages (:798, filter+dedup), displayEntries (:882), toolResultsByCallId (:1055), toolInteractionCount (:1125), and the streamingState signature map (:1160) all recompute over the FULL message set per frame.
  4. Two independent rAF typewriter loops run simultaneously for the same stream: use-streaming-message.ts:345-373 (pushTargetText rAF) and use-smooth-streaming-text.ts:56-78, fed at chat-screen.tsx:1444 — every revealed character pays the above cost twice.

Per-message memoization (MessageItem memo + areMessagesEqual) is solid and skips correctly — the O(N) work is all at the list level.

Fix

  • Render the streaming bubble as an isolated sibling component that subscribes to streaming state directly; keep finalDisplayMessages free of live text. OR memoize displayEntries/toolResultsByCallId on a content signature excluding __streamingText.
  • Drop one of the two rAF smoothing layers (the store text is already smoothed once).

Found in chat-area audit 2026-06-11 (Opus agent, verified against source).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions