Repository navigation
fix(web): queued runs, relationships and changed files no longer re-render on every streamed token - #24055
Conversation
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused performance fix that makes existing queue, Git, relationship, and changed-files consumers ignore irrelevant streaming-only updates while preserving structural state and final-state updates. It adds targeted browser coverage without changing product defaults, APIs, schemas, or sensitive code. You can add or adjust custom eligibility rules. Learn more. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
juliusmarminge
left a comment
There was a problem hiding this comment.
Thanks for tackling these streaming renders! The Git actions subscription is already covered by #27418 (aaee7743f8). Could you cut the Git actions changes and keep the remaining queue, relationships, and changed-files fixes?
8c51994 to
c29c437
Compare
|
Thanks! Rebased onto main and cut the Git actions changes (component and test), since #27418 covers them. The queue, relationships and changed-files fixes remain. Main's newer QueuedRunsControl tests now run on the real atoms, and the ThreadRelationshipsControl test mock picks up the renamed hook. |
…ender on every streamed token stops re-rendering it. QueuedRunsControl, ThreadRelationshipsPanel and the changed-files section still subscribed to the live projection or shell, so they re-rendered on every token although they only read thread structure. Move them to the quiet hooks, and let deriveThreadRelationshipGraph and resolveMergeBackTargetThreadId take a ThreadStructure. Co-Authored-By: Claude Opus 5.5 <[email protected]>
c29c437 to
187ca27
Compare
…rerenders Co-Authored-By: Claude Opus 5.5 <[email protected]>
The relationships panel read its shells from the live thread projection and the live environment shells, so every streamed token and every itemCount, visibleItemCount and updatedAt tick on the running thread re-rendered it. Select from the quiet thread structure and hand back each related thread's quiet shell. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…essage A streaming reply's text also lands on its conversation message, and the quiet thread compared messages by identity, so those updates still reached queued runs and relationships. Compare messages that stay streaming apart from their text and updatedAt, as turn items already are. Co-Authored-By: Claude Opus 5.5 <[email protected]>
EvidenceOn each fresh server, created t2 with one completed Replay turn changing app.txt and other.txt; opened t2, forked its response, returned to t2 and confirmed Lineage and the two-file card; sent go with Enter, queued Run the tests next with Ctrl+Enter, observed both streamed replies and queue completion, then returned to the completed card. Environment: Linux Chromium, 1280×800, light theme; fresh isolated state on each commit. Both sides use the same Replay recording with paragraph breaks to expose streaming updates. The overlay observes real React commits correlated with incoming WebSocket updates. Proven: the scenario fails on base 248f5b64cc and passes on head 64e4d15b28.
Both sides produced 12 qualifying text-only frames and two quiet-shell frames. Text-only renders, ordered queue / relationships / completed files: 6 / 12 / 0 before, 0 / 0 / 0 after. Quiet-shell renders: 0 / 2 / 1 before, 0 / 0 / 0 after. The queued follow-up executed, and Lineage and the completed two-file card remained correct. Scenario and Replay seed · Before attribution · After attribution No elapsed-time speedup is claimed. Work by gpt-6.1-sol through the Codex harness. Not shown:
|
…ender on every streamed token (pingdotgg#24055) Co-authored-by: Julius Marminge <[email protected]> (cherry picked from commit 4c2f04d)






Problem
While a reply streams, the server replaces the assistant turn item many times a second. #23117 added
useQuietThreadProjection, a thread projection that keeps its identity while only streamed text changes, and moved ChatView onto it. Some components that stay mounted during streaming still read the live projection or shell, so they re-render on every token even though they never read streamed text:QueuedRunsControlis always mounted under a server thread's composer. Each render runsderiveThreadQueueWorkflowStatetwice and builds a Set of every message id.ThreadRelationshipsPanel(thread details panel) reads the full projection for the thread and its parent, and re-runsderiveThreadRelationshipGraphand the subagent and workflow grouping each time.AssistantChangedFilesSectionInner, rendered for every turn that has changed files, reads the live shell but only usesprojectId,environmentIdandworktreePath.GitActionsControlhad the same problem; #27418 fixed it on main, so this PR no longer touches it.Change
These components now use the quiet hooks #23117 introduced:
QueuedRunsControlandThreadRelationshipsPaneluseuseQuietThreadProjection.deriveThreadQueueWorkflowState,deriveProviderSubagentStatus,resolveLatestMergeBackRunandcanDetachThreadProviderSessionalready acceptThreadStructure.deriveThreadRelationshipGraphandresolveMergeBackTargetThreadIdinpackages/client-runtime/src/state/threadRelationships.tsnow takeThreadStructuretoo. They only readthread,subagentsandcontextTransfers, and a full projection still fits that type, so other callers are unaffected.AssistantChangedFilesSectionInnerusesuseQuietThreadShell. The quiet shell ignores only streaming timestamps, item counts and the latest message, so the three fields it reads stay current.None of these components read streamed text,
updatedAtorlastVisitedAt. TheThreadStructuretype omits those fields, so typecheck would catch a read of any of them.Scope and approval
No prior issue or discussion. This finishes #23117's move of streaming-time readers onto the quiet projection: it only swaps hooks and narrows two parameter types to fields the functions already read. No new behavior, settings or UI; rendered output is unchanged.
Verification
Rebased onto current main (187ca27), Git actions changes dropped per review.
Added a browser test in
apps/web/src/components/chat/QueuedRunsControl.browser.test.tsx. It runs the real thread detail atoms (createEnvironmentThreadDetailAtoms) on a state atom the test writes and rendersQueuedRunsControlunder a ReactProfilerwith one queued follow-up. It pushes two streamed-text deltas for the running assistant item, keeping every other item's identity as the server does, and asserts the deltas cause no commits. As a positive control, it then completes the assistant item and asserts the control re-renders. Main's two newer tests in that file (sender labels, scroller containment) now run on the same real atoms instead of a mockeduseThreadProjection.useThreadProjection):AssertionError: expected 4 to be 2, one extra render per streamed token.ThreadRelationshipsControl.browser.test.tsx: its entities mock was renamed touseQuietThreadProjection; 10 tests pass.node scripts/check-changed.ts: format, lint, knip, typecheck (7 packages), tests beside the changes and react compiler all pass.Not checked:
ThreadRelationshipsPanelandAssistantChangedFilesSectionInnerswaps are covered by typecheck and by perf(web): streamed reply text no longer re-renders ChatView #23117'squietThreadAtomtests, not by render-count tests of their own.Implemented with Claude Opus 5.5 via Claude Code (running in T3 Code).
🤖 Generated with Claude Code