perf(web): a streaming thread re-renders only its own sidebar row#22730
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
quietThreadShellsAtomin client-runtime lists the existing per-threadquietThreadShellAtomvalues. 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 asuseQuietThreadShells. - The sidebar reads the quiet list. The sidebar's partition and
useSidebarProjectGroupsnow 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.
SortableThreadRowand 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 renders | main | this PR |
|---|---|---|
| all sidebar components | 286 | 27 |
Sidebar | 7 | 0 |
SortableThreadRow | 63 | 2 |
SidebarThreadRow (memo) | 6 | 2 |
SortableSidebarMarker | 42 | 0 |
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.tsSidebar.logic.test.tstscfor apps/web and client-runtime- oxlint on the changed files
Claude Opus 5.5 in Claude Code.
🤖 Generated with Claude Code
Checks
- SuccessfulCodeRabbitGitHub
Review completed
- Successful
Skipped - Not affected
- Skipped
- Skipped
- Skipped
- SuccessfulRemove preview downloadGitHub
- SuccessfulCheckGitHub
- Skipped
- SkippedRustGitHub
- SkippedRelease SmokeGitHub
- SuccessfulBuild and TypecheckGitHub
- SuccessfulTestGitHub
- SuccessfulTest WebGitHub
- Skipped
- Skipped
- Skipped
- SuccessfulLabel PR sizeGitHub
- SuccessfulLabel PR 22730GitHub
- Skipped
- Skipped
- SkippedDeploy web previewGitHub
- SkippedEAS PreviewGitHub
- SuccessfulPrepare PR size configGitHub
- SuccessfulCollect PR targetsGitHub
- SuccessfulTest Server 7GitHub
- SuccessfulTest Server 5GitHub
- SuccessfulChangesGitHub
- SuccessfulTest Server 6GitHub
- SuccessfulTest Server 1GitHub
- SuccessfulTest Server 2GitHub
- SuccessfulTest Server 8GitHub
- SuccessfulTest Server 4GitHub
- SuccessfulTest Server 3GitHub
- SuccessfulLintGitHub
- SuccessfulNative fingerprint diffGitHub
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
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.tsxapps/web/src/components/sidebar/SidebarThreadRow.tsxapps/web/src/hooks/useSidebarProjectGroups.tsapps/web/src/state/entities.tspackages/client-runtime/src/state/threadShell.test.tspackages/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.tsThe 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.tsxThe 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 name Status Explanation Title check Passed The title clearly and concisely describes the primary performance change: streaming updates re-render only the affected sidebar row. Description check Passed 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 eviden… 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: 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.