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):
- The View keeps its anchor: the row at
scrollTop and the distance into it. After heights change it computes the anchor's new offset.
- 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.
- 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.
- A correction below the anchor has a zero difference and never moves the view.
- 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
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
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
scrollTopis 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
GridRowScrollPinning.applyPinningcomputes the actualscrollTopminus the worker's last one (L117).onDeltaUpdateadopts the view's metadata (L159). Pinning can stay active after a thumb release until the worker catches up.The Fix
scrollTopwrite.scrollTopand the distance into it. After heights change it computes the anchor's new offset.scrollCorrectionin the View's update meta (getVdomUpdateMeta), next toscrollTop, in the batch that renders the moved rows. When an ancestor's payload absorbs the View's,Neo.mixin.VdomLifecyclesends the absorbed meta along with that batch.updateevent. It applies the target in theupdatedevent, 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.useRowScrollPinning: false, so it registers the addon to apply corrections without ever engaging the pin.Contract Ledger
GridRowScrollPinningscrollCorrectionin the View's update metaupdateand applies it inupdated, after the deltas, then pinsAcceptance Criteria
Out of Scope
Measurement itself (L2, L3),
AnimateRows(L5), and the 100k journey (L6).Avoided Traps
scrollTopwrite outside the pinning bookkeeping.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