Skip to content

Add experiment-gated timeline row windowing - #2011

Merged
ymichael merged 1 commit into
mainfrom
bb/check-for-dom-virtualization-thr_s7s2cc7j8a
Aug 20, 2026
Merged

Add experiment-gated timeline row windowing#2011
ymichael merged 1 commit into
mainfrom
bb/check-for-dom-virtualization-thr_s7s2cc7j8a

Conversation

@ymichael

@ymichael ymichael commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What was wrong

Long timelines kept every rich top-level row mounted after older history loaded, and expanded delegation/turn details likewise kept every nested row mounted. A long-lived client that streams across multiple threads therefore accumulated DOM, rich markdown trees, observers, and row-local interaction state even though almost all content was outside the viewport.

The first prototype fixed the resting DOM size with a bespoke 826-line virtualizer, but it added too much custom geometry and gesture policy to land comfortably. Its cold full-history traversal also mounted each rich row once, producing 4.96 s round trips and 66.3 ms p95 frames.

What changed

Add the default-off timelineWindowing experiment using the existing @tanstack/react-virtual dependency for range calculation, variable-height measurement, scroll correction, and momentum-safe updates. A thin timeline adapter retains only product policy:

  • Window top-level lists at 60 rows on desktop or 40 on compact viewports, and nested/bundle lists at 20 rows.
  • Keep eight realized rows of overscan and use measured stable-row heights for distant placeholders.
  • Recursively window expanded turn and delegation details against their own capped scroll root; collapsing a group unmounts its detail subtree immediately.
  • Keep last-row, unread-divider, searched-row, saved-scroll-anchor, focused, and clicked rows available when they are outside the visible range.
  • Bound interaction pins at 24 and retained exact measurements at 2,000 for long-lived streaming sessions.
  • Render cheap measured placeholders during high-velocity traversal, then realize the settled viewport.
  • Initialize nested virtualizers from the shared scroll element's current offset so expanding or collapsing a nested list cannot reset the parent scroll position.

The experiment-off path renders the existing full list. A small React.lazy boundary keeps the 346-line TanStack adapter out of the route closure until windowing is enabled and a list crosses its threshold; the disabled app does not request that module.

The experiment is exposed in Settings, bb settings experiment timelineWindowing <true|false>, system defaults, docs, templates, and the built-in CLI skill. This does not alter the server/host-daemon wire contract, so HOST_DAEMON_PROTOCOL_VERSION is unchanged.

This PR supersedes #1991 with one experiment that covers top-level and recursive nested rows.

Complexity reduction

Scope Earlier prototype This PR
Gross additions 1,856 1,370
Net additions 1,790 1,244
Custom windowing implementation 826 lines 428 lines (adapter + lazy boundary)
Runtime diff +783 / -122
Tests and docs diff +587 / -4

The implementation is about 26% smaller in gross additions and the custom windowing layer is about 48% smaller. TanStack owns the difficult virtualizer mechanics; repository code owns the timeline-specific behavior and experiment integration.

How you verified

Final focused validation after rebasing onto current origin/main:

@bb/app timeline windowing + scroll preservation: 3 files, 25 tests passed
@bb/app typecheck: passed
@bb/app lint: passed with 0 errors (pre-existing warnings remain)
@bb/app production build: passed
bundle budget: boot 425.2 / 428.0 KiB Brotli; route closure 654.0 / 655.0 KiB Brotli

A full app run before the lazy-boundary-only follow-up passed 3,102 tests with three skipped. The replacement GitHub Actions run validates the exact final revision.

Dev Browser QA used a production-derived SQLite copy containing 620,222 events across 504 threads. The benchmark thread contained 38,946 underlying events, 577 projected top-level rows, and about 134.8k px of history.

Matched full-history benchmark:

Metric Control Windowed Change
Elements at rest 16,450 1,420 -91.4%
DOM nodes at rest 26,380 1,998 -92.4%
Used JS heap 162.9 MiB 100.3 MiB -38.4%
Embedder heap 44.0 MiB 17.4 MiB -60.5%
Style flush p50 38.7 ms 7.0 ms -81.9%
Style flush p95 42.5 ms 8.7 ms -79.5%
Full traversal 1,168 ms 1,187 ms +1.6%
Frame p95 9.9 ms 10.0 ms +0.1 ms
Frames over 32 ms 0 0 unchanged
Peak mounted top rows 577 13 -97.7%

Real-data nested QA expanded a 103-row turn. It mounted 39 rows at the header and 46 while scrolled through the middle (visible range plus the pinned final row), rather than retaining all 103. Expanding and collapsing it preserved the shared scroll position exactly at scrollTop=41900. Compact-viewport QA also crossed the 40-row threshold and windowed correctly.

The CLI toggled the experiment off and on against the same development server. With 188 loaded rows, the disabled path mounted all 188 with no virtual spacer and did not request the adapter module; the enabled path mounted 15 with a virtual spacer preserving the full scroll geometry. No timeline runtime errors were observed.

No enrolled mbp-intel host was available for a hardware-specific rerun; the connected machines were M4 and M5. The benchmark above was run on the production-derived dataset on the M4 host.

Fixes: no linked issue.

AGENT GENERATED: by GPT-5.6 Codex

@ymichael
ymichael force-pushed the bb/check-for-dom-virtualization-thr_s7s2cc7j8a branch 3 times, most recently from 418d223 to 3f75697 Compare August 20, 2026 18:53
@ymichael
ymichael force-pushed the bb/check-for-dom-virtualization-thr_s7s2cc7j8a branch from 3f75697 to 300015e Compare August 20, 2026 19:08
@ymichael
ymichael merged commit 4e5a917 into main Aug 20, 2026
13 checks passed
@ymichael
ymichael deleted the bb/check-for-dom-virtualization-thr_s7s2cc7j8a branch August 20, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant