LearnNewsExamplesServices
Frontmatter
id19664
titleGrid: height corrections keep the visible row and the pinning origin stable
stateClosed
labels
enhancementaigrid
assigneesneo-opus-ada
createdAtOct 12, 2026, 4:08 PM
updatedAtOct 12, 2026, 10:45 PM
githubUrlhttps://github.com/neomjs/neo/issues/19664
authorneo-opus-ada
commentsCount1
parentIssue19658
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 19661 Grid: an opt-in row-geometry model drives offsets, extents and navigation
blocking[x] 19666 Grid: a 10k wrapped-text example proves content-sized rows end to end
closedAtOct 12, 2026, 10:44 PM
milestonev13.3

Grid: height corrections keep the visible row and the pinning origin stable

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

Context

This is leaf L4 of epic #19658, blocked by #19661 (the geometry model). When measured heights correct rows above the viewport, the visible record must not jump. Scroll corrections must also agree with GridRowScrollPinning, which reconciles the actual scroll position with the worker's acknowledged render origin.

The Problem

A height correction above the anchor row moves every following offset. If scrollTop is not adjusted with the transforms, the view jumps. If it is adjusted outside the pinning addon's bookkeeping, pinning can double-displace rows or stay stuck after a thumb release.

The Architectural Reality

  • The pinning addon: GridRowScrollPinning.applyPinning computes the actual scrollTop minus the worker's last one (L117). onDeltaUpdate adopts the view's metadata (L159). Pinning can stay active after a thumb release until the worker catches up.
  • The pipeline: existing scroll-to-fresh-rows takes 50–80 ms (D#19645 probe 6), which pinning already covers.

The Fix

  • The anchor rule: a correction above the anchor row adjusts the scroll offset in the same update as the transforms, through the pinning addon's acknowledged origin, never as an independent scrollTop write.
  • The mechanism (sharpened at intake, comment 6128589243; steps 2 and 3 as built in #19674):
    1. The View keeps its anchor: the row at scrollTop and the distance into it. After heights change it computes the anchor's new offset.
    2. A non-zero difference travels as scrollCorrection in the View's update meta (getVdomUpdateMeta), next to scrollTop, in the batch that renders the moved rows. When an ancestor's payload absorbs the View's, Neo.mixin.VdomLifecycle sends the absorbed meta along with that batch.
    3. The addon reads the target, the offset before the deltas plus the correction, in the DeltaUpdates update event. It applies the target in the updated event, once the deltas have resized the bodies, then pins: all in the same task as the deltas. The target is computed from the offset before the deltas, so scrolling the user did while the worker computed survives; and since it is applied as an absolute value, the clamp of a shrunk extent does not count twice.
    4. A correction below the anchor has a zero difference and never moves the view.
    5. Content-sized mode needs corrections even with useRowScrollPinning: false, so it registers the addon to apply corrections without ever engaging the pin.
  • Thumb drags: extent changes are held during an active thumb drag and settle at release or acknowledgement. The final row stays reachable before and after settlement. A store replacement drops the held heights.
  • The race spec (Sophie, D#19645 18878897): during a thumb drag, deliver a height correction above the anchor and an older render acknowledgement in opposite orders, then release. Transforms, both extents and the acknowledged origin must converge without double displacement or stuck pinning.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback / edge case Docs Evidence
Anchor correction in content-sized mode #19658 / D#19645 Keeps the visible record's pixel position Corrections below the anchor never move it JSDoc Spec
Interaction with GridRowScrollPinning Pinning L117/L159 One origin; no double displacement Thumb drag: hold, then settle; a store replacement drops held heights JSDoc Race spec
scrollCorrection in the View's update meta This leaf (intake; transport as built) Carried by the batch that renders the moved rows, also when an ancestor absorbs the View's payload; the addon reads the target in update and applies it in updated, after the deltas, then pins Pinning off: the addon is still registered to apply it, never pins JSDoc Unit + e2e

Acceptance Criteria

  • A height correction above the visible record leaves that record's on-screen position unchanged; this is a spec.
  • Sophie's ordering race passes in both delivery orders, with no double displacement and no stuck pinning; this is a spec.
  • During a thumb drag the extent does not change, and after release it settles while the final row stays reachable.
  • Reverse wheel scrolling over freshly measured rows shows no visible jump at the viewport, checked in an e2e.

Out of Scope

Measurement itself (L2, L3), AnimateRows (L5), and the 100k journey (L6).

Avoided Traps

  • A scrollTop write outside the pinning bookkeeping.
  • Correcting rows below the anchor by moving the viewport.
  • Using browser scroll anchoring here: the grid already sets overflow-anchor: none, and the engine owns anchoring.

Related

#19658 · #19661 (blocked by) · D#19645 · #19515 (selection projections; announce files before branching)

unowned-rationale: open for any peer; the epic steward (Ada) picks it up otherwise.

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

tobiu referenced in commit 13e077f - "feat(grid): content-sized height corrections keep the visible row still (#19664) (#19674) on Oct 12, 2026, 10:44 PM
tobiu closed this issue on Oct 12, 2026, 10:44 PM