Repository navigation
fix(web): queued runs, git actions and relationships no longer re-render on every streamed token - #24055
fix(web): queued runs, git actions and relationships no longer re-render on every streamed token#24055Batuhan1999 wants to merge 1 commit into
Conversation
…der on every streamed token pingdotgg#23117 moved ChatView onto useQuietThreadProjection so streamed reply text stops re-rendering it. QueuedRunsControl, GitActionsControl, 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 <noreply@anthropic.com>
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. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
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.GitActionsControlsubscribes to the full projection only to useprojection.threadas a fallback when the quiet shell is missing.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.Change
These components now use the quiet hooks #23117 introduced:
QueuedRunsControl,GitActionsControlandThreadRelationshipsPaneluseuseQuietThreadProjection.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.All four changes fix the same problem: they finish #23117's move of streaming-time readers onto the quiet projection. Each one swaps a hook for the quiet hook that already exists.
Scope and approval
No prior issue or discussion. This is a very small, focused fix for an obvious bug. #23117 established the intended behavior (structural readers should not re-render on streamed text) and the mechanism, but some readers were left on the live subscriptions. The change only swaps hooks and narrows two parameter types to fields the functions already read. It adds no new behavior, settings or UI. The rendered output is unchanged. The only difference is that these components skip renders while text streams in.
Verification
Added a browser test in the existing
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 it rendersQueuedRunsControlunder a ReactProfilerwith one queued follow-up. It then pushes two streamed-text deltas for the running assistant item, keeping every other item's identity as the server does. The test asserts that the deltas cause no commits. As a positive control, it then completes the assistant item and asserts that the control does re-render.useThreadProjection): fails withAssertionError: expected 4 to be 2. Each of the two streamed tokens caused one extra render, plus twoderiveThreadQueueWorkflowStatecalls per render.GitActionsControl.browser.test.tsxonly had its mock renamed fromuseThreadProjectiontouseQuietThreadProjection, and it still passes.vp run check:changed upstream/main(7 changed files): format, lint, knip, typecheck (7 packages) and tests beside the changes (4 files, 25 tests) all pass.Not checked:
GitActionsControl,ThreadRelationshipsPanelandAssistantChangedFilesSectionInnerswaps are covered by typecheck, which confirms every field they read exists on the quiet type, and by perf(web): streamed reply text no longer re-renders ChatView #23117'squietThreadAtomtests. They have no render-count test of their own.Implemented with Claude Opus 5.5 via Claude Code (running in T3 Code).
🤖 Generated with Claude Code