Skip to content

fix(web): queued runs, relationships and changed files no longer re-render on every streamed token - #24055

Merged
juliusmarminge merged 4 commits into
pingdotgg:mainfrom
Batuhan1999:fix/quiet-projection-rerenders
Nov 14, 2026
Merged

juliusmarminge merged 4 commits into
pingdotgg:mainfrom
Batuhan1999:fix/quiet-projection-rerenders

Conversation

@Batuhan1999

@Batuhan1999 Batuhan1999 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

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:

  • QueuedRunsControl is always mounted under a server thread's composer. Each render runs deriveThreadQueueWorkflowState twice and builds a Set of every message id.
  • ThreadRelationshipsPanel (thread details panel) reads the full projection for the thread and its parent, and re-runs deriveThreadRelationshipGraph and the subagent and workflow grouping each time.
  • AssistantChangedFilesSectionInner, rendered for every turn that has changed files, reads the live shell but only uses projectId, environmentId and worktreePath.

GitActionsControl had 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:

  • QueuedRunsControl and ThreadRelationshipsPanel use useQuietThreadProjection. deriveThreadQueueWorkflowState, deriveProviderSubagentStatus, resolveLatestMergeBackRun and canDetachThreadProviderSession already accept ThreadStructure. deriveThreadRelationshipGraph and resolveMergeBackTargetThreadId in packages/client-runtime/src/state/threadRelationships.ts now take ThreadStructure too. They only read thread, subagents and contextTransfers, and a full projection still fits that type, so other callers are unaffected.
  • AssistantChangedFilesSectionInner uses useQuietThreadShell. 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, updatedAt or lastVisitedAt. The ThreadStructure type 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 renders QueuedRunsControl under a React Profiler with 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 mocked useThreadProjection.

  • Before the fix (on useThreadProjection): AssertionError: expected 4 to be 2, one extra render per streamed token.
  • After the fix: all 4 QueuedRunsControl tests pass; the two streamed tokens cause 0 renders and the structural change still re-renders.
  • ThreadRelationshipsControl.browser.test.tsx: its entities mock was renamed to useQuietThreadProjection; 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:

  • No manual check in a running client and no profiler capture in a real app. The render-count test covers QueuedRunsControl only.
  • The ThreadRelationshipsPanel and AssistantChangedFilesSectionInner swaps are covered by typecheck and by perf(web): streamed reply text no longer re-renders ChatView #23117's quietThreadAtom tests, 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

@github-actions github-actions Bot added vouch:unvouched Author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Oct 23, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 8c51994

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.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

Web components now use quiet thread hooks for projection or shell reads. Thread relationship utilities accept ThreadStructure projections. Browser tests provide thread-state atoms and check QueuedRunsControl render counts during streaming updates and completion.

Changes: Thread state reads

Layer / File(s) Summary
Relationship projection reads
packages/client-runtime/src/state/threadRelationships.ts, apps/web/src/state/entities.ts, apps/web/src/components/chat/ThreadRelationshipsControl.tsx, apps/web/src/components/chat/ThreadRelationshipsControl.browser.test.tsx
Thread relationship utilities and the relationship-shell atom now use ThreadStructure. The relationship-shell atom reads quiet thread projections and uses quiet shell sources when available. ThreadRelationshipsPanel and its browser test use useQuietThreadProjection.
Quiet component reads and render validation
apps/web/src/components/chat/QueuedRunsControl.tsx, apps/web/src/components/chat/TimelineAssistantRows.tsx, apps/web/src/components/chat/QueuedRunsControl.browser.test.tsx
QueuedRunsControl reads its projection through useQuietThreadProjection, and TimelineAssistantRows reads its shell through useQuietThreadShell. Browser tests provide thread-state atoms and check render counts during streaming text updates and completion.

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

Merge Risk: Minimal · previous review 178d1

This change reduces unnecessary re-renders during streaming and leaves rendered output unchanged. No merge-blocking risk was found, and the change is easy to revert.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the fix (queued runs, relationships and changed files no longer re-rendering on streamed tokens). The raw summary and PR objectives show that the changeset does this: QueuedRunsControl…
Description check Passed The description follows the template. It has Problem, Change, Scope and Verification sections, and each carries specific content. The Problem section gives the reproduction context and the affected co…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the fix (queued runs, relationships and changed files no longer re-rendering on streamed tokens). The raw summary and PR objectives show that the changeset does this: QueuedRunsControl, ThreadRelationshipsControl and TimelineAssistantRows switch to quiet projection or shell hooks, and the threadRelationships and entities helpers narrow to ThreadStructure. The title is long at 98 characters, but it is conventional-commit style and matches the main change.

Full details: Description check

Explanation

The description follows the template. It has Problem, Change, Scope and Verification sections, and each carries specific content. The Problem section gives the reproduction context and the affected components. The Change section explains the hook swaps and the type narrowing. The Scope section says there is no linked issue and explains why the PR is a bug fix that needs no issue. The Verification section gives concrete before/after render counts and lists what was not checked. It does not include before/after screenshots, but the change renders identical output, so the UI screenshot requirement does not clearly apply. The template's agent-model line is present.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 40c2e17 and 178d177.

Files selected for processing: 1

  • apps/web/src/state/entities.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@juliusmarminge juliusmarminge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@Batuhan1999
Batuhan1999 force-pushed the fix/quiet-projection-rerenders branch from 8c51994 to c29c437 Compare November 11, 2026 08:57
@Batuhan1999 Batuhan1999 changed the title fix(web): queued runs, git actions and relationships no longer re-render on every streamed token fix(web): queued runs, relationships and changed files no longer re-render on every streamed token Nov 11, 2026
@Batuhan1999

Copy link
Copy Markdown
Contributor Author

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. check:changed passes. Head: c29c437.

…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]>
@Batuhan1999
Batuhan1999 force-pushed the fix/quiet-projection-rerenders branch from c29c437 to 187ca27 Compare November 12, 2026 08:49
@juliusmarminge juliusmarminge added the takeover:in-progress A takeover lead is working on this PR label Nov 14, 2026
juliusmarminge and others added 2 commits November 14, 2026 08:19
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]>
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Nov 14, 2026
…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]>
@juliusmarminge

Copy link
Copy Markdown
Member

Evidence

On 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.

Before: 248f5b64cc After: 64e4d15b28
Result Failed: extra renders observed Passed: zero qualifying extra renders
Completed card and Lineage Before: completed card and Lineage After: completed card and Lineage
During streaming Before: render observer during streaming After: render observer during streaming
Finished render counts Before: extra renders and preserved completed card After: zero qualifying renders and preserved completed card
Full scripted interaction Before recording After recording

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:

  • Elapsed-time performance and long real-provider sessions were not measured; Replay demonstrates render behavior only.
  • Independent file-context or relationship mutations during a stream, other providers, desktop shell and mobile were not exercised.

@juliusmarminge juliusmarminge added takeover:ready Takeover done; auto-lands when gates pass and removed takeover:in-progress A takeover lead is working on this PR labels Nov 14, 2026
@juliusmarminge
juliusmarminge merged commit 4c2f04d into pingdotgg:main Nov 14, 2026
50 checks passed
@juliusmarminge juliusmarminge removed the takeover:ready Takeover done; auto-lands when gates pass label Nov 14, 2026
Pipyakas pushed a commit to Pipyakas/t3code that referenced this pull request Nov 15, 2026
…ender on every streamed token (pingdotgg#24055)

Co-authored-by: Julius Marminge <[email protected]>
(cherry picked from commit 4c2f04d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched Author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants