LearnNewsExamplesServices
Frontmatter
id15246
titleWorkspace-set composition, continuous remote preview, claim arbitration
stateClosed
labels
enhancementaiarchitecture
assigneesneo-fable-clio
createdAtJul 16, 2026, 1:24 PM
updatedAtJul 19, 2026, 11:09 AM
githubUrlhttps://github.com/neomjs/neo/issues/15246
authorneo-fable-clio
commentsCount0
parentIssue15239
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 15244 Dock tear-out: lift the proxy-to-popup opt-out into dock semantics, [x] 15240 Amend ADR 0029: claim arbitration, gesture outcome states, vessel lifecycle
blocking[ ] 15252 The five-beat multi-window wow demo: recorded journey on the workstation, [x] 15250 Keyboard detach path: a11y parity for the multi-window choreography, [x] 15247 Whole-stack reintegration and the vessel close policy
closedAtJul 18, 2026, 5:15 PM

Workspace-set composition, continuous remote preview, claim arbitration

Closed Backlog/active-chunk-6 enhancementaiarchitecture
neo-fable-clio
neo-fable-clio commented on Jul 16, 2026, 1:24 PM

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)

  • {workspaceId → document} composition: each vessel's document registered under a stable workspace id; windowId stays runtime-only (never persisted identity).
  • Continuous remote preview: hovering a foreign popup renders that popup's OWN zone previews/menu through the shared affordance consumer, updating per frame — not only after the dwell timer.
  • The claim protocol implemented exactly per #15240: token, stable-identity claims, expiry, deterministic tie/stale/no-claim — fail closed (no preview, no commit) when unclaimed.
  • The ≥3-window OVERLAP witness: three overlapping windows, one gesture → exactly ONE preview and exactly ONE commit (the graduated falsifier, verbatim).
  • Active-vs-saved boundary preserved (Step-Back ✓7): worker documents/hints stay separate from per-window projections and runtime hover state; saved slot order/windowId/geometry are never active identity.
  • Registration-order resolution (getWindowAt first-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)

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"