LearnNewsExamplesServices
Frontmatter
id12939
titleGrid view→body update races spawn id-less insertNode duplicates (stale vnode baselines)
stateClosed
labels
bugairegressiongrid
assigneesneo-fable
createdAtJun 12, 2026, 3:07 AM
updatedAtJun 12, 2026, 7:39 AM
githubUrlhttps://github.com/neomjs/neo/issues/12939
authorneo-fable
commentsCount4
parentIssue12883
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 12, 2026, 7:39 AM

Grid view→body update races spawn id-less insertNode duplicates (stale vnode baselines)

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

Context

The delta-level source of the v13 gate family's duplication symptoms, captured 2026-06-12 ~00:25 via main-thread logDeltaUpdates during a single overdrag walk (full harvest on #12929; reproducible deterministically in ~10s by the probe spec on the agent/12929-12932-grid-fixes trail). Splits the FIX lane out of #12929 (whose within-region legs PR #12938 delivers) so the trail survives that close.

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

The Problem (delta evidence)

During one overdrag walk (DOM grid-body count 3 → 5):

update 37  (1):   set        neo-grid-body-3                 ← full-node re-render, end body
update 43  (120): insertNode → neo-grid-body-1__row-N idx 22 ← ID-LESS cell inserts, per row
update 51-75:     insertNode → …__row-N idx 23/24/25/26 (100-160 deltas per beat)
update 89  (1):   insertNode → neo-grid-view-1 idx 2         ← ID-LESS body insert: a body-3 COPY born
update 107 (1):   set        neo-grid-body-3

An id-less insertNode means the differ created a new node from vdom instead of matching an existing one — its vnode baseline did not contain what the DOM already held. Two layers, one mechanism:

  • view-level: region-body copies born into neo-grid-view-1 (up to ×14 observed across a session — the operator's "columns duplicate" screenshot)
  • row-level: new cells created instead of pool recycling — the two-generation cell-id doubling (#12930) at the moment of birth

Locked regions are irrelevant: the operator's no-locks control reproduces (recorded on #12930) — it is the multi-body-era view→body→row update stack, exercised at walk-beat frequency.

The Architectural Reality

Per walk beat: grid.ScrollManager ingests the scroll → View.syncBodies runs silent per-body sets + createViewData(true) then view.updateDepth = 3; view.update() (throttled 16ms) — concurrently, each body's own update paths fire (buffered re-mounts emitting 100-160 deltas per beat; mid-drag, switchItems additionally runs body.updateDepth = -1; body.update()). Updates whose vnode baselines go stale against each other produce created-not-matched children. The framework's update-collision machinery (VdomLifecycle merging/queueing) is either bypassed by the silent-set + direct-createViewData paths or defeated by the depth-spanning parent update racing child updates.

The Fix (territory — exact patch from the investigation)

Vnode-baseline discipline / update serialization at the collision point: the view's depth-spanning update and the bodies' own updates must not diff against each other's stale baselines. Candidate shapes (to be validated against the probe): route the per-beat body updates THROUGH the view's single update (true silent aggregation), or gate syncBodies' view update on no body-update-in-flight (and vice versa), or fix the baseline capture in the depth-merge path. Symptom-side dedup is explicitly rejected.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
Grid view→body per-beat update flow (View.syncBodies + body createViewData interplay) This delta harvest + the framework's VdomLifecycle collision contract One coherent baseline per frame: no id-less insertNode for nodes that exist in the DOM; pool cells recycle, never re-create Behavior under non-scroll updates (store loads, resize) unchanged — probe forensics cover baseline + drags + scrolls JSDoc at the touched flow points L3: the 10s probe — verify_component_consistency(view) clean through walk+drop; delta capture shows zero id-less inserts for existing nodes; #12930's repro A/B gestures clean. L4: operator gate session
grid.Row pool-cell identity (__cell-N ↔ field-named) #12930 (the cell-layer manifestation) Unchanged by this ticket's fix UNLESS the bisect (#12930, @neo-fable-clio) shows an independent identity seam — the two lanes converge on one patch if not Cross-validated with her bisect baselines n/a here Her #12930 verification matrix

Acceptance Criteria

  • One full probe run (drags, scroll cycles, overdrag walk + scrolled drop, drag-in-window): verify_component_consistency on the view AND a body sample = zero mismatches at every step.
  • Delta capture during a walk shows no id-less insertNode for already-mounted nodes.
  • #12930's repros A (same-drag reversal) and B (overdrag cells don't animate) re-tested against the fix — closed there if cured, else the residual is characterized.
  • Operator gate session (batched).

Out of Scope

  • The already-fixed legs (PR #12938: drop re-sync, single-fire rebuild, echo gate).
  • NL motion-forensics granularity (#12931).

Related

  • Parent: #12883 · Evidence trail: #12929 (delta harvest comment) · Cell layer: #12930 (@neo-fable-clio's bisect — convergence expected) · Fixture: #12936

Live latest-open sweep: checked at 2026-06-12T01:0xZ; #12930 covers the cell-layer manifestation and its bisect — this ticket owns the update-race FIX; no other equivalent. A2A in-flight sweep: the lane split (her bisect / my fix) is on the record both directions.

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

Retrieval Hint: "id-less insertNode stale vnode baseline view syncBodies update race grid duplication fix"

tobiu referenced in commit 82778b0 - "test(grid): locked-DnD duplication net — four stateful oracles per gesture (#12939) (#12962) on Jun 12, 2026, 7:39 AM
tobiu closed this issue on Jun 12, 2026, 7:39 AM