Context
Operator question at the close of the #13018 ship night: "vertical buffering, like inside src/grid" — for the streaming markdown component as the harness transcript surface. A transcript is hours of streamed markdown; the component (PR #13034, merged) currently keeps every rendered block in the DOM for the component's lifetime. Endurance (Epic #13012 pillar 1) degrades by DOM bloat: style/layout/paint cost grows with session age even when most blocks sit far off-screen.
Release classification: post-release (harness product line).
The Problem
Long transcripts need the grid's discipline — render what the viewport can see (plus buffer), not everything ever streamed. But the grid's windowing rides fixed rowHeight math (src/grid/Body.mjs: mountedRows + bufferRowRange_ + scrollTop ÷ rowHeight); markdown blocks are variable-height, the classically hard virtualization case — because generic lists must re-measure when content changes.
The Architectural Reality
The transcript case deletes the hard part: settled blocks are immutable by parser contract (src/component/markdown/Parser.mjs — content, ids, and therefore height never change once a block settles; only the open tail mutates, and during streaming the tail is pinned visible). Consequences:
- Measure each block ONCE at settle-time; heights prefix-sum into the parser's block ledger (already the ordered index). Viewport→block-range = binary search over static data.
- Eviction/re-entry rides the legal re-birth lifecycle: remove→insert of the same id is exactly the cross-batch pattern
DeltaCoherenceRegistry models as legitimate — and the parser memo returns reference-identical subtrees on re-entry, so re-mounting a scrolled-back block is nearly free.
- Browser-native precedent already in-tree: the docs component's
resources/scss/src/component/Markdown.scss:37 uses content-visibility: auto (+ allow-discrete transitions).
- The streaming tail block must always stay resident (auto-scroll pins it); the settled prefix is the only evictable region — the parser's settled/open split IS the eviction boundary.
The Fix (two phases, independently shippable)
Phase 1 — render virtualization (cheap, near-term): content-visibility: auto + contain-intrinsic-size (estimated, corrected post-measure) on every block root via the component's block cls. DOM keeps all nodes; the browser skips style/layout/paint off-screen. Zero delta traffic, zero id-lifecycle changes. Kills layout-cost scaling; DOM memory still grows.
Phase 2 — true DOM windowing (grid-class, for extreme sessions): mountedBlocks window (the mountedRows analog) with a block buffer range; top/bottom spacers sized from the ledger's height prefix-sums; settled blocks outside the window evict (removeNode), re-enter on scroll-back (insertNode of the memoized reference-identical subtree). Tail always mounted. Scroll math via binary search on the prefix-sum array; heights witnessed once at settle (post-mount measurement round-trip).
Bonus contract: Phase 2's eviction/re-entry churn is exactly the producer class the coherence registry polices — the virtualized transcript runs become another zero-findings falsification corpus (extend MarkdownStreamingCoherenceNL.spec.mjs with a scroll battery).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| Markdown block root cls (EXISTING, extended) |
PR #13034 component |
Phase 1: content-visibility + intrinsic-size styling |
styling absent = current behavior |
SCSS + class JSDoc |
Markdown.scss:37 precedent |
| Parser block ledger (EXISTING, extended) |
Parser.mjs |
Phase 2: per-block STRUCTURAL units (content lines / list items / table rows+1) + blockMeta accessor — estimates, never DOM measurement |
absent units = 1 |
module JSDoc |
settled-immutability contract in shipped JSDoc |
Neo.component.markdown.Component (EXISTING, extended) |
PR #13034 |
Phase 2: PAGE-QUANTIZED mountedBlocks window (bufferPages, unitHeights estimate map, linear scan) + stable-id spacers + follow-mode state machine over real scroll geometry (tail pin; direction-delta exit/re-enter) |
virtualize: false = render-all |
class JSDoc |
grid/Body.mjs mountedRows/bufferRowRange precedent |
Decision Record impact: none (component-layer; grid-precedent pattern transfer).
Ledger reconciliation (2026-06-13, per the PR #13051 review cycle): the original Phase-2 rows prescribed measured-heights + prefix-sum + binary search; the operator's in-session calibration ("does not have to be pixel-perfect... even if we mount one more page — in marathons with 100s of pages a clear win") collapsed that machinery before implementation. Shipped reality: structural-unit ESTIMATES (no measurement round-trips, no post-mount height witnessing), page-quantized windows (in-page scrolling delta-free), linear scan (microseconds at transcript scale). The AC "no visible jumps from estimate-vs-measured drift" resolves through the over-mounted buffer + browser scroll clamping rather than a correction strategy. Rows above updated to shipped truth; the measured-heights path remains available as a future refinement if extreme-session evidence ever demands pixel-grade spacers.
Acceptance Criteria
Out of Scope
- Horizontal virtualization (code fences scroll natively).
- The grid itself (pattern source, untouched).
- Transcript APP surface composition (#13012 staging consumes this).
Avoided Traps
- Generic virtual-list re-measurement machinery — unnecessary here; settled-immutability is the load-bearing simplification, do not import a measurement-invalidation framework the contract makes dead code.
- Evicting the open tail — breaks streaming + the settled/open invariant; the tail is structurally resident.
- Phase-2-first — Phase 1 is one SCSS+config slice with most of the endurance win; windowing complexity must earn itself on measured extreme-session evidence.
Related
Parent: Epic #13012 (markdown lane follow-up). Foundation: #13018 / PR #13034 (merged). Pattern source: src/grid/Body.mjs. Instruments: #12986 family. Operator prompt: the ship-night buffering question (2026-06-13).
Live latest-open sweep: checked latest 20 open issues at 2026-06-13T00:34Z; no equivalent found. A2A in-flight sweep: clean.
Origin Session ID: e7e5274c-6486-45ec-9c5d-0933ca3f123a
Retrieval Hint: "markdown transcript virtualization settled block windowing content-visibility mountedBlocks prefix-sum heights"
Context
Operator question at the close of the
#13018ship night: "vertical buffering, like inside src/grid" — for the streaming markdown component as the harness transcript surface. A transcript is hours of streamed markdown; the component (PR#13034, merged) currently keeps every rendered block in the DOM for the component's lifetime. Endurance (Epic #13012 pillar 1) degrades by DOM bloat: style/layout/paint cost grows with session age even when most blocks sit far off-screen.Release classification:post-release (harness product line).The Problem
Long transcripts need the grid's discipline — render what the viewport can see (plus buffer), not everything ever streamed. But the grid's windowing rides fixed
rowHeightmath (src/grid/Body.mjs:mountedRows+bufferRowRange_+scrollTop ÷ rowHeight); markdown blocks are variable-height, the classically hard virtualization case — because generic lists must re-measure when content changes.The Architectural Reality
The transcript case deletes the hard part: settled blocks are immutable by parser contract (
src/component/markdown/Parser.mjs— content, ids, and therefore height never change once a block settles; only the open tail mutates, and during streaming the tail is pinned visible). Consequences:DeltaCoherenceRegistrymodels as legitimate — and the parser memo returns reference-identical subtrees on re-entry, so re-mounting a scrolled-back block is nearly free.resources/scss/src/component/Markdown.scss:37usescontent-visibility: auto(+allow-discretetransitions).The Fix (two phases, independently shippable)
Phase 1 — render virtualization (cheap, near-term):
content-visibility: auto+contain-intrinsic-size(estimated, corrected post-measure) on every block root via the component's block cls. DOM keeps all nodes; the browser skips style/layout/paint off-screen. Zero delta traffic, zero id-lifecycle changes. Kills layout-cost scaling; DOM memory still grows.Phase 2 — true DOM windowing (grid-class, for extreme sessions):
mountedBlockswindow (themountedRowsanalog) with a block buffer range; top/bottom spacers sized from the ledger's height prefix-sums; settled blocks outside the window evict (removeNode), re-enter on scroll-back (insertNode of the memoized reference-identical subtree). Tail always mounted. Scroll math via binary search on the prefix-sum array; heights witnessed once at settle (post-mount measurement round-trip).Bonus contract: Phase 2's eviction/re-entry churn is exactly the producer class the coherence registry polices — the virtualized transcript runs become another zero-findings falsification corpus (extend
MarkdownStreamingCoherenceNL.spec.mjswith a scroll battery).Contract Ledger Matrix
#13034componentMarkdown.scss:37precedentParser.mjsunits(content lines / list items / table rows+1) +blockMetaaccessor — estimates, never DOM measurementNeo.component.markdown.Component(EXISTING, extended)#13034mountedBlockswindow (bufferPages,unitHeightsestimate map, linear scan) + stable-id spacers + follow-mode state machine over real scroll geometry (tail pin; direction-delta exit/re-enter)virtualize: false= render-allgrid/Body.mjsmountedRows/bufferRowRange precedentDecision Record impact:none (component-layer; grid-precedent pattern transfer).Acceptance Criteria
useDeltaCoherenceRegistry: trueproduces zero findings (e2e extension).Out of Scope
Avoided Traps
Related
Parent: Epic
#13012(markdown lane follow-up). Foundation:#13018/ PR#13034(merged). Pattern source:src/grid/Body.mjs. Instruments:#12986family. Operator prompt: the ship-night buffering question (2026-06-13).Live latest-open sweep: checked latest 20 open issues at 2026-06-13T00:34Z; no equivalent found. A2A in-flight sweep: clean.
Origin Session ID: e7e5274c-6486-45ec-9c5d-0933ca3f123a
Retrieval Hint: "markdown transcript virtualization settled block windowing content-visibility mountedBlocks prefix-sum heights"