Context
The operator's hand test on his own display (screen recording, 2026-09-26): a tab torn out and carried over the Metrics popup converts, and the pane leaves the vessel, but the vessel stays on top of the target. The target's drop zones and its target-side proxy render under it, and the release commits the vessel as its own popup. The frame-by-frame reading and the fork are on #19186 ("Operator recording, 2026-09-26 11:56").
The ADR 0029 §2.8.6 record's author ruled there at 10:11Z: option 1. The vessel parks clear of the target, inside the visible work area, with zones-before-park as the belt, in one PR, accepted on a hand receipt from the operator's display. This is that PR's ticket.
No automated witness reproduces the failure: the headed runs grant or emulate focus (every receipt reads refocused: true), and none captures the compositor. The mechanism below is read from the recording. The operator's lastVesselParkReceipt is still wanted (#19186).
The Problem
§2.8.6 parks the vessel by z-order. It moves the vessel onto the target's frame origin and raises the target with focus(), checked by a document.hasFocus() poll. Under a real OS mouse drag, macOS Chrome does not raise the target on focus(). The poll still admits, so the conversion goes ahead with every affordance it produces hidden under the vessel. A better raise is not available: no raise primitive works under a drag, and the record forbids re-acquisition through window.open('', name).
A second gap: while the park is pending or refused, the target receives no frame at all, so the hand sees no zones until the park admits.
The Architectural Reality
- Two park bodies run the same focus → move → refocus sequence:
src/dashboard/dock/window/NativeVesselTransaction.mjs, the engine default (effectsFor, park): windowNativeFocus, an optional windowNativeResizeTo, DragDrop.parkWindowDrag to the target's origin, then a refocus that compensates when refused. Demo B parks through it.
apps/workstation/view/VesselWorkspace.mjs#parkTearOutVessel, the Workstation's own. The pointer path (nativeTitlebar: false) parks through DragDrop.parkWindowDrag with parkSize and restoreRect; the native title-bar path moves through windowNativeMoveTo.
- The usable display bounds already reach the App worker.
Main#getWindowData() returns screen.availLeft/availTop/availWidth/availHeight, and Placement.resolvePosition clamps a popup into them, which is the precedent for a pure static.
src/manager/DragCoordinator.mjs#onDragMove sends the target a transition-owned frame only when transition.engage && transition.commitEligible && claimed.zone. TabSortZone#resolveRemoteDragTransition answers engage: false while record.transitioning, so a pending park reaches no target.
- Readers of
lastVesselParkReceipt.refocused: test/playwright/unit/apps/workstation/Workspace.spec.mjs, test/playwright/unit/examples/dashboard/crossWindow/DemoBWorkspace.spec.mjs and test/playwright/e2e/dashboard/DemoBCrossWindowDragNL.spec.mjs.
- #19248 (PR #19254) makes the target-side proxy the tab header and keeps the pane in the vessel until the drop. Once the vessel parks clear, nothing but the vessel's own frame covers the target.
The Fix
This implements the amendment text drafted on #19186 ("§2.8.6 amendment text") for the pointer path:
- Park position. A static on
NativeVesselTransaction (which owns the park, and whose statics the Workstation's park already calls) answers the parked frame's origin. It picks the corner of the target display's work area whose frame overlaps the target's inner rect least (the farthest among equals), the whole frame inside the work area, and reports whether the frame is clear of the target's inner rect. A frame larger than the work area, or a target without extent, refuses before any move. (Corrected after review RA-1, Euclid on PR #19289: this line first said "clamps", which could not keep the whole frame inside.) Both park bodies use it; the display bounds come from Main#getWindowData for the target window, since manager.Window keeps none. The shrink stays as today and runs first.
- No focus step. The pointer path drops both focus calls and the
hasFocus() poll. The receipt records cleared instead of focused / refocused, and a refused move still compensates.
- Zones before park. The source marks a frame whose claim stands while the park is pending or refused with
preview: true. The coordinator sends it to onRemoteDragMove with embodyProxy: false, and it stays commit-ineligible, so the target renders its zones from the claim. The header proxy still embodies only after the park admits.
- ADR 0029 §2.8.6 carries the amendment.
Fix restated 2026-09-26 at intake: the static's owner (NativeVesselTransaction, not beside Placement) and the explicit preview mark came out of the prescription check.
Contract Ledger
| Target Surface |
Source of Authority |
Behavior |
Fallback / Edge |
Docs |
Evidence |
NativeVesselTransaction.resolveClearPark({frame, screen, target}) (new) |
#19186 ruling |
{cleared, x, y}: the origin of the work-area corner whose frame overlaps the target's inner rect least (farthest among equals), the whole frame inside the work area |
Non-finite or non-positive input, a target without extent, or a frame larger than the work area → null, and the park refuses with refusedAt: 'screen' before any move |
JSDoc |
AC-1 |
lastVesselParkReceipt (Workstation pointer path and engine default) |
same |
cleared: Boolean replaces focused / refocused; requested is the corner |
Native title-bar receipts unchanged |
JSDoc |
AC-2, AC-5 |
TabSortZone#resolveRemoteDragTransition record: preview: true (new) |
same |
Present while the pointer claims this target and the park has not admitted (pending or refused); absent otherwise |
Absent → no pre-park frame, as today |
JSDoc |
AC-3 |
DragCoordinator pre-park frame |
same |
onRemoteDragMove({embodyProxy: false}) for a preview record with a standing claim; activeTargetCommitEligible stays false, so a drop leaves |
No claim or no preview → no frame, as today |
inline comment |
AC-3 |
Acceptance Criteria
- AC-1 (unit, red-first): the park-position static answers the corner farthest from the target, the whole frame inside the work area, with
cleared: true whenever some corner is clear. A target spanning the work area yields cleared: false. Missing bounds, a target without extent, or a frame larger than the work area yield null.
- AC-2 (unit): on the pointer path neither park body calls a focus route. The vessel moves to the static's origin, the receipt carries
cleared, and a refused move compensates as today.
- AC-3 (unit): a transition-owned frame with a standing claim and a pending park reaches the target with
embodyProxy: false, and a drop on that frame commits nothing.
- AC-4 (docs): ADR 0029 §2.8.6 carries the amendment from #19186. (Restated 2026-09-26: the record's author, @neo-fable-clio, is away until Friday; her confirmation of the text moves to Post-Merge Validation.)
- AC-5 (hand, on the operator's display, at the PR head; carries #19248's AC-3): a tab torn out over a popup parks clear of it. The target shows its drop zones and the tab-header-sized proxy, whose bounding box is about the source tab button's, and the drop commits the pane. Receipt:
lastVesselParkReceipt with cleared, plus the operator's confirmation. (Delivered: four operator hand runs on 2026-09-26, the last at cea0d47e10 with cleared: true and the drop committed; the operator confirmed the visual half on 2026-09-27.)
Post-Merge Validation
Out of Scope
- The native title-bar path. It parks only after the OS releases the drag, when
focus() raises, and the operator called it good as it is. It keeps target-cover unless the record's author rules otherwise.
- The bounded
disposeVessel retry, which the ruling ships alone (#19186).
Related
#19186 is the parent: the recording, the fork, the ruling and the amendment draft. #19248 / PR #19254 is the header proxy; AC-5 carries its AC-3. #19243 / PR #19244 fixed a still pointer after the park. #16117 introduced the target-cover binding this amends. ADR 0029 §2.8.6.
Live latest-open sweep: the latest 20 open issues at 2026-09-26T13:00:54Z, rechecked at 13:02:33Z, hold no equivalent. A2A in-flight sweep (the last 30 messages up to 13:00Z) found no park claim; my 10:43Z note to @neo-fable said I would file and build this. The Memory Core sweep surfaced @neo-fable's 10:04Z frame reading and no prior leaf. Own-assignment sweep: #19248 is the neighbour, not a duplicate.
Retrieval Hint: query_raw_memories("parked vessel stays on top of the target popup under a hand drag, target-clear park")
Origin Session ID: 1b945fcf-1142-475f-8007-ac18d51c069a
Context
The operator's hand test on his own display (screen recording, 2026-09-26): a tab torn out and carried over the Metrics popup converts, and the pane leaves the vessel, but the vessel stays on top of the target. The target's drop zones and its target-side proxy render under it, and the release commits the vessel as its own popup. The frame-by-frame reading and the fork are on #19186 ("Operator recording, 2026-09-26 11:56").
The ADR 0029 §2.8.6 record's author ruled there at 10:11Z: option 1. The vessel parks clear of the target, inside the visible work area, with zones-before-park as the belt, in one PR, accepted on a hand receipt from the operator's display. This is that PR's ticket.
No automated witness reproduces the failure: the headed runs grant or emulate focus (every receipt reads
refocused: true), and none captures the compositor. The mechanism below is read from the recording. The operator'slastVesselParkReceiptis still wanted (#19186).The Problem
§2.8.6 parks the vessel by z-order. It moves the vessel onto the target's frame origin and raises the target with
focus(), checked by adocument.hasFocus()poll. Under a real OS mouse drag, macOS Chrome does not raise the target onfocus(). The poll still admits, so the conversion goes ahead with every affordance it produces hidden under the vessel. A better raise is not available: no raise primitive works under a drag, and the record forbids re-acquisition throughwindow.open('', name).A second gap: while the park is pending or refused, the target receives no frame at all, so the hand sees no zones until the park admits.
The Architectural Reality
src/dashboard/dock/window/NativeVesselTransaction.mjs, the engine default (effectsFor, park):windowNativeFocus, an optionalwindowNativeResizeTo,DragDrop.parkWindowDragto the target's origin, then a refocus that compensates when refused. Demo B parks through it.apps/workstation/view/VesselWorkspace.mjs#parkTearOutVessel, the Workstation's own. The pointer path (nativeTitlebar: false) parks throughDragDrop.parkWindowDragwithparkSizeandrestoreRect; the native title-bar path moves throughwindowNativeMoveTo.Main#getWindowData()returnsscreen.availLeft/availTop/availWidth/availHeight, andPlacement.resolvePositionclamps a popup into them, which is the precedent for a pure static.src/manager/DragCoordinator.mjs#onDragMovesends the target a transition-owned frame only whentransition.engage && transition.commitEligible && claimed.zone.TabSortZone#resolveRemoteDragTransitionanswersengage: falsewhilerecord.transitioning, so a pending park reaches no target.lastVesselParkReceipt.refocused:test/playwright/unit/apps/workstation/Workspace.spec.mjs,test/playwright/unit/examples/dashboard/crossWindow/DemoBWorkspace.spec.mjsandtest/playwright/e2e/dashboard/DemoBCrossWindowDragNL.spec.mjs.The Fix
This implements the amendment text drafted on #19186 ("§2.8.6 amendment text") for the pointer path:
NativeVesselTransaction(which owns the park, and whose statics the Workstation's park already calls) answers the parked frame's origin. It picks the corner of the target display's work area whose frame overlaps the target's inner rect least (the farthest among equals), the whole frame inside the work area, and reports whether the frame is clear of the target's inner rect. A frame larger than the work area, or a target without extent, refuses before any move. (Corrected after review RA-1, Euclid on PR #19289: this line first said "clamps", which could not keep the whole frame inside.) Both park bodies use it; the display bounds come fromMain#getWindowDatafor the target window, sincemanager.Windowkeeps none. The shrink stays as today and runs first.hasFocus()poll. The receipt recordsclearedinstead offocused/refocused, and a refused move still compensates.preview: true. The coordinator sends it toonRemoteDragMovewithembodyProxy: false, and it stays commit-ineligible, so the target renders its zones from the claim. The header proxy still embodies only after the park admits.Fix restated 2026-09-26 at intake: the static's owner (
NativeVesselTransaction, not besidePlacement) and the explicitpreviewmark came out of the prescription check.Contract Ledger
NativeVesselTransaction.resolveClearPark({frame, screen, target})(new){cleared, x, y}: the origin of the work-area corner whose frame overlaps the target's inner rect least (farthest among equals), the whole frame inside the work areanull, and the park refuses withrefusedAt: 'screen'before any movelastVesselParkReceipt(Workstation pointer path and engine default)cleared: Booleanreplacesfocused/refocused;requestedis the cornerTabSortZone#resolveRemoteDragTransitionrecord:preview: true(new)DragCoordinatorpre-park frameonRemoteDragMove({embodyProxy: false})for apreviewrecord with a standing claim;activeTargetCommitEligiblestays false, so a drop leavespreview→ no frame, as todayAcceptance Criteria
cleared: truewhenever some corner is clear. A target spanning the work area yieldscleared: false. Missing bounds, a target without extent, or a frame larger than the work area yieldnull.cleared, and a refused move compensates as today.embodyProxy: false, and a drop on that frame commits nothing.lastVesselParkReceiptwithcleared, plus the operator's confirmation. (Delivered: four operator hand runs on 2026-09-26, the last atcea0d47e10withcleared: trueand the drop committed; the operator confirmed the visual half on 2026-09-27.)Post-Merge Validation
Out of Scope
focus()raises, and the operator called it good as it is. It keeps target-cover unless the record's author rules otherwise.disposeVesselretry, which the ruling ships alone (#19186).Related
#19186 is the parent: the recording, the fork, the ruling and the amendment draft. #19248 / PR #19254 is the header proxy; AC-5 carries its AC-3. #19243 / PR #19244 fixed a still pointer after the park.
#16117introduced the target-cover binding this amends. ADR 0029 §2.8.6.Live latest-open sweep: the latest 20 open issues at 2026-09-26T13:00:54Z, rechecked at 13:02:33Z, hold no equivalent. A2A in-flight sweep (the last 30 messages up to 13:00Z) found no park claim; my 10:43Z note to @neo-fable said I would file and build this. The Memory Core sweep surfaced @neo-fable's 10:04Z frame reading and no prior leaf. Own-assignment sweep: #19248 is the neighbour, not a duplicate.
Retrieval Hint:
query_raw_memories("parked vessel stays on top of the target popup under a hand drag, target-clear park")Origin Session ID: 1b945fcf-1142-475f-8007-ac18d51c069a