LearnNewsExamplesServices
Frontmatter
id15248
titleDragCoordinator teardown hygiene: exact-once cleanup across gesture terminals
stateOpen
labels
enhancementaitesting
assignees[]
createdAt1:25 PM
updatedAt1:25 PM
githubUrlhttps://github.com/neomjs/neo/issues/15248
authorneo-fable-clio
commentsCount0
parentIssue15239
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[ ] 15240 Amend ADR 0029: claim arbitration, gesture outcome states, vessel lifecycle
blocking[]

DragCoordinator teardown hygiene: exact-once cleanup across gesture terminals

Open Backlog/active-chunk-6 enhancementaitesting
neo-fable-clio
neo-fable-clio commented on 1:25 PM

Context

Epic #15239 leaf — the cleanup-invariant enforcement of the #15240 outcome machine, split from G3/G4 so the idempotency work is one reviewable PR (D#15204 graduation shape named "the coordinator teardown hygiene leaf" explicitly; Step-Back ✗4 falsified the current residue). Unowned — self-select.

The Problem

Falsified residue paths in the current coordinator family (Step-Back ✗4, source-verified):

  1. DragCoordinator.onDragEnd() calls source onRemoteDropOut() UNCONDITIONALLY — CrossWindowDragTarget.onRemoteDrop() returning null (no preview/operation committed) still retires the source gesture: a no-commit drop can strand the dragged item's source state.
  2. DragCoordinator.unregister() clears candidates but can leave activeTargetZone populated — a stale target survives a vessel's departure.
  3. Candidate timers (the 450 ms/250 ms dwell path) and preview state need exact-once disposal across ALL four terminals (COMMITTED_TARGET | TERMINAL_DETACHED | REJECTED | CANCELLED).

The Architectural Reality

Neo.manager.DragCoordinator (App Worker) + the participation seams (DockCrossWindowParticipation, CrossWindowDragTarget, DockTabSortZone.onRemoteDropOut/suspend/resume). The #15240 amendment ratifies the invariants; this leaf makes the code and witnesses match them. Blocked-by: #15240 (the invariant authority). Parallel-safe with G1 (different files).

The Fix

One PR: outcome-aware onDragEnd (source retirement ONLY on a committed outcome), unregister clearing activeTargetZone + timers, and an idempotency witness suite over every terminal × every cleanup surface.

Acceptance Criteria (the graduated invariant set)

  • onRemoteDropOut() fires ONLY when the target committed (onRemoteDrop() returned a real operation) — the null-commit path resumes/restores the source with zero model mutation; witnessed with the exact falsifier shape (begin drop, no commit, assert source intact).
  • unregister() leaves no activeTargetZone, no candidate timer, no preview residue; re-registering the same identity starts clean.
  • Every cleanup surface (preview, claim, candidate timer, activeTargetZone, registration, vessel bookkeeping) is idempotent — double-invocation of any terminal is a witnessed no-op.
  • The witness matrix covers all four terminals, including a mid-gesture vessel disappearance (window closed under the drag) resolving to a clean terminal.
  • Zero behavior change on the committed happy path (the existing cross-window witness stays green unmodified).

Out of Scope

The claim protocol itself (G3) · vessel close policy (G4) · any new gesture capability.

Related

Parent #15239 · blocked-by #15240 · D#15204 (Step-Back ✗4) · siblings #15246 (G3) / #15247 (G4, consumes these invariants).

Live latest-open sweep: latest 20 at 2026-07-16T10:56Z + last-step re-check 11:09Z; no equivalent. A2A claim sweep: no competing claim.

Origin Session ID: c5d7cd6b-4e01-45fd-aa59-5ccbc0e5f091 Retrieval Hint: "coordinator teardown activeTargetZone onRemoteDropOut null-commit idempotent exact-once"