perf(web): a streaming thread re-renders only its own sidebar row#22730

merged1
main
perf/sidebar-rows-read-own-shell
opened3d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

The sidebar body and useSidebarProjectGroups both subscribed to every thread shell. A running thread bumps its shell's updatedAt on every update, so each update re-rendered the whole sidebar: the header, the project-scope picker, every sortable row wrapper and every shelf marker. That happened even when the streaming thread was not the one on screen.

How this PR fixes it:

  • Quiet shell list. A new quietThreadShellsAtom in client-runtime lists the existing per-thread quietThreadShellAtom values. A quiet shell stays the same while only streaming fields change (updatedAt, item counts, latest message), and the list keeps its identity with it. The web exposes it as useQuietThreadShells.
  • The sidebar reads the quiet list. The sidebar's partition and useSidebarProjectGroups now read that list. Any real change (runtime, title, pin, settle, snooze, a new run) still produces a new shell, so sections and order still update.
  • Each row reads its own live shell. SortableThreadRow and the search-result row read their thread's live shell, so a streaming thread re-renders only its own row: the relative time, the working state and so on.

One visible difference: with "sort projects by activity", the project order inside one running turn follows the turn's last real change, not every streaming tick. Turn start and turn end are real changes, so the project still moves up when the run starts.

Measured on a dev server seeded with migrate-dev-db: 9 visible rows, React Compiler build, render counter counting only fibers React rendered, median of 3 runs.

Thread A streams a reply while thread B is open, counted over 15 s:

component rendersmainthis PR
all sidebar components28627
Sidebar70
SortableThreadRow632
SidebarThreadRow (memo)62
SortableSidebarMarker420

Switching threads (4 clicks): 363 → 364 sidebar component renders, Sidebar 14 → 13, SidebarThreadRow 10 → 14. That is unchanged within noise, which is expected: a route change is a real change for the sidebar.

Tests run:

  • threadShell.test.ts, including a new test: the quiet list keeps its identity through a streaming tick and follows a rename.
  • React Compiler coverage for the sidebar and entities files (0 bailouts).
  • entities.relationshipShells.test.ts
  • Sidebar.logic.test.ts
  • tsc for apps/web and client-runtime
  • oxlint on the changed files

Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

Checks

All checks passed
Synced from GitHub

Approvability

Verdict: Approved at e1c4bdc

Macroscope's review found this PR approvable — This is a focused sidebar performance optimization that isolates streaming updates to the affected row while preserving meaningful ordering and grouping updates. Its intentional activity-ordering delay during streaming is narrow, documented, and covered by targeted tests.

No code changes detected at bb23117. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Added 1 commit

  • perf(web): a streaming thread re-renders only its own sidebar rowbb23117
Synced from GitHub

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: b053415f-ad62-4564-95c6-919b02bdb9c7
📥 Commits

Reviewing files that changed from the base of the PR and between e1c4bdc7316caa94c7973814585150a7d2cdfd70 and bb231176c2a2fc535f3dd782244882bb0bd335ee.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 2b9b97c0-71ef-4a51-a8ad-bd5f094cafd9
📥 Commits

Reviewing files that changed from the base of the PR and between e7f62ef05daedd08c320da0526b616396d3c6aae and e1c4bdc7316caa94c7973814585150a7d2cdfd70.

📒 Files selected for processing: 6
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/sidebar/SidebarThreadRow.tsx
  • apps/web/src/hooks/useSidebarProjectGroups.ts
  • apps/web/src/state/entities.ts
  • packages/client-runtime/src/state/threadShell.test.ts
  • packages/client-runtime/src/state/threadShell.ts

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

📝 Walkthrough

The change adds an aggregate quiet-thread-shell atom and a public hook. Sidebar thread lists and project groups use the quiet-shell collection. Sidebar rows resolve current thread shells by environment and thread ID, with supplied data as a fallback.

Changes: Sidebar thread-shell updates

Layer / File(s)Summary
Aggregate quiet thread shells
packages/client-runtime/src/state/threadShell.ts, apps/web/src/state/entities.ts, packages/client-runtime/src/state/threadShell.test.ts
The new atom aggregates non-null quiet thread shells and reuses the prior array when its elements are unchanged. The public hook exposes the collection. Tests cover streaming updates and title changes.
Sidebar thread-shell consumers
apps/web/src/components/Sidebar.tsx, apps/web/src/hooks/useSidebarProjectGroups.ts, apps/web/src/components/sidebar/SidebarThreadRow.tsx
The sidebar thread list and project groups use quiet thread shells. Sortable and search-result rows resolve the current scoped shell, with supplied thread data as a fallback.

Priority: ⬇️ Low

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

Change: Refactor

Suggested reviewers: maria-rcks

Merge Risk: ⚪ Minimal · up to e1c4b

The sidebar subscription change has no established user-visible regression and is mergeable after normal checks.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the primary performance change: streaming updates re-render only the affected sidebar row.
Description checkPassedThe description clearly explains the problem, implementation, behavior change, performance measurements, and verification steps. It does not include an explicit Scope and approval section or UI eviden…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description clearly explains the problem, implementation, behavior change, performance measurements, and verification steps. It does not include an explicit Scope and approval section or UI evidence, but the core description is mostly complete.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.