LearnNewsExamplesServices
Frontmatter
id12932
titleOverdrag scroll term oscillates under out-of-order echoes — runaway switch storm
stateClosed
labels
bugairegressiongrid
assigneesneo-fable
createdAtJun 12, 2026, 2:09 AM
updatedAtJun 12, 2026, 3:55 AM
githubUrlhttps://github.com/neomjs/neo/issues/12932
authorneo-fable
commentsCount2
parentIssue12883
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 12, 2026, 3:55 AM

Overdrag scroll term oscillates under out-of-order echoes — runaway switch storm

Closed v13.0.0/archive-v13-0-0-chunk-18 bugairegressiongrid
neo-fable
neo-fable commented on Jun 12, 2026, 2:09 AM

Context

Live-session trace evidence (@neo-fable-clio's forensic package on the operator's Chrome session, recorded 2026-06-11 ~23:18, preserved in the SortZone ring buffer): during an overdrag walk, the zone's scroll term raced 0→1884 in ~370ms with the pointer nearly still (x≈1716-1784), force-switching the dragged column through twenty involuntary switches in ~235ms. The trace shows scroll events carrying one-step-stale term values and scrollSync echoes arriving duplicated and out of order (364→444→364; 524→444). The operator's no-locks control test (recorded on #12930) shows the downstream face: overdrag walks where body cells never animate while header switching proceeds.

Release classification: ON the board — v13 gate family (parent #12883).

The Problem

The overdrag pipeline (the #12908 reroute) keeps the zone's scroll term current through TWO writers: the optimistic set in grid.header.Toolbar#scrollToIndex (me.scrollLeft = target before the DOM command) and the authoritative echo (grid.ScrollManager#onContainerScroll mirroring scrollbar scroll events back into the toolbar config). The PR's review explicitly carried the claim "the echo re-sets the same value (idempotent)" — the claim is falsified by event re-ordering: echoes are only idempotent when they arrive in order. A stale echo overwriting a newer optimistic value regresses the term; the overdrag loop's nearest-math then re-commands a scroll it believes it still needs; each regression re-arms the loop — a feedback runaway. Switch decisions ride the same term, so the dragged column force-walks with the storm.

The Architectural Reality

  • Toolbar#afterSetScrollLeft forwards EVERY config write into the drag zone's term — commanded targets, ordered echoes, and stale echoes alike; nothing distinguishes them.
  • The worker receives scrollbar scroll events through the capture pipeline at queue mercy; burst scrolling (one DomAccess.scrollTo per walk beat) guarantees multiple echoes in flight, so re-ordering is expected, not exotic.
  • dup-marked move events in the same trace suggest possible double-dispatch of drag moves on some DOM paths (Chrome session) — a compounding amplifier, tracked here as secondary.

The Fix

Command/echo reconciliation on the toolbar (the single chokepoint both writers pass through):

  1. Toolbar#scrollToIndex records the commanded value (pendingScrollTarget = target) before the optimistic set + DOM command.
  2. Toolbar#beforeSetScrollLeft (new): while a command is pending, an incoming value that does not match the pending target is a stale echo — reject it (return the current value). The matching echo clears the pending state and passes.
  3. Drag end clears any pending state unconditionally (the drag-end re-sync remains the final authority).
  4. User scrolling outside a drag is unaffected (no pending command → all values pass).

Acceptance Criteria

  • The existing two-leg overdrag spec stays green (walk reaches the content end and returns home; exact landing).
  • By construction: a stale echo can no longer regress the term mid-walk (unit-style coverage of the beforeSet gate).
  • Operator re-test: overdrag walks at human speed produce no involuntary switch storms (parent #12883's pass).
  • The dup double-dispatch markers are characterized (fixed here if trivial, split if not).

Out of Scope

  • The cell-id generation doubling (#12930, @neo-fable-clio's lane) and the view-level region-body DOM duplication (tracked on #12929's trail).

Related

  • Parent: #12883 · Term pipeline: #12906/#12908 · Desync sibling: #12929 · Evidence: #12930 trail + the preserved ring-buffer trace

Live latest-open sweep: checked at 2026-06-12T00:0xZ; no equivalent (#12929 covers the drag-END desync; this is the MID-WALK term oscillation). A2A in-flight sweep: lane split with @neo-fable-clio explicitly assigns this leg to me.

Origin Session ID: e605ce21-3668-445c-bc00-45896aa9a092

Retrieval Hint: "overdrag echo out-of-order oscillation runaway switch storm pendingScrollTarget"

tobiu referenced in commit c3d3720 - "fix: scrolled-drop re-sync, single-fire lock rebuild, stale-echo gate (#12929, #12932) (#12938) on Jun 12, 2026, 3:55 AM
tobiu closed this issue on Jun 12, 2026, 3:55 AM