LearnNewsExamplesServices
Frontmatter
id19663
titleGrid: a hidden pre-render measures rows ahead of mounting
stateClosed
labels
enhancementaigrid
assigneesneo-gpt
createdAtOct 12, 2026, 4:08 PM
updatedAtOct 13, 2026, 2:49 AM
githubUrlhttps://github.com/neomjs/neo/issues/19663
authorneo-opus-ada
commentsCount2
parentIssue19658
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 19662 Grid: content-sized cells measure their wrapped text in place
blocking[x] 19666 Grid: a 10k wrapped-text example proves content-sized rows end to end
closedAtOct 13, 2026, 2:49 AM
milestonev13.3

Grid: a hidden pre-render measures rows ahead of mounting

Closed Backlog/active-chunk-8 enhancementaigrid
neo-opus-ada
neo-opus-ada commented on Oct 12, 2026, 4:08 PM

Context

This is leaf L3 of epic #19658, blocked by #19662 (in-place measurement). It measures rows before they mount, and height-driving columns that are scrolled out of view, so heights are known when rows appear.

The Problem

In-place measurement only sees mounted rows and columns. Two cases go unmeasured:

  • rows entering at wheel speed (41–71 per second on bigData, D#19645 probe 6);
  • a height-driving column that is horizontally unmounted.

The estimate for unseen rows needs refining as measurements arrive.

The Architectural Reality

  • The precedent: MagicMoveText.measureChars renders a hidden measure element, waits for the VDom update, then batch-reads rects.
  • The extents: a 100k-row PoC stays within native element-extent limits (about 4.8M px at mixed heights). Scaled scrolling is out of scope.
  • The costs: a hidden pre-render costs about 1 ms per 100 cells and about 4 ms per 300 in three engines (probe 4). The existing scroll-to-fresh-rows pipeline takes 50–80 ms (probe 6).

The Fix

  • The hidden subtree: a visibility: hidden subtree inside the grid's own DOM and style scope, never display: none or detached, since those have no layout box. It renders the declared text cells of not-yet-mounted rows, plus off-screen height-driving columns, at their actual column widths, using L2's cell CSS. It is read through the same batched getLayoutRect call and the same generation fence.
  • Measure-ahead: measure the rows that lie about one pipeline-latency of scroll beyond the overscan window. Without input nothing is measured: there is no idle sweep over the store.
  • Estimates: unseen rows use the mean of measured heights, rounded to a whole pixel, never in place of a known height. The total extent is called estimated while unseen rows remain.
  • Font arrival: the main thread's document.fonts events bump the font generation, which invalidates and re-measures.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback / edge case Docs Evidence
Hidden measurement subtree #19658 Real layout box, grid style scope, actual widths, fenced Font not yet loaded: re-measure on arrival JSDoc Hidden vs displayed parity spec
Measure-ahead scheduling D#19645 probe 6 rule About one pipeline-latency beyond the overscan Fast thumb drag: mount at the estimate, correct later (L4) JSDoc Integrated measurement in L6
Unseen-row estimate Operator's running-average idea Mean of measured heights for unseen rows only — JSDoc Unit

Acceptance Criteria

  • A hidden-measured cell and the same cell measured displayed give equal heights at fractional widths; this is a spec.
  • A height-driving column that is horizontally unmounted still drives its row's height; this is a spec.
  • Font arrival invalidates and re-measures, and no height measured with the fallback font survives; this is a spec.
  • Ordinary wheel scrolling brings every row into the viewport at its measured height. The PR reports the estimated-first shares separately for mounted buffer rows and visible rows, and records abrupt-jump correction on its own.
  • Unseen rows use the measured mean, and known heights are never replaced by it.
  • Without input, the grid neither measures nor re-renders; this is a spec.

Out of Scope

Scroll correction with pinning (L4), AnimateRows (L5), the 100k journey and integrated throughput acceptance (L6).

Avoided Traps

  • display: none or detached nodes, which measure 0.
  • A general measurement service in v1.
  • Measuring before fonts are ready without invalidating afterwards.
  • An idle sweep over the store: on 100k rows it re-rendered 6–7 times per second without input and kept moving the extent.

Related

#19658 · #19662 (blocked by) · D#19645

Origin Session ID: daac81df-5205-405e-a3af-aec56e5cca45

tobiu referenced in commit 72dd307 - "feat(grid): premeasure content-sized rows in native layout (#19663) (#19688) on Oct 13, 2026, 2:49 AM
tobiu closed this issue on Oct 13, 2026, 2:49 AM
tobiu referenced in commit c8c7ccc - "feat(grid): a 10k wrapped-text example proves content-sized rows end to end (#19666) (#19704) on Oct 13, 2026, 4:53 AM