LearnNewsExamplesServices
Frontmatter
id14980
titleEarly gestures are deaf to worker-side drag zones + no gesture-time key capture exists
stateClosed
labels
bugai
assigneesneo-gpt-emmy
createdAtJul 10, 2026, 8:25 PM
updatedAtJul 14, 2026, 10:17 PM
githubUrlhttps://github.com/neomjs/neo/issues/14980
authorneo-fable-clio
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 14772 Cross-window drag showcase: the two-window transfer demo scene
closedAtJul 14, 2026, 10:17 PM
milestonev13.2

Early gestures are deaf to worker-side drag zones + no gesture-time key capture exists

neo-fable-clio
neo-fable-clio commented on Jul 10, 2026, 8:25 PM

Context

Found while building the binding §06 gesture witness for PR #14974 (2026-07-10). Two related engine-tier gaps in the drag layer's lifecycle, both bisected against the live dockdemo surface with a scripted-pointer reproducer.

The Problem

Gap 1 — the early-gesture deafness race. A real pointer drag started within ~1s of app mount renders a drag proxy and the neo-is-dragging state (main-thread DragDrop addon — instant), but no worker-side sort zone ever hears the gesture: the zones' delegated drag:start / drag:move DOM-listener registration round-trip (worker → main) has not landed when the gesture begins. Consequences on the dock surfaces: no dockCrossZoneDragMove / dockCrossZoneDrop fire, so the drop-indicator menu never lights and the release commits nothing — the drag LOOKS alive and is functionally dead.

Bisected cleanly (fixture probe, identical flow): pointer-down at +0ms after readiness selectors → deaf (subclass onDragMove never runs — verified via an instance-field trail read over the Neural Link, since SharedWorker consoles don't bubble to the page); pointer-down at +1000ms → everything fires. The .neo-draggable cls is NOT a sufficient readiness witness — it lands before the listener registration completes.

Gap 2 — no gesture-time key capture. During a header drag, the base sort zone placeholder-swaps the dragged button out of the DOM; browser focus falls to <body>, so element-scoped keydown listeners (the only kind a workspace can register) can never receive a real mid-drag Escape. The §06 Escape-cancel seam (PR #14974) is therefore drivable and unit/e2e-proven at the seam tier, but a REAL keystroke has no path to it. The same family as the known no-abort-API residual (a cancelled drag's proxy visual rides until release).

The Architectural Reality

  • Main-thread DragDrop addon owns proxy/visuals and works from frame one; worker zones subscribe via addDomListeners (async round-trip) — the two halves have no shared readiness contract.
  • DockTabSortZone fires the dock seams from onDragStart/Move/End — all downstream of the deaf registration.
  • No drag:cancel primitive exists anywhere in the stack (grep-verified), and no window-scoped key listener surface is available to worker components during a gesture.

The Fix

Investigation-first, two candidate shapes:

  1. Readiness: either the main-thread addon defers gesture-start until the worker's drag listeners for the pressed element's path are registered (a registration-complete handshake), or a queryable/awaitable readiness signal is exposed so surfaces (and specs) can gate honestly. Today's only workaround is a blind settle (documented in test/playwright/e2e/dashboard/DemoADragMenuNL.spec.mjs).
  2. Gesture-time input capture: while a drag session is active, the main-thread drag owner captures keydown (at minimum Escape) and routes it into the drag event stream (drag:cancel or a keyed field on drag:move/drag:end) — giving every drop consumer one honest cancel primitive and retiring the per-workspace Escape workarounds.

Acceptance Criteria

  • A pointer drag started immediately after first paint reaches worker-side zones (or is deferred until it can) — the +0ms reproducer passes without a settle.
  • The blind settle in DemoADragMenuNL.spec.mjs is removed in favor of the readiness contract.
  • A real mid-drag Escape reaches a documented cancel primitive; the Demo-A Escape e2e upgrades from seam-driven to real-keystroke.
  • Regression specs at the owning tier for both gaps.

Out of Scope

  • The dock surfaces' consumer logic (PR #14974) — correct once events arrive.
  • The #14970 overlay cascade defect (separate, PR #14975 in flight).

Decision Record impact

none — engine defect investigation; no ADR authority touched.

Related: #14974 (the witness that found both) · #13158 · #14923 (drag-lifecycle destroy family) · #14970 (sibling finding from the same gate lineage).

Live latest-open sweep: latest 12 checked at 2026-07-10T18:12Z; no equivalent (nearest #14970 = CSS cascade, disjoint). A2A herd window checked: no competing claim.

Origin Session ID: 183ac080-2f24-4837-8f63-69bfc536af0d Retrieval Hint: "early gesture drag listener registration race worker deaf escape key capture drag cancel"

tobiu referenced in commit 9fa53b7 - "feat(dashboard): the drop-indicator menu — candidate-set producer + DockDropIndicators + the Demo-A §06 experience (#14959) (#14974) on Jul 10, 2026, 9:33 PM
tobiu referenced in commit 3f526bd - "fix(drag): add gesture readiness and cancellation (#14980) (#15182) on Jul 14, 2026, 10:17 PM
tobiu closed this issue on Jul 14, 2026, 10:17 PM