LearnNewsExamplesServices
Frontmatter
id17889
titleMigrate the drag sensors to Pointer Events with pointer capture
stateOpen
labels
enhancementaiarchitecture
assignees[]
createdAtAug 30, 2026, 8:19 PM
updatedAtOct 4, 2026, 1:04 PM
githubUrlhttps://github.com/neomjs/neo/issues/17889
authorneo-opus-vega
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

Migrate the drag sensors to Pointer Events with pointer capture

Open Backlog/active-chunk-19 enhancementaiarchitecture
neo-opus-vega
neo-opus-vega commented on Aug 30, 2026, 8:19 PM

Context

Two defect classes measured this week both trace to the same foundation: the drag sensors are a mouse/touch-event, hit-testing-based design. (1) A pointer crossing an iframe pane starves the move stream and can swallow pointerup, leaving a session in neo-is-dragging forever — #17883, shielded in PR #17887 by making iframes hit-test-inert during a session. (2) A session whose pointerup is lost terminates never — observed surviving multiple later gestures. Both are symptoms of relying on hit-testing to keep receiving a gesture the sensor already owns.

The platform primitive that removes the class is pointer capture: setPointerCapture(pointerId) routes every subsequent pointer event to the capturing element until release — hit-testing bypassed entirely (iframes included), pointerup/pointercancel delivery guaranteed to the captor, implicit release built in.

The Architectural Reality

  • src/main/draggable/sensor/Mouse.mjs — claims gestures via testPathInclusion (:135), installs a document-level dragstart → preventDefault for the gesture (:162/:217), stamps body.neo-drag-active (:160/:215); secondary-click/ctrl/meta gating lives here too.
  • src/main/draggable/sensor/Touch.mjs — a separate sensor for the same job. Pointer Events unify mouse, touch, and pen: the migration plausibly collapses two sensors into one.
  • src/main/addon/DragDrop.mjs:424 — session cleanup half.
  • The sensors' OUTPUT contract (drag:start / drag:move / drag:end synthetic events into the worker world) stays untouched — consumers, DragZones, the dock stack, and the #17885 native-drag partition gate never notice.

The Fix

Replace the mouse/touch listeners with pointer listeners; on claiming a gesture, setPointerCapture on a stable capture target; translate to the existing synthetic vocabulary; release capture on end/cancel. Port the existing gating semantics verbatim (dragTargetClasses claim, primary-button/modifier checks, the native-dragstart suppression for claimed gestures, neo-drag-active stamping). Migration details that must not be lost:

  • touch-action: none (or pan-x/pan-y as appropriate) on drag handles — without it, browser gestures fire pointercancel mid-drag on touch devices.
  • pointercancel is a first-class terminal now: it must end sessions as cleanly as pointerup (this is the structural fix for the never-terminating session).
  • The #17887 iframe shield stays in place as defense-in-depth; whether it can retire is a later, evidence-gated decision once capture has soaked (explicitly not part of this ticket).
  • The #17885 partition contract (native-drag sources exempt from sensor claiming) applies unchanged to the new listeners.

Acceptance Criteria

  • One pointer-based sensor path drives mouse, touch, and pen; the synthetic drag:* contract is byte-compatible for consumers.
  • A drag whose pointer crosses an iframe keeps streaming moves and terminates on release inside the iframe — asserted WITHOUT the #17887 shield active in the test (the shield stays shipped; the test isolates the sensor's own robustness).
  • pointercancel ends a session; no code path can leave neo-is-dragging on a node after the gesture's pointer is gone.
  • Existing drag/sort/splitter/dock suites green; touch-driven suites (or manual touch verification notes) included.
  • touch-action styling documented/applied for engine-owned draggable surfaces.

Out of Scope

  • Retiring the #17887 shield (separate, evidence-gated follow-up).
  • Native HTML5 drag sources (#17885) — partition unchanged.
  • The SharedWorkers first-mount race (#17888) — different layer.

Related

  • #17883 / PR #17887 — the shielded symptom this makes structural
  • #17885 — the native-drag partition that must keep holding
  • #17888 — sibling robustness work in the dock stack

Live latest-open sweep: latest 20 open checked 2026-08-30 ~18:20 UTC — no equivalent. A2A claim sweep: none; the idea was routed to @neo-opus-grace via A2A ~18:17 as future-direction — this ticket banks it per operator direction; her #17887 lane is untouched and she has first claim here if she wants it.

Origin Session ID: b51135ac-ec45-4689-ac45-0e99fb073069

Retrieval Hint: "pointer capture drag sensor iframe pointerup stuck neo-drag-active" · query_recent_turns 2026-08-30 (iframe drag forensics)

tobiu unassigned from @neo-opus-grace on Sep 1, 2026, 10:08 AM