You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Composer keystroke cost scales with the mounted timeline size #1304
Found while profiling the mobile web frontend against a seeded database (pnpm seed:perf, #1300). Follow-up to the tap-freeze work in #1269 / #1285: this looks like a remaining re-render amplifier.
Numbers
With a 2,633-event thread fully loaded (~7.7k DOM nodes mounted), typing 67 characters at a 25 ms cadence took 2,966 ms wall against a 1,675 ms schedule — ~19 ms of main-thread overhead per keystroke, plus a 94 ms long task mid-sentence (desktop CPU, dev build). At phone speeds this approaches per-keystroke jank, and it compounds with the timeline growth in the timeline-virtualization issue.
Repro
pnpm seed:perf on a dev instance.
Open a large seeded thread and scroll far up to mount history.
Type a long sentence into the composer; watch main-thread work per input event in the performance panel.
Suggested approach
Profile which ancestors re-render per keystroke; isolate composer draft state from the timeline tree (memo boundary or external store subscription) so typing re-renders only the composer. Timeline virtualization would also shrink the amplified tree.
Root cause (short): ThreadDetailViewInternal subscribes to the composer draft store via usePromptDraftStorage (only to get the stable addQuote/storageKey), so every keystroke re-renders the whole 3k-line thread view; each render rebuilds threadOpenContext, which (identity-keyed memo in useLocalOpenTargets) churns openPathInFileTarget -> getLocalFileContextMenuItems -> the MarkdownLocalFileContextMenuContext.Provider value wrapping the timeline, forcing React to scan the entire mounted timeline subtree (1.5k fibers small, 17.9k fibers big) for consumers on every keystroke.
Proposed fix: Two-part diff (/tmp/bb-reports/issues/1304/repro/fix.diff, +54/-8, typecheck + 439 app tests pass): (A) extend getPromptDraftAccessor with storageKey + addQuote and make ThreadDetailView use useMemo(() => getPromptDraftAccessor(scope)) instead of usePromptDraftStorage, so the thread view no longer subscribes to per-keystroke draft writes; (B) in useLocalOpenTargets memoize openContext structurally on (kind, hostId, serverOrigin) instead of object identity so callers that rebuild an equal context per render get stable callbacks and the timeline-wide MarkdownLocalFileContextMenuContext value sto…
Caveats: Timing numbers are from a dev build (StrictMode, jsxDEV) on a shared machine, so absolute ms/key vary +-5 ms between runs; the deterministic counters (render counts, changed-provider subtree sizes) and React's propagateParentContextChanges self time are the reliable signals. The unit repro test pins the two mechanisms at hook level (draft-store subscription; identity-keyed openContext memo) rather…
The report has copy-paste repro steps, screenshots where the bug is visual, code permalinks at 16ceb3a54, and repro artifacts. It was independently re-run by a second agent (see its Verification section).
Activity
SawyerHood commentedon Aug 18, 2026
Reproduction & root-cause report: https://get-bb.github.io/reports/issues/1304.html
The report has copy-paste repro steps, screenshots where the bug is visual, code permalinks at
16ceb3a54, and repro artifacts. It was independently re-run by a second agent (see its Verification section).Avoid timeline rerenders while typing in prompt boxes (get-bb#1799)