Context
#18029 (PR #18040) repaired the native-titlebar popup drag against the main window and left one e2e arm behind, WorkstationNativeTitlebarDragNL. The multi-window continuation — a torn-out popup moved by its OS titlebar onto another torn-out popup — has no witness at all: every popup-over-popup spec (WorkstationHumanPopupOverlapNL) drives the pointer path and calls publishGeometry() by hand. A ten-run diagnostic probe on dev@ff4ccd512b proved the gesture works end to end; the receipts and the rig facts are on Epic #15239 (comment). The operator asked for more e2e coverage of exactly this surface (2026-09-01).
Live latest-open sweep at 2026-09-01T21:23Z (newest #18044) plus keyword sweeps (native titlebar vessel, popup over popup native, titlebar drag transfer vessel): no equivalent. Nearest: #17578 (open, pointer-path coordinator gap), #16123 / #16497 (closed native-return tickets), #18029 (closed, main-window target only).
The Problem
The class of defect #18029 exposed — a working feature silently broken because nothing exercises its trigger — applies with more force to the multi-window path, which has three more moving parts than the main-window path: the vessel participation's claim (workstation-vessel:<item> stable ids), the vessel-target park (parkTearOutVessel with a non-main target route), and the cross-window transferItem commit into the vessel document. None is witnessed on the native path today, so a regression in any of them would ship green.
The Architectural Reality
- Trigger:
Neo.main.addon.WindowPosition publishes on each movement of the source popup (poll owned by observeMovement, Workstation opt-in since #18040); no pointer event exists on this path.
- Claim:
DragCoordinator#onWindowPositionChange (src/manager/DragCoordinator.mjs:1034) → resolveClaimedTarget over the workstation-cross-window sort group; GestureClaimArbiter keeps hover seniority across moves and settles ties lexicographically, so a popup that first claims main keeps main while it still intersects main.
- Preview:
Workspace#renderCrossWindowPreview for a vessel workspace; ensureCrossWindowPreviewGeometry measures asynchronously and nothing re-renders on resolve — the first update over an unmeasured target renders nothing, the next update renders.
- Terminal: dwell 450 ms / settle 250 ms →
commitNativeWindowDrop → suspendNativeWindowDrag → parkTearOutVessel({nativeTitlebar: true}) (non-main target requires targetRoute.capabilities.focus, granted by Main.nativeWindowCapabilities defaults) → onRemoteDrop → commitCrossWindowTransfer → transferItem into workstation-vessel-tabs:<item> → retireNativeWindowDrag closes the source vessel.
- Rig facts that decide whether a run is evidence: Playwright emulates
window.screen at the viewport size (a popup outside the main window is then off the emulated screen and the app's window.moveTo cannot park it — viewport: null restores the real display); Chrome will not shrink the main window below ~500 px wide, so on an 800×600 stage the target sits below a 300 px-tall main window; the pop-outs must happen before the shrink or the tab headers collapse into the overflow menu; the source must reach the target without pausing over main.
The Fix
Add test/playwright/e2e/workstation/WorkstationNativePopupOverPopupNL.spec.mjs (Neural Link fixture, viewport: null), sibling of WorkstationNativeTitlebarDragNL:
- Stage guard:
test.skip when the real display cannot hold a 760 px main window plus a 240 px target below it, with the reason stated.
- Pop out the target (
metrics) then the source (commits) via the header action; after each, wait for the detach projection's refresh (main Participation id changes) and for all three participations to be registered.
- Shrink the main window to 300 px tall (wait for
manager.Window to publish it), place the target vessel fully on-screen below main, outside the main rect.
- Assert the source realm's poll is armed by config and no
mouseout reaches it; move the source straight onto the target's centre, then nudge twice inside the dwell window.
- Assert, in order: the last claim is
workstation-vessel:metrics; .neo-dock-preview-affordance count in the target popup ≥ 1 while the main window shows 0; lastVesselParkReceipt.parked === true; lastCrossWindowTransfer is transferItem commits → workstation-vessel:metrics with applied: true; the source vessel retires (tearOutPanes.commits null, popup closed); the target vessel document lists both items; the main document no longer lists commits; no page errors.
- Header comment carries the trigger discipline (CDP bounds = window-server stand-in only; no synthetic input; no runtime patching) and the four rig facts.
Decision Record impact
aligned-with ADR 0029 §2.8 (claim protocol, outcome machine, vessel lifecycle) — a witness of landed behaviour, no authority mutation.
Acceptance Criteria
Out of Scope
- Wiring the Neural Link tier into CI (
playwright.config.e2e.mjs gates it on the external Brain runtime; #17844 owns battery runs).
- A preview on the first hover over an unmeasured target (a design gap in
ensureCrossWindowPreviewGeometry, benign under continuous movement; would be its own leaf).
- Multi-display behaviour (
window.moveTo is unpermissioned across displays, per the D#15204 audit).
Avoided Traps
- Emulating the screen with
test.use({screen}) — it did not take effect in this rig and would hide the window.moveTo clamp; viewport: null is the honest stage.
- Walking the source through the main window in steps — the arbiter's seniority makes main win and a pause there reintegrates into main (the feature, not a bug); the witness moves once, directly.
- Reading the preview after a single update — the measurement is asynchronous; the witness reads after the second update and inside the dwell window.
- Any synthetic
mouseout or CDP mouse input — it would test the trigger the human gesture never produces (#18029's lesson).
Related
Refs #15239 (multi-window choreography epic, receipts comment) · #18029 / PR #18040 (main-window native path) · #17836 (DockLayouts rewrite) · #15252 (five-beat film) · #17578 (pointer-path coordinator gap)
Origin Session ID: 93fd8de9-7231-4979-b906-c050bcfc25a4
Retrieval Hint: query_raw_memories("native titlebar popup over popup vessel target transferItem probe viewport null 800x600 stage arbiter seniority")
Context
#18029 (PR #18040) repaired the native-titlebar popup drag against the main window and left one e2e arm behind,
WorkstationNativeTitlebarDragNL. The multi-window continuation — a torn-out popup moved by its OS titlebar onto another torn-out popup — has no witness at all: every popup-over-popup spec (WorkstationHumanPopupOverlapNL) drives the pointer path and callspublishGeometry()by hand. A ten-run diagnostic probe ondev@ff4ccd512bproved the gesture works end to end; the receipts and the rig facts are on Epic #15239 (comment). The operator asked for more e2e coverage of exactly this surface (2026-09-01).Live latest-open sweep at 2026-09-01T21:23Z (newest #18044) plus keyword sweeps (
native titlebar vessel,popup over popup native,titlebar drag transfer vessel): no equivalent. Nearest: #17578 (open, pointer-path coordinator gap), #16123 / #16497 (closed native-return tickets), #18029 (closed, main-window target only).The Problem
The class of defect #18029 exposed — a working feature silently broken because nothing exercises its trigger — applies with more force to the multi-window path, which has three more moving parts than the main-window path: the vessel participation's claim (
workstation-vessel:<item>stable ids), the vessel-target park (parkTearOutVesselwith a non-main target route), and the cross-windowtransferItemcommit into the vessel document. None is witnessed on the native path today, so a regression in any of them would ship green.The Architectural Reality
Neo.main.addon.WindowPositionpublishes on each movement of the source popup (poll owned byobserveMovement, Workstation opt-in since #18040); no pointer event exists on this path.DragCoordinator#onWindowPositionChange(src/manager/DragCoordinator.mjs:1034) →resolveClaimedTargetover theworkstation-cross-windowsort group;GestureClaimArbiterkeeps hover seniority across moves and settles ties lexicographically, so a popup that first claims main keeps main while it still intersects main.Workspace#renderCrossWindowPreviewfor a vessel workspace;ensureCrossWindowPreviewGeometrymeasures asynchronously and nothing re-renders on resolve — the first update over an unmeasured target renders nothing, the next update renders.commitNativeWindowDrop→suspendNativeWindowDrag→parkTearOutVessel({nativeTitlebar: true})(non-main target requirestargetRoute.capabilities.focus, granted byMain.nativeWindowCapabilitiesdefaults) →onRemoteDrop→commitCrossWindowTransfer→transferItemintoworkstation-vessel-tabs:<item>→retireNativeWindowDragcloses the source vessel.window.screenat the viewport size (a popup outside the main window is then off the emulated screen and the app'swindow.moveTocannot park it —viewport: nullrestores the real display); Chrome will not shrink the main window below ~500 px wide, so on an 800×600 stage the target sits below a 300 px-tall main window; the pop-outs must happen before the shrink or the tab headers collapse into the overflow menu; the source must reach the target without pausing over main.The Fix
Add
test/playwright/e2e/workstation/WorkstationNativePopupOverPopupNL.spec.mjs(Neural Link fixture,viewport: null), sibling ofWorkstationNativeTitlebarDragNL:test.skipwhen the real display cannot hold a 760 px main window plus a 240 px target below it, with the reason stated.metrics) then the source (commits) via the header action; after each, wait for the detach projection's refresh (mainParticipationid changes) and for all three participations to be registered.manager.Windowto publish it), place the target vessel fully on-screen below main, outside the main rect.mouseoutreaches it; move the source straight onto the target's centre, then nudge twice inside the dwell window.workstation-vessel:metrics;.neo-dock-preview-affordancecount in the target popup ≥ 1 while the main window shows 0;lastVesselParkReceipt.parked === true;lastCrossWindowTransferistransferItemcommits→workstation-vessel:metricswithapplied: true; the source vessel retires (tearOutPanes.commitsnull, popup closed); the target vessel document lists both items; the main document no longer listscommits; no page errors.Decision Record impact
aligned-with ADR 0029 §2.8 (claim protocol, outcome machine, vessel lifecycle) — a witness of landed behaviour, no authority mutation.
Acceptance Criteria
transferItemcommit, and the source retirement — each on a production receipt, none on a synthetic input.publishGeometry()call reaches either popup realm during the gesture (amouseoutcounter in the source realm is asserted at zero).WorkstationNativeTitlebarDragNLandWorkstationHumanPopupOverlapNLare untouched and stay green where they run.Out of Scope
playwright.config.e2e.mjsgates it on the external Brain runtime; #17844 owns battery runs).ensureCrossWindowPreviewGeometry, benign under continuous movement; would be its own leaf).window.moveTois unpermissioned across displays, per the D#15204 audit).Avoided Traps
test.use({screen})— it did not take effect in this rig and would hide thewindow.moveToclamp;viewport: nullis the honest stage.mouseoutor CDP mouse input — it would test the trigger the human gesture never produces (#18029's lesson).Related
Refs #15239 (multi-window choreography epic, receipts comment) · #18029 / PR #18040 (main-window native path) · #17836 (DockLayouts rewrite) · #15252 (five-beat film) · #17578 (pointer-path coordinator gap)
Origin Session ID: 93fd8de9-7231-4979-b906-c050bcfc25a4
Retrieval Hint:
query_raw_memories("native titlebar popup over popup vessel target transferItem probe viewport null 800x600 stage arbiter seniority")