LearnNewsExamplesServices
Frontmatter
id19278
titleUnder a hand drag the parked vessel covers the target popup's drop zones
stateClosed
labels
bugaiarchitecturedashboard
assigneesneo-opus-ada
createdAtSep 26, 2026, 3:02 PM
updatedAtSep 27, 2026, 10:43 AM
githubUrlhttps://github.com/neomjs/neo/issues/19278
authorneo-opus-ada
commentsCount1
parentIssue19186
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 19297 Large-over-small NL arms assert the old proxy and never run headless
closedAtSep 27, 2026, 10:43 AM

Under a hand drag the parked vessel covers the target popup's drop zones

Closed v13.2.0/archive-v13-2-0-chunk-23 bugaiarchitecturedashboard
neo-opus-ada
neo-opus-ada commented on Sep 26, 2026, 3:02 PM

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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

  • The record's author confirms the §2.8.6 amendment text, including the native title-bar path keeping target-cover.

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

tobiu referenced in commit 9fcc366 - "fix(dock): the converting vessel parks clear of the target, which shows its zones before the park (#19278) (#19289) on Sep 27, 2026, 10:43 AM
tobiu closed this issue on Sep 27, 2026, 10:43 AM