perf(desktop): make multitab streaming sessions fast - #71780
Merged
Conversation
Keep-alive keeps every ever-active tab mounted, but each hidden tab's ChatRuntimeBoundary still subscribed to its view's $messages — so every streaming delta flush (~30x/s) re-rendered every busy tab's whole thread, and five concurrent sessions dropped the app to a crawl. Flow the pane layer's visibility down as PaneVisibleContext and gate the $messages subscription on it: a hidden tab freezes its transcript (status dots stay live through the separate status atoms) and catches up in one commit on reveal, since subscribe fires immediately with the current value.
ThreadTimeline's scroll compute read getBoundingClientRect for every user message per scroll frame; interleaved with React's streaming style writes each read forced a full reflow — the hottest self-time frame in the multitab profile (620ms over one 5-tab run). While the viewport is pinned to the bottom the active prompt is simply the last entry, so answer from data and save the layout reads for actual scrollback.
Contributor
૮ >ﻌ< ა ci reviewran on 9173f58 all good! |
N session tiles stacked as tabs, all mounted (keep-alive) and all streaming concurrently through the real publishSessionState path — the "several PR reviews at once" workload. Frame pacing + longtask metrics, no backend or credits needed; the workload that exposed both fixes above and the regression gate that keeps them fixed.
OutThisLife
force-pushed
the
bb/multitab-perf
branch
from
July 26, 2026 05:34
6859447 to
9173f58
Compare
This was referenced Jul 26, 2026
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
perf(desktop): make multitab streaming sessions fast
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With several session tabs streaming at once (the multi-PR-review workload), the renderer crawled. Profiling with a new
multitabharness scenario found two costs and this PR removes both:$messagessubscription stayed live — every delta flush re-rendered every busy tab's entire thread. The pane layer now flows its visibility down (PaneVisibleContext), and a hidden tab freezes its transcript, catching up in one commit on reveal. Status dots stay live through the separate status atoms.getBoundingClientRectself-time in one 5-tab run). While the viewport follows the bottom the active prompt is just the last entry, so the walk is skipped entirely; real scrollback keeps the precise compute.The
multitabscenario joins the CI tier ofscripts/perf/so this stays fixed. Its--codeflag streams one growing fenced code block per tab with no settle boundaries — the coding-agent worst case, where block memoization can't help and every flush re-renders the whole block.Measurements
Dev renderer, isolated instance (
perf:serve), 5 tabs each streaming into a 20-turn transcript at the real 33ms flush cadence.worst-second fps= the slowest sliding 1-second window — the number you feel.Coding workload (
--code, one growing code block per tab):Longer streams degrade further on main as the blocks grow (frames >200ms appear — the sub-10fps crawl at ~30s in); after the fix a 30s 5-tab code stream holds 59 avg / 55 worst-second fps, zero longtasks. Hidden-tab cost also scaled with tab count before (each mounted tab re-rendered per flush), which is why more tabs made it worse; after, only the visible tab pays.
Prose workload (blocks settle as they stream): frame p95 44.8→24.5ms, p99 118→30.9ms, slow frames 109→2.
Stash-toggle re-runs confirmed the regression returns without the patch in both workloads. Single-tab
stream/keystrokescenarios are unchanged.Test plan
npm run perf -- multitab --tiles 5 --codebefore/after (numbers above)npm run typecheck(all three tsconfigs)npm run test:ui— 2287 passed; one pre-existing fuzz-test timeout (markdown-blocks.test.ts) passes in isolation and is untouched by this change