Context
Foundation leaf of #15396 (epic #15239, G3 family), the same tier split just executed for its companion (#15432 ← #15395, PR #15434; pattern lineage #15407 ← #15244, #15426 ← #15243): #15396's platform park MECHANIC (hide vs offscreen-move vs minimize — moveTo is advisory, #15243 row 3) is matrix-gated, and its headed round-trip witness rides the harness — but the park CHOREOGRAPHY (what happens, in what order, exactly once) is pure decision-machine territory every consumer needs settled now. This ticket is the Resolves target for the contract-tier PR; #15396 stays open owning the matrix-cited mechanic selection + the headed round-trip cell.
The Problem
#15396 proves close-and-reopen is broken by platform law: popup acquisition consumes transient user activation and window.open reports failure by Boolean — a popup→proxy→popup round-trip within one gesture cannot count on re-acquisition, so conversion (the #15432 sensor's convert-in) must PARK the vessel, never close it. No module owns that choreography; without a settled machine, the #15395 wire-in and the G4 terminal work would each improvise lifecycle order — and any improvisation that closes mid-gesture ships the one-way door the ticket exists to prevent.
The Fix
One pure, zero-import choreography module — src/dashboard/DockVesselPark.mjs, factory createVesselParkHandlers matching the landed createDockTearOutHandlers idiom (closure state over one single-gesture park slot, injected seams, witnesses drive without a browser):
- Convert-in parks, never closes. The slot records
{itemId, windowName, preConversionRect}; the host's parkVessel seam actuates the platform mechanic (matrix-selected, the host's business). A duplicate convert-in with a live slot is a stale re-fire: ignored.
- Convert-out re-shows the SAME window through the
reshowVessel seam — at the out-event's live rect when supplied (the §2.3 resumeWindowDrag(widgetName, proxyRect) semantics: the popup resumes under the pointer), falling back to the recorded pre-conversion rect (the cancel/origin semantics). Zero re-acquisition BY CONSTRUCTION: the machine has no acquisition seam at all — the activation wall cannot be hit through it.
- Terminals dispose exactly once, commit-only.
COMMITTED_TARGET with a live matching slot routes the ONE disposeVessel call (the host binds it to the G4 close policy); every non-commit outcome fails toward RESTORE (re-show at the pre-conversion rect) — the machine never loses the user's window. Slot cleared before seam invocation; duplicate terminals and mismatched itemIds are no-ops (the §2.8.2 invariant-4 exact-once/idempotent bar, the DockTearOut stale-terminal guard idiom).
- Fail-loud config: all three seams are required functions.
Contract Ledger
| Surface |
Direction |
Contract |
createVesselParkHandlers({disposeVessel, parkVessel, reshowVessel}) → {onConversionIn, onConversionOut, onGestureTerminal, parkedVessel} |
provided |
The in-gesture vessel lifecycle authority between conversion and terminal: park/re-show/dispose ordering, exact-once disposition, restore-on-non-commit. Zero imports; no window/DOM/model access; no acquisition capability. |
ADR 0029 §2.8.2 (outcome machine) + §2.8.3 (vessel lifecycle) + §2.3 resumeWindowDrag rect semantics |
consumed (upstream authority) |
The machine implements the in-gesture segment between HOVERING_CLAIM entry and the gesture terminals; commit-precedes-close and close-as-render-target-effect stay the host's obligations at the seams. |
#15432's sensor (convert-in/out events), #15396 (mechanic + headed cell), #15247 (G4 close policy behind disposeVessel), #15395 wire-in |
downstream consumers |
They bind the handlers; lifecycle ordering changes land HERE. The parent's headed round-trip witness receipts land on #15396. |
Acceptance Criteria
Out of Scope
The platform park mechanic selection + matrix citation and the headed round-trip cell (#15396) · the conversion decision itself (#15432, shipped) · the G4 reintegration close policy (#15247 — consumed behind disposeVessel) · claim arbitration (#15246) · disconnect correlation (§2.8.3 — the host routes disconnects to terminals; the machine only consumes them).
Decision Record impact
depends-on ADR 0029 §2.8.2/§2.8.3/§2.3 — consumed as upstream authority, not amended (park is in-gesture embodiment state behind host seams, never a new outcome-machine state — same negative resolution class as #15432's).
Related
Parent #15396 (stays open; owns mechanic + headed remainder) · epic #15239 · companion #15432 / PR #15434 (the sensor whose events this consumes) · #15247 (the close policy behind the dispose seam) · #15215 (preserved-park salvage source) · #15243 (the harness the parent's cell rides).
Live latest-open sweep: checked 2026-07-18T06:24Z (top-10 via gh issue list); #15396 is the only park-family leaf — no contract-tier equivalent. A2A sweep: my [lane-claim] follows this filing; no competing claim (Mnemo on #15404/PR #15435, Ada on the #15244 e2e leg + PR #15434 seat, Emmy on #15433/#15435 seat).
Origin Session ID: 0c8fc4d9-2456-44fd-b120-048402bb9839
Retrieval Hint: query_raw_memories("vessel park contract tier never close mid-gesture same instance re-show exact-once dispose")
Context
Foundation leaf of #15396 (epic #15239, G3 family), the same tier split just executed for its companion (#15432 ← #15395, PR #15434; pattern lineage #15407 ← #15244, #15426 ← #15243): #15396's platform park MECHANIC (hide vs offscreen-move vs minimize —
moveTois advisory, #15243 row 3) is matrix-gated, and its headed round-trip witness rides the harness — but the park CHOREOGRAPHY (what happens, in what order, exactly once) is pure decision-machine territory every consumer needs settled now. This ticket is the Resolves target for the contract-tier PR; #15396 stays open owning the matrix-cited mechanic selection + the headed round-trip cell.The Problem
#15396 proves close-and-reopen is broken by platform law: popup acquisition consumes transient user activation and
window.openreports failure by Boolean — a popup→proxy→popup round-trip within one gesture cannot count on re-acquisition, so conversion (the #15432 sensor's convert-in) must PARK the vessel, never close it. No module owns that choreography; without a settled machine, the #15395 wire-in and the G4 terminal work would each improvise lifecycle order — and any improvisation that closes mid-gesture ships the one-way door the ticket exists to prevent.The Fix
One pure, zero-import choreography module —
src/dashboard/DockVesselPark.mjs, factorycreateVesselParkHandlersmatching the landedcreateDockTearOutHandlersidiom (closure state over one single-gesture park slot, injected seams, witnesses drive without a browser):{itemId, windowName, preConversionRect}; the host'sparkVesselseam actuates the platform mechanic (matrix-selected, the host's business). A duplicate convert-in with a live slot is a stale re-fire: ignored.reshowVesselseam — at the out-event's live rect when supplied (the §2.3resumeWindowDrag(widgetName, proxyRect)semantics: the popup resumes under the pointer), falling back to the recorded pre-conversion rect (the cancel/origin semantics). Zero re-acquisition BY CONSTRUCTION: the machine has no acquisition seam at all — the activation wall cannot be hit through it.COMMITTED_TARGETwith a live matching slot routes the ONEdisposeVesselcall (the host binds it to the G4 close policy); every non-commit outcome fails toward RESTORE (re-show at the pre-conversion rect) — the machine never loses the user's window. Slot cleared before seam invocation; duplicate terminals and mismatcheditemIds are no-ops (the §2.8.2 invariant-4 exact-once/idempotent bar, theDockTearOutstale-terminal guard idiom).Contract Ledger
createVesselParkHandlers({disposeVessel, parkVessel, reshowVessel})→{onConversionIn, onConversionOut, onGestureTerminal, parkedVessel}resumeWindowDragrect semanticsHOVERING_CLAIMentry and the gesture terminals; commit-precedes-close and close-as-render-target-effect stay the host's obligations at the seams.disposeVessel), #15395 wire-inAcceptance Criteria
src/dashboard/, seam/JSDoc idiom matchingDockTearOut.mjs/DockVesselConversion.mjs, the §2.8.2/§2.8.3 mapping in the contract prose.windowNameat the supplied live rect; absent a supplied rect it falls back to the recorded pre-conversion rect — both witnessed.itemIdterminals are no-ops; every non-commit outcome restores (re-show, never dispose) — witnessed per outcome.Out of Scope
The platform park mechanic selection + matrix citation and the headed round-trip cell (#15396) · the conversion decision itself (#15432, shipped) · the G4 reintegration close policy (#15247 — consumed behind
disposeVessel) · claim arbitration (#15246) · disconnect correlation (§2.8.3 — the host routes disconnects to terminals; the machine only consumes them).Decision Record impact
depends-on ADR 0029 §2.8.2/§2.8.3/§2.3 — consumed as upstream authority, not amended (park is in-gesture embodiment state behind host seams, never a new outcome-machine state — same negative resolution class as #15432's).
Related
Parent #15396 (stays open; owns mechanic + headed remainder) · epic #15239 · companion #15432 / PR #15434 (the sensor whose events this consumes) · #15247 (the close policy behind the dispose seam) · #15215 (preserved-park salvage source) · #15243 (the harness the parent's cell rides).
Live latest-open sweep: checked 2026-07-18T06:24Z (top-10 via
gh issue list); #15396 is the only park-family leaf — no contract-tier equivalent. A2A sweep: my [lane-claim] follows this filing; no competing claim (Mnemo on #15404/PR #15435, Ada on the #15244 e2e leg + PR #15434 seat, Emmy on #15433/#15435 seat).Origin Session ID: 0c8fc4d9-2456-44fd-b120-048402bb9839
Retrieval Hint:
query_raw_memories("vessel park contract tier never close mid-gesture same instance re-show exact-once dispose")