Files
npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6andTyler Longwell 2bd2076ebb fix(desktop): rekey upscroll momentum gate onto rendered scroll (w4a-gate-1)
The mid-history correction's momentum-skip gate keyed on the raw
`scrollTop` delta since baseline. On WebKit, momentum is coalesced into
`scrollTop` on the compositor's own async clock, so a frame that PAINTED
still can read a large raw delta and wrongly skip a genuine above-anchor
reflow — the reversal survivor bin (the 204px-class lurch of #1662).

Rekey the gate onto the RENDERED scroll — the painted wheel motion this
frame — `renderedScroll = aboveShift - ΔtopOffset`: the anchor's painted
on-screen move with the reflow's own push removed, both terms read from
the painted DOM (getBoundingClientRect), not the possibly-ahead
`scrollTop`. A rendered-still frame now reads ~0 and the reflow is
corrected instead of dropped.

Because the gate now needs `aboveShift` to decide, the band walk runs
before the gate. To keep "under-correcting is invisible; the next quiet
frame catches up," `sumAboveAnchorShift` no longer refreshes the height
cache inline — it STAGES the refresh and returns `{ aboveShift, commit }`.
The caller applies `commit()` only past the gate, so a momentum skip
leaves the cache for the next frame; a kept correction refreshes it as
one indivisible pass (the "second observer no-ops" invariant is intact).

Arbiter contract, fresh w4a-gate-1 build, both engines:
- raf-correction HARD gate: Chromium 0 reversals, rAF fires 0 / RO 21
  (RO sole writer); WebKit 2 reversals max 14.5px (<=34 quantum floor),
  rAF fires 24 / RO no-op (residual 0.0). Ratified invariants hold.
- slow-scroll: 0 reversals both engines (felt low-velocity regime clean).
- fast-classify DECOMPOSITION self-test (WebKit): pure-reflow attempts
  median |signedShift|=18.5 but median |renderedScroll|=0.0 — the reflow
  does NOT leak into the gated quantity, and 19/28 fire. Pure-scroll
  |renderedScroll| runs up to 28. The decomposition is proven, not
  asserted. Guarded WebKit-only: on Chromium the rAF is the passive loser
  observer (RO corrects first), so renderedScroll there tracks signedShift
  by construction — characterized, not asserted.

KNOWN RESIDUAL (surfaced, not hidden): 2 WebKit survivors remain, but
they are a DIFFERENT class — signedShift=0.0 (the band walk sees no
above-anchor reflow) while the row still lurches 14.5px. The momentum-gate
survivors the rekey targeted are gone; these are cross-check/null-target
skips where the reflow is invisible to the band walk. Separate diagnosis.

Diagnostic-only `renderedScroll` is threaded through the shared result
type + emitted on the rAF probe so the fixture can prove the
decomposition; stripped/gated before merge per the file-size exit
condition. Raises the useAnchoredScroll.ts size exception 1180->1210 to
cover the arm (biome reflows the 4-prop returns); flagged in the override
comment for Eva as heavy-for-a-diagnostic.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-09 01:11:30 -04:00
..