Fix first
Reduce sidebar prewarming and stop performing a global event-log replay for every thread subscription.
Live Chrome profiling found a WebSocket-driven main-thread stall, while source analysis found several multiplicative paths that can turn active agent streams into continuous decoding, history scans, state rewrites, persistence, timeline derivation, and highlighting.
The bad interaction was delayed primarily by WebSocket delivery and synchronous client work, not by painting. The most serious issue is a fan-out loop: the sidebar prewarms up to ten full thread subscriptions; stale subscriptions can each replay the global event log; every event is decoded, reduced into a whole-thread object, queued for full snapshot persistence, and allowed to invalidate history-wide derivations.
One interaction trace proves a burst. Source inspection shows why bursts can become sustained during active agent sessions. Direct OS-level CPU and thermal telemetry was not recorded, so the report distinguishes measured evidence from high-confidence causal inference.
Reduce sidebar prewarming and stop performing a global event-log replay for every thread subscription.
Batch client events, incrementalize thread state and shell summaries, and move full snapshot encoding away from the hot path.
Stop reparsing and re-highlighting cumulative streaming output, split cold-start code, and lazily mount the diff worker pool.
Measurements below came from Chrome’s Performance panel against the open T3 Code Nightly production tab, plus a production build of the pinned local repository. A later malformed DevTools INP value was discarded rather than used.
The system category is not fully attributable to application JavaScript, but its timing overlaps the interaction and WebSocket processing window. The decisive observation is that paint is small relative to delivery, decode, and scripting.
Heat requires sustained energy use, not merely one slow click. The following mechanisms can repeat for the entire duration of one or more busy agent sessions.
| Mechanism | Pattern | Thermal plausibility | How to recognize it |
|---|---|---|---|
| Background thread subscriptions | Continuous while agents emit events, including threads not currently open. | Very high | CPU rises with any busy agent and remains elevated while viewing another thread. |
| Global catch-up replay | Large burst after reconnect, wake, navigation, or stale cache recovery. | Very high | CPU and network spike together after reconnect; UI becomes unresponsive. |
| Whole-thread reduction and persistence | Repeats every stream event and every 500 ms persistence window. | High | CPU scales with thread history and event frequency; IndexedDB work appears often. |
| Markdown and code highlighting | Can reprocess the cumulative answer for every streaming update. | High when enabled | CPU climbs while a long code-heavy answer streams and drops when it finishes. |
| Diff workers | Two to six workers retained for the lifetime of the chat view. | Medium | Multiple Chrome worker processes/threads stay active after diff use. |
| Cold bundle parse | Primarily startup and reload. | Short spike | CPU spikes after reload, then settles. |
| Minimap and composer work | Scroll- or typing-bound. | Localized | Load follows rapid scrolling or typing rather than agent activity. |
Chrome DevTools was open with retained Performance recordings. DevTools can increase CPU and memory use, so thermal confirmation must be repeated with DevTools closed and Chrome Task Manager or OS telemetry collecting separately. The source-level amplification paths remain valid regardless of profiler overhead.
The performance risk is multiplicative. Each stage feeds more work into the next, and several stages scale with the total history of a thread rather than with the size of the newest event.
Confidence reflects the combination of live evidence and direct source confirmation. Impact estimates should be validated with the measurement gates later in this report.
The sidebar prewarms the first ten visible threads using the same full detail hook as an opened thread. Thread state has a five-minute idle TTL, allowing previously visible subscriptions and their cached state to linger. This multiplies snapshots, live events, decode work, reducers, persistence, and downstream invalidation.
SIDEBAR_THREAD_PREWARM_LIMIT = 10; full thread prewarmer is mounted
for every selected reference.
Prewarm only active, hovered, or next-likely threads. Prefer a snapshot fetch without opening a durable stream.
At idle, one shell stream and at most one detail stream. Background threads produce no client CPU work.
Shell and thread catch-up both call
readEvents(afterSequence, Number.MAX_SAFE_INTEGER). Per-thread filtering
happens after the read. Live events arriving during catch-up are placed into unbounded
queues. Ten stale detail streams can therefore multiply the same global scan and
retain additional live data while replay is in progress.
Direct source confirmation plus a measured WebSocket/decode long task.
Add aggregate-indexed reads, a maximum replay gap, snapshot fallback, batched frames, and bounded queues.
Reconnect cost scales with events for the selected thread, not all events since its cursor.
Several ordinary thread events call a projection routine that loads all messages, plans, activities, and approvals, then rescans them to derive a handful of shell fields. Broad shell upserts can then make the client reprocess and re-sort sidebar state even when the visible summary barely changed.
Four repository list operations occur together inside
refreshThreadShellSummary.
Maintain counters and timestamps incrementally. Filter shell-irrelevant events and coalesce upserts.
Assistant streaming chunks do not issue history-wide shell reads or sidebar upserts.
Message events search and map the message array; activity events filter, append, and sort activities. The resulting thread object is published through a subscription reference and queued for complete schema encoding and IndexedDB persistence after a 500 ms debounce. Debouncing limits write frequency but not the size of each encode.
Direct reducer and persistence path inspection; consistent with main-thread scripting in the trace.
Index by ID, append monotonic events, batch updates per frame, and persist deltas or snapshots during idle time in a worker.
Per-event processing remains approximately constant as a thread grows from 100 to 10,000 activities.
Activities are already sorted by the reducer, then copied and sorted again for work-log derivation. Messages, plans, and work entries are mapped into new arrays and the combined timeline is sorted again. Additional row derivation performs further passes. DOM virtualization prevents a large mounted tree, but it cannot prevent this upstream work.
Only about seven timeline rows were mounted, while scripting remained substantial.
Maintain an incrementally ordered timeline and split subscriptions by messages, activity, session, and composer concerns.
Appending one activity does not allocate or sort arrays proportional to total thread history.
Each update reparses the cumulative message through Markdown, GFM, raw HTML, and sanitization. Streaming bypasses the highlighted-result cache, but still runs Shiki over the full growing code block. Unsupported languages also retry with the original language after obtaining a text fallback, generating repeated exceptions and console warnings.
Repeated live warnings were observed for sh and yaml;
source confirms full-code highlighting.
Render plain code during streaming, highlight once finalized, throttle Markdown updates, and negative-cache fallback languages.
CPU stays nearly flat as a streaming code block grows; no fallback exceptions appear.
The main bundle is approximately 3.425 MB minified / 1.028 MB gzip, and the preloaded textarea chunk adds about 850 KB / 265 KB gzip. The main bundle statically contains xterm, Pierre diffs, parse5, Lexical, and broad application code even when terminal, diff, file preview, and settings surfaces are closed.
Production Vite build completed with chunk-size warnings and source-map attribution.
Lazy-load terminal, diff/highlighter, settings, account, and command-palette implementations. Add gzip budgets.
Initial route ships under 500 KB gzip JavaScript, excluding code loaded only after explicit feature use.
Every chat view is wrapped in a provider configured for two to six workers and a large AST cache. Workers may be created lazily by the dependency, but once used they remain tied to the chat-view lifetime. This can contribute to multi-core load and heap retention after a diff was viewed.
Worker tracks existed in the live trace; exact active time and retained cache size were not isolated.
Mount on first diff use, lower the default pool, adapt to device class, and terminate after an idle timeout.
No diff workers exist before a diff is opened, and they disappear after the configured idle interval.
Each prompt update changes the persisted Zustand store. The partializer walks all retained drafts and clones nested contexts, annotations, and comments before JSON storage hands the final write to a debounced backend. With many retained drafts, every keystroke can do work proportional to all draft state.
Code-backed risk; a large-draft typing profile was not captured.
Debounce before partialization and serialization, persist the active draft delta, use idle work, and prune stale drafts.
Typing latency remains stable with hundreds of retained drafts and large review-comment state.
Sidebar and command palette each run an independent two-second bootstrap polling hook. The timeline minimap checks every item and mutates datasets during scroll. Large public source maps increase deployment and tooling overhead. Markdown component factories also trigger existing unstable-nested-component lint warnings.
Source confirmed; none individually dominated the sampled long task.
Centralize polling, compare before publishing, update only changed minimap ranges, and use hidden or disabled production maps.
An idle tab performs no recurring React updates or list-wide DOM writes.
Sequence the work by removing amplification before micro-optimizing render code. Each phase should ship behind measurements that allow before/after comparison on the same fixture.
This is intentionally narrow: it should reduce CPU immediately and provide cleaner evidence for the aggregate-indexed replay work that follows.
Performance fixes should be evaluated with sustained-load measurements, not only click traces. The user-reported failure mode is high CPU and heat, so thermal proxies and idle behavior matter.
performance.measure for RPC decode, event application, timeline
derivation, and cache encoding.
The timeline already uses virtualization, HTTP snapshots are gzip-friendly, resume cursors exist, and assistant streaming defaults to disabled. These are solid foundations. The recommended work should preserve those mechanisms while removing fan-out, redundant history scans, and hot-path full-state serialization.
0.0.29-nightly.20260709.763 while the bb-1
server was 0.0.29-nightly.20260701.697.
vp check passed with 29 pre-existing warnings.infra/relay.