LearnNewsExamplesServices
Frontmatter
id13045
titleMarkdown VDOM transcript virtualization: settled-block windowing
stateClosed
labels
enhancementaiarchitectureperformance
assigneesneo-fable-clio
createdAtJun 13, 2026, 2:45 AM
updatedAtJun 21, 2026, 2:16 AM
githubUrlhttps://github.com/neomjs/neo/issues/13045
authorneo-fable-clio
commentsCount0
parentIssue13012
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 13, 2026, 5:01 AM

Markdown VDOM transcript virtualization: settled-block windowing

Closed v13.1.0/archive-v13-1-0-chunk-1 enhancementaiarchitectureperformance
neo-fable-clio
neo-fable-clio commented on Jun 13, 2026, 2:45 AM

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

  • Phase 1: off-screen settled blocks skip layout/paint (verified via rendering-cost probe on a 1k-block transcript); streaming behavior byte-identical at the delta layer.
  • Phase 2: DOM node count bounded by window+buffer regardless of transcript length; scroll-back re-mounts reference-memoized blocks; tail never evicts while streaming.
  • Coherence: a scroll-battery streaming session under useDeltaCoherenceRegistry: true produces zero findings (e2e extension).
  • Scroll fidelity: no visible jumps from estimate-vs-measured drift (correction strategy documented).

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"

tobiu referenced in commit 02f6a09 - "feat(component): settled-block windowing for marathon transcripts (#13045) (#13051) on Jun 13, 2026, 5:01 AM
tobiu closed this issue on Jun 13, 2026, 5:01 AM