LearnNewsExamplesServices
Frontmatter
id16758
titleFirst drag:start of every boot forwards with dragZoneId null — the zone id only arrives via the app-side dragStart handshake
stateClosed
labels
bugaitesting
assigneesneo-kimi-phoebe
createdAtAug 9, 2026, 12:30 AM
updatedAtAug 9, 2026, 8:07 PM
githubUrlhttps://github.com/neomjs/neo/issues/16758
authorneo-kimi-phoebe
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 9, 2026, 8:07 PM

First drag:start of every boot forwards with dragZoneId null — the zone id only arrives via the app-side dragStart handshake

Closed Backlog/active-chunk-14 bugaitesting
neo-kimi-phoebe
neo-kimi-phoebe commented on Aug 9, 2026, 12:30 AM

Context

Surfaced by the #16365 probe ladder (three deterministic boot cycles, CDP page.mouse, headless Chromium, dev head 7f81768063+): the first dock drag gesture after every app boot forwards its drag:start to the app worker with dragZoneId: null, while the first drag:move (6–14ms later) already carries the zone. The gesture's beginning is invisible to any consumer keyed on the zone id; its middle and end process normally and the resize commits. Live latest-open sweep: latest 20 checked 2026-08-08 ~22:2xZ (newest #16756); nearest neighbours distinguished: #16365 (synthetic-buttons=0 wedge — resolved), #16754 (the 08-02 transient — different surface).

The Problem

The zone id reaches the main thread only through the app worker: DragDrop.onDragStart (src/main/addon/DragDrop.mjs) forwards dragZoneId from its own config (default null, never assigned in that file), and the only writer is the app-side DragZone.dragStartDragDrop.setConfigs({...getMainThreadConfigs()}) (src/draggable/DragZone.mjs:427,505), which runs in response to the very start being forwarded. So the first start in every boot is zoneless by construction — a cold-start race baked into the handshake, not a timing flake.

Deterministic receipts (probe spec, per-run fresh boot):

  • run 1: drag:start dragZoneId:null @t=602drag:move neo-dragzone-1 @t=614
  • run 2: drag:start dragZoneId:null @t=1115drag:move neo-dragzone-1 @t=1121
  • run 3: same shape (earlier probe session)

The Architectural Reality

  • Neo.main.addon.DragDrop.dragZoneId is read at five forward sites (:309,319,512,573,613,628) and at the Escape guard (:337if (event.key === 'Escape' && me.dragZoneId && !me.dragCancelled)).
  • Consequence with a name on it: an Escape pressed in the opening window of the FIRST-ever drag finds dragZoneId: null and does not cancel — the logical drag continues while the user believes it cancelled. Narrow, real, and exactly the class the guard exists for.
  • Open question the ticket must answer before any repair: does the app-side router deliver a zoneless start to the correct zone anyway (by event target-path id), or is DockSplitter.onDragStart skipped for cold-start gestures? Probe evidence is ambiguous: the resize commits via moves, while the app-side dragStartState read stayed null at mid-gesture sample time (possible read race — named, not inferred away).

The Fix

  1. Answer the open question with a probe receipt: instrument the app-side routing of the zoneless start (which zone handler, if any, receives it; does dragStartState set before the first move).
  2. Repair at the owning layer — candidates, in test order: (a) DragDrop.onDragStart resolves the zone from the event target-path synchronously instead of reading its own config; (b) the app-side zone registers its main-thread configs eagerly at zone creation (boot), not lazily on first drag; (c) if the zoneless start is genuinely benign except the Escape window, the Escape guard keys on an engaged-gesture fact rather than the zone id.
  3. Witness: the cold-start boot-cycle probe (first gesture of a fresh boot) asserts the forwarded start carries a zone AND the Escape guard cancels inside the opening window.

Acceptance Criteria

  • The app-side fate of the zoneless first start is named with a probe receipt (routed-and-benign vs dropped-handler vs read-race artifact).
  • The cold-start Escape window is either closed or proven unreachable, with a witness.
  • drag:start forwards carry the zone id from the first gesture of a boot, verified across 3 consecutive boot cycles.

Out of Scope

  • The #16365 synthetic-buttons=0 wedge (resolved) and the #16754 transient (open, different surface).
  • Drag proxy rendering / drop indicators.

Avoided Traps

  • Treating the null-zone start as harmless because the resize commits — the Escape guard's zone-keyed condition is the counterexample already in the file.
  • Repairing at the sensor: the sensor's 'drag:start' CustomEvent is fine; the zone id is a DragDrop-config concern.

Decision Record impact

none.

Related

#16365 (probe ladder origin, resolved), #16754 (transient tracker — this observation was parked there pending this second witness), #16381 (recovery — unrelated mechanism).

Origin Session ID: 94296ece-1e77-47de-b74c-d1f3b63d8265

Retrieval Hint: query_raw_memories("dragZoneId null first drag:start cold-start DragDrop setConfigs")

tobiu referenced in commit 2d646f3 - "fix(draggable): resolve the drag zone synchronously at drag:start via an eager registry (#16758) (#16797) on Aug 9, 2026, 8:07 PM
tobiu closed this issue on Aug 9, 2026, 8:07 PM