Skip to content

fix(renderer-dom): never estimate a row height we have already measured - #342

Merged
blove merged 7 commits into
mainfrom
blove/hero-grid-jitter-chrome-01430d
Aug 13, 2026
Merged

blove merged 7 commits into
mainfrom
blove/hero-grid-jitter-chrome-01430d

Conversation

@blove

@blove blove commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The homepage hero grid jittered in Chrome while streaming. The heights it jittered to were never measured — they were produced by estimateDomRowHeight and applied on top of rows that had already been measured.

Measured in Chrome over 8 seconds before the fix: the DOM measurement path produced 63/89/68, while the row nodes received 63, 66, 114, 89, 68. 66 and 114 were published 71 times and measured zero times, and both reconcile exactly with the estimator's constants (1×24+42 and 3×24+42).

Mechanism

While a row-model replacement is in flight, controller.measure only stages the measurement. Streaming issues an update for every row every tick, which discards the staged entry — correctly, since the content changed — after which the estimate gate treats a row measured dozens of times as one never seen. The estimator overshoots the real DOM in both of its terms, so the correction a commit later is always downward and always visible.

The fix

Retain the last measured height per data-row identity and use it as the estimate gate's fallback, so an estimate is only ever used for a row that has never been measured. hasMeasurement still goes false; the scheduler, the sliced catch-up, and the staging discard are untouched. The gate only iterates the planned window, so a retained height is only ever a one-frame placeholder for a row about to be re-measured.

Retention is scoped to data rows (the gate itself is), bounded by a new maxRetainedRowHeights option with least-recently-measured eviction, and cleared on dispose.

Result

Before After
Estimator publications (66/114) 71 0
Height transitions in 8s 154 19
Transitions after t=3000ms 84 6 — all monotonic content growth

renderer-dom 52/52, @pretable/core 7/7, @pretable/react 827/827, website smoke 12/12 including the existing "no row drift" guard. Every new line was mutation-checked: each was removed individually and shown to turn a test red.

Not fixed here, deliberately

A row entering the window for the first time still jumps (66 → 63), because it has no prior measurement to fall back on. That needs the estimator's inputs to be real — its line height, chrome and character width are constants calibrated against the bench app rather than the active theme, and it is blind to render and format besides. Both are recorded as follow-ups in the design doc, along with a false comment at packages/react/src/pretable-model.ts:405.

Group rows also still revert on update; the estimate gate is data-only, so extending retention there is a separate product call.

Design: docs/superpowers/specs/2026-08-12-row-height-estimate-stomping-design.md

🤖 Generated with Claude Code

blove and others added 7 commits August 12, 2026 14:04
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er-dom

The first design's hypothesis (sub-pixel Range rounding on the tick-flash
underline) was falsified at its verification gate. Measurement showed the
spiked heights are never measured at all: they are estimateDomRowHeight's
output, re-applied to rows already measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s own bound

Retain only data rows, since the estimate gate that consumes retained heights
is itself gated on data rows and group entries could never be looked up.

Give retention its own `maxRetainedRowHeights` option rather than borrowing
`maxRetainedMeasurements`, which bounds the height index's tombstones and does
not bound live measurements at all. Sharing it meant a legitimate `0` silently
disabled the fix.

Pin the previously uncovered lines: the LRU refresh, the remove-path eviction,
the disposal clear, and the data-row guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pretable Ready Ready Preview Aug 12, 2026 11:48pm

@github-actions

Copy link
Copy Markdown
Contributor

Vercel preview ready

Preview: https://pretable-az3sw4s2p-cacheplane.vercel.app
Commit: 8bea1c7458f8f3b6cddbc4d9ddef94efaf192502

Updated automatically by the deploy-preview job.

@blove
blove merged commit 77febe7 into main Aug 13, 2026
15 checks passed
@blove
blove deleted the blove/hero-grid-jitter-chrome-01430d branch August 13, 2026 01:11
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