Context
Epic #15239 leaf G3 — beat 3 of the five-beat story: dragging over ANOTHER popup lights dock-zone previews INSIDE it, deterministically, with exactly one target claiming the gesture. Implements the #15240 amendment's claim protocol (from D#15204 OQ3 [RESOLVED_TO_AC], cycle-4). Unowned — self-select.
The Problem
Three gaps compose this beat: (a) the App Worker holds ONE dock document per workspace today — multi-window needs the {workspaceId → document} workspace-set (ADR 0029 §2.1's nested-workspaces shape); (b) a remote target renders NO continuous preview during a foreign hover (only the landed 450 ms/250 ms candidate dwell); (c) physical target resolution is Window.getWindowAt() first-intersecting-rect — overlapping windows resolve by REGISTRATION ORDER (falsified in the Discussion), so popup-over-popup is nondeterministic today.
The Architectural Reality
Neo.manager.DragCoordinator: already N-window shaped (sortGroup → Map(windowId → zone)); the #13028 native-popup candidate path (450 ms dwell / 250 ms settle) is EXTENDED, never replaced.
DockCrossWindowParticipation / CrossWindowDragTarget: the preview→operation seam the remote preview feeds.
- The claim contract (authority:
#15240): session-scoped gesture token; short-lived hit-claims on STABLE workspace/zone identity (never windowId/registration order); validity/expiry; deterministic tie (earliest valid claim; stable-identity lexicographic final tiebreak), stale (ignored), no-claim (fail closed).
- Blocked-by:
#15240 (the contract must be ratified ADR text first) + #15244 (G1's tear-out produces the vessels this arbitrates).
The Fix
One PR: the workspace-set registry on the App-Worker dock service; continuous native-geometry remote preview through the participation seam (the target window renders its own affordances via the shared #15207 consumer); the claim-protocol implementation in the coordinator.
Acceptance Criteria (the graduated OQ3 contract + Step-Back ✓7)
Contract Ledger (folded 2026-07-18, implementer disposition on reviewer RA — maps the consumed surfaces)
| Surface |
Owner |
Caller(s) |
Inputs → Outputs |
Unknown / stale / failure behavior |
Persistence class (§2.1 table) |
createGestureClaimArbiter (src/manager/GestureClaimArbiter.mjs) |
manager tier (pure factory; one arbiter IS one gesture) |
DragCoordinator — pointer path: one per gesture (lazy mint, dies at end/cancel); native path: one per moving popup (dies at commit / source-end / departure) |
{claimTtlMs=300, now} → {token, claimCount, claim(stableId, zone)→record, release(stableId), reset(), resolve()→{stableId, zone}|null} |
No valid claim → resolve() null = fail closed (no preview, no commit). Expired claims ignored AND pruned at resolve; an expired record at claim() time is absent — reacquisition gets a NEW acquisition time (stale seniority cannot revive). Boundary: expiresAt >= now = live, identical to the resolver's prune condition. |
None — gesture-scoped runtime state; a token's claims die with its gesture. |
stableTargetId + the coordinator claim path |
CrossWindowDragTarget contract config; dock participation declares it as its workspaceId |
DragCoordinator.resolveClaimedTarget (pointer + native candidate resolution) |
registered zones + main-thread window innerRect geometry → the single winning zone, driving the LANDED enter/leave/suspend choreography |
A zone without stableTargetId never claims (pinned legacy first-intersecting path, closed to stable zones); containment or hit-test failing → claim released; no winner → fail closed. |
None — windowId stays runtime-only; the stable id is semantic workspace identity. |
createDockWorkspaceSet (src/dashboard/DockWorkspaceSet.mjs) |
the workspace container (App Worker) |
owner hosts (Demo-B today; G4/#15247 next); participation seams (getForeignDocument reads); keyboard-path consumers read ids()/getDocument() |
register(workspaceId, {getDocument, setDocument})→Boolean · unregister→Boolean · has · ids() · getDocument(id)→doc|null · adoptTransfer(pair)→Boolean |
Unknown id → null/false (fail closed). Re-register REPLACES (re-embodied vessel). adoptTransfer: preconditions checked before the first write; either write failing compensates every writer already invoked (reverse order, prior documents, guarded re-invocation — a mutating-then-throwing writer's own assignment ordering makes the restore stick) and the original error propagates. Documented residual: a writer defeating both the write and the compensation is a double accessor-contract breach; its own state is its breach, the pair's other side still restores. Retirement is explicit-only (the retained-vs-retired disposition is #15247's named decision). |
Registry: runtime worker-owned. The documents it resolves: §2.1 worker-owned shared truth. |
Demo-B adoption (commitCrossWindowTransfer) |
DemoBWorkspace (the reference reducer-container host) |
the participation commitTransfer seam (executor-committed pairs) |
committed {sourceDocument, targetDocument, …} → workspaceSet.adoptTransfer(pair) interior, then the owner's target-first reconcile + vessel bookkeeping |
A refused adoption ends the commit BEFORE vessel bookkeeping — projection choreography can never diverge from document truth. Consumers bind to commitCrossWindowTransfer, never to adoptTransfer directly (double-adoption / false-owner hazard). |
Owner fields = worker-owned shared truth; reconcile choreography runtime-only. |
Out of Scope
Whole-stack reintegration + vessel close (G4) · tear-out itself (G1/G2) · teardown-residue hygiene (its own leaf).
Related
Parent #15239 · blocked-by #15240 + #15244 · D#15204 (OQ3; the falsified getWindowAt reality) · #13028 · #15207 (the shared consumer).
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: "G3 workspace-set claim arbitration continuous remote preview three-window overlap witness"
Context
Epic
#15239leaf G3 — beat 3 of the five-beat story: dragging over ANOTHER popup lights dock-zone previews INSIDE it, deterministically, with exactly one target claiming the gesture. Implements the#15240amendment's claim protocol (from D#15204 OQ3[RESOLVED_TO_AC], cycle-4). Unowned — self-select.The Problem
Three gaps compose this beat: (a) the App Worker holds ONE dock document per workspace today — multi-window needs the
{workspaceId → document}workspace-set (ADR 0029 §2.1's nested-workspaces shape); (b) a remote target renders NO continuous preview during a foreign hover (only the landed 450 ms/250 ms candidate dwell); (c) physical target resolution isWindow.getWindowAt()first-intersecting-rect — overlapping windows resolve by REGISTRATION ORDER (falsified in the Discussion), so popup-over-popup is nondeterministic today.The Architectural Reality
Neo.manager.DragCoordinator: already N-window shaped (sortGroup → Map(windowId → zone)); the#13028native-popup candidate path (450 ms dwell / 250 ms settle) is EXTENDED, never replaced.DockCrossWindowParticipation/CrossWindowDragTarget: the preview→operation seam the remote preview feeds.#15240): session-scoped gesture token; short-lived hit-claims on STABLE workspace/zone identity (neverwindowId/registration order); validity/expiry; deterministic tie (earliest valid claim; stable-identity lexicographic final tiebreak), stale (ignored), no-claim (fail closed).#15240(the contract must be ratified ADR text first) +#15244(G1's tear-out produces the vessels this arbitrates).The Fix
One PR: the workspace-set registry on the App-Worker dock service; continuous native-geometry remote preview through the participation seam (the target window renders its own affordances via the shared
#15207consumer); the claim-protocol implementation in the coordinator.Acceptance Criteria (the graduated OQ3 contract + Step-Back ✓7)
{workspaceId → document}composition: each vessel's document registered under a stable workspace id;windowIdstays runtime-only (never persisted identity).#15240: token, stable-identity claims, expiry, deterministic tie/stale/no-claim — fail closed (no preview, no commit) when unclaimed.windowId/geometry are never active identity.getWindowAtfirst-intersecting) is fully replaced on the dock path; the old path's behavior is pinned as the negative witness.Contract Ledger (folded 2026-07-18, implementer disposition on reviewer RA — maps the consumed surfaces)
createGestureClaimArbiter(src/manager/GestureClaimArbiter.mjs)DragCoordinator— pointer path: one per gesture (lazy mint, dies at end/cancel); native path: one per moving popup (dies at commit / source-end / departure){claimTtlMs=300, now}→{token, claimCount, claim(stableId, zone)→record, release(stableId), reset(), resolve()→{stableId, zone}|null}resolve()null = fail closed (no preview, no commit). Expired claims ignored AND pruned at resolve; an expired record atclaim()time is absent — reacquisition gets a NEW acquisition time (stale seniority cannot revive). Boundary:expiresAt >= now= live, identical to the resolver's prune condition.stableTargetId+ the coordinator claim pathCrossWindowDragTargetcontract config; dock participation declares it as itsworkspaceIdDragCoordinator.resolveClaimedTarget(pointer + native candidate resolution)innerRectgeometry → the single winning zone, driving the LANDED enter/leave/suspend choreographystableTargetIdnever claims (pinned legacy first-intersecting path, closed to stable zones); containment or hit-test failing → claim released; no winner → fail closed.windowIdstays runtime-only; the stable id is semantic workspace identity.createDockWorkspaceSet(src/dashboard/DockWorkspaceSet.mjs)getForeignDocumentreads); keyboard-path consumers readids()/getDocument()register(workspaceId, {getDocument, setDocument})→Boolean·unregister→Boolean·has·ids()·getDocument(id)→doc|null·adoptTransfer(pair)→Booleannull/false(fail closed). Re-register REPLACES (re-embodied vessel).adoptTransfer: preconditions checked before the first write; either write failing compensates every writer already invoked (reverse order, prior documents, guarded re-invocation — a mutating-then-throwing writer's own assignment ordering makes the restore stick) and the original error propagates. Documented residual: a writer defeating both the write and the compensation is a double accessor-contract breach; its own state is its breach, the pair's other side still restores. Retirement is explicit-only (the retained-vs-retired disposition is #15247's named decision).commitCrossWindowTransfer)DemoBWorkspace(the reference reducer-container host)commitTransferseam (executor-committed pairs){sourceDocument, targetDocument, …}→workspaceSet.adoptTransfer(pair)interior, then the owner's target-first reconcile + vessel bookkeepingcommitCrossWindowTransfer, never toadoptTransferdirectly (double-adoption / false-owner hazard).Out of Scope
Whole-stack reintegration + vessel close (G4) · tear-out itself (G1/G2) · teardown-residue hygiene (its own leaf).
Related
Parent
#15239· blocked-by#15240+#15244· D#15204 (OQ3; the falsifiedgetWindowAtreality) ·#13028·#15207(the shared consumer).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: "G3 workspace-set claim arbitration continuous remote preview three-window overlap witness"