Context
Reported by @tobiu 2026-09-01 after pulling dev@2da8389d4c and hand-testing the Workstation: pop out a pane via the header action, drag the popup by its OS titlebar over the main window — the indicator arrows render, but "ALL my tried drops did not re-integrate." This is the L4 hand check that #18029 (PR #18040) and #18047 (PR #18051) deferred to a human, and it fails where both rigs passed.
Read live through the Neural Link on the operator's Workstation (session 15926019…, window 73d7ff8c…) minutes after the attempts — the app's own receipts, not a reconstruction:
DragCoordinator.claimTrace: gestures 20–26, every entry claimed workstation-main with intersects: true, accepts: true for the main window. The hover half works: each drop produced a valid candidate.
Workspace.lastVesselParkReceipt: authority.source* all true, focused: false, and no moved key — the park returned at the focus step before ever trying to move the popup.
lastCrossWindowTransfer: null, tearOutPanes: {} afterwards (the panes came home only through the close path, as in #18029's report).
- Coordinator constants on that instance: dwell 450 ms, settle 250 ms, disposition retry 250 ms, handoff 180 ms.
Live latest-open sweep at 2026-09-01T22:25Z (newest #18052) plus keyword sweeps (park focus native titlebar, reintegrate popup release, native drop OS drag focus): no equivalent. #18029 (closed) covered the poll arming; #17578 (open) is the pointer path.
The Problem
The native-titlebar terminal is designed as dwell (450 ms of the same target) + settle (250 ms without a geometry update) → commit. A human who stops moving the popup over a target while still holding the titlebar satisfies both. The commit then parks the source popup, and the Workstation's park begins by asking the target window to take focus. During a live OS window drag the dragged window holds focus and the window server owns its position, so that request cannot succeed; the park reports focused: false and returns false; the strict native route reads a refused park as the end of the gesture and clears the candidate. When the user finally releases, nothing re-arms: the release produces no DOM event, and a stationary popup publishes no geometry. So the only reintegration a human can reach is the close path — exactly the report.
The rigs never saw it because CDP Browser.setWindowBounds moves a window nobody is holding: focus and moveTo both succeed, so the park always parked.
The Architectural Reality
src/manager/DragCoordinator.mjs:320-341 — commitNativeWindowDrop: candidate.phase = 'parking', suspended = await sourceSortZone.suspendWindowDrag(widgetName, candidate), then if (embodyNativeHover === true && suspended !== true) { clearNativeWindowDropCandidate; endNativeGesture; return } — a refused park is terminal, with no retry.
apps/workstation/view/Workspace.mjs:3116-3120 — parkTearOutVessel: focused = await focusTarget() === true; if (!focused) return false, then windowNativeMoveTo(...). For a main-window target focusTarget() is Neo.Main.windowFocus({windowId: entry.windowId}): the popup's main thread asks its opener to take focus.
src/Main.mjs:748-770 — #focusWindow polls win.document.hasFocus() six times at 50 ms; under an OS drag of another window it stays false.
- The disposition half already has the retry the park lacks:
settleNativeWindowDisposition retries close/restore with nativeWindowDispositionRetryMs backoff while the candidate is retained (DragCoordinator.mjs ~:262-275).
- There is no observable "mouse released the titlebar" on this path. A park that succeeds is the release signal: focus and
moveTo start succeeding the moment the window server lets go.
The Fix
Make the refused park a retry, not a terminal, on the native route:
DragCoordinator.commitNativeWindowDrop: when embodyNativeHover === true && suspended !== true, keep the candidate, set phase = 'park-retry', and reschedule commitNativeWindowDrop after nativeWindowDispositionRetryMs with the same backoff shape the disposition retry uses; bound it (attempt count, or a total window such as 8 s), and abandon it on the existing exits — a new geometry update for the source (the user moved on: the normal onWindowPositionChange path replaces the candidate), source disconnect, or a different claimed target.
- Keep the park's focus precondition — after release it succeeds and the embodiment stays visible — but record the attempt count and the last refusal reason in
lastVesselParkReceipt so a stalled retry is readable.
park-retry is deliberately NOT added to the onWindowPositionChange guard (:1043-1049): that guard shields phases whose geometry the app itself publishes (parking, embodied, settling-*). A geometry update during park-retry means the user moved on, so the ordinary path replaces the candidate and clears the pending retry with it.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
Neo.manager.DragCoordinator#commitNativeWindowDrop |
src/manager/DragCoordinator.mjs:283 |
a refused strict park retries with disposition-style backoff until it parks, the source moves/disconnects, or the bound expires |
bound expired → today's behaviour (clearNativeWindowDropCandidate + endNativeGesture) |
method JSDoc |
unit witness |
Neo.manager.DragCoordinator native phase set |
onWindowPositionChange guard :1043-1049 |
park-retry is a retained generation that geometry REPLACES (not shielded); only app-published phases stay shielded |
— |
JSDoc |
unit witness |
Workstation.view.Workspace#parkTearOutVessel receipt |
apps/workstation/view/Workspace.mjs:3059 |
lastVesselParkReceipt gains attempt and the refusing step |
unchanged shape otherwise |
JSDoc |
unit witness |
Decision Record impact
aligned-with ADR 0029 §2.8 (native-titlebar reintegration: the dwell/settle intent contract stands; only the terminal's tolerance to a held OS drag changes). No amendment.
Acceptance Criteria
Out of Scope
- The native path's drop grammar: the indicator tier evaluates every zone under the popup's centre while
renderCrossWindowPreview offers only the stored-home node, so arrows render with no region overlay behind them and a drop lands at the stored home regardless of the arrow. Real, reported in the same test, and its own leaf (UX call on whether the native path adopts the pointer path's full zone grammar).
- The pointer-path coordinator gap (#17578).
- Multi-display behaviour (D#15204 audit).
Avoided Traps
- Dropping the focus precondition for native parks:
moveTo fails the same way while the OS drags the window, so the terminal still needs a release detector — the retry is that detector.
- Treating the CDP rigs as L4: they never hold an OS drag, so they cannot exercise this branch; the unit witness owns the mechanics and the human owns the gesture.
- Unbounded retries: a popup parked over a target for minutes must not keep a candidate alive forever; the bound and the geometry-change exit keep the machine honest.
Related
Refs #15239 (multi-window choreography epic) · #18029 / PR #18040 (poll arming) · #18047 / PR #18051 (popup→popup witness) · #17578 (pointer path)
Origin Session ID: 93fd8de9-7231-4979-b906-c050bcfc25a4
Retrieval Hint: query_raw_memories("native titlebar drop park focus refused OS drag holds popup retry release detector lastVesselParkReceipt focused false")
Context
Reported by @tobiu 2026-09-01 after pulling
dev@2da8389d4cand hand-testing the Workstation: pop out a pane via the header action, drag the popup by its OS titlebar over the main window — the indicator arrows render, but "ALL my tried drops did not re-integrate." This is the L4 hand check that #18029 (PR #18040) and #18047 (PR #18051) deferred to a human, and it fails where both rigs passed.Read live through the Neural Link on the operator's Workstation (session
15926019…, window73d7ff8c…) minutes after the attempts — the app's own receipts, not a reconstruction:DragCoordinator.claimTrace: gestures 20–26, every entryclaimed workstation-mainwithintersects: true, accepts: truefor the main window. The hover half works: each drop produced a valid candidate.Workspace.lastVesselParkReceipt:authority.source*alltrue,focused: false, and nomovedkey — the park returned at the focus step before ever trying to move the popup.lastCrossWindowTransfer: null,tearOutPanes: {}afterwards (the panes came home only through the close path, as in #18029's report).Live latest-open sweep at 2026-09-01T22:25Z (newest #18052) plus keyword sweeps (
park focus native titlebar,reintegrate popup release,native drop OS drag focus): no equivalent. #18029 (closed) covered the poll arming; #17578 (open) is the pointer path.The Problem
The native-titlebar terminal is designed as dwell (450 ms of the same target) + settle (250 ms without a geometry update) → commit. A human who stops moving the popup over a target while still holding the titlebar satisfies both. The commit then parks the source popup, and the Workstation's park begins by asking the target window to take focus. During a live OS window drag the dragged window holds focus and the window server owns its position, so that request cannot succeed; the park reports
focused: falseand returnsfalse; the strict native route reads a refused park as the end of the gesture and clears the candidate. When the user finally releases, nothing re-arms: the release produces no DOM event, and a stationary popup publishes no geometry. So the only reintegration a human can reach is the close path — exactly the report.The rigs never saw it because CDP
Browser.setWindowBoundsmoves a window nobody is holding: focus andmoveToboth succeed, so the park always parked.The Architectural Reality
src/manager/DragCoordinator.mjs:320-341—commitNativeWindowDrop:candidate.phase = 'parking',suspended = await sourceSortZone.suspendWindowDrag(widgetName, candidate), thenif (embodyNativeHover === true && suspended !== true) { clearNativeWindowDropCandidate; endNativeGesture; return }— a refused park is terminal, with no retry.apps/workstation/view/Workspace.mjs:3116-3120—parkTearOutVessel:focused = await focusTarget() === true; if (!focused) return false, thenwindowNativeMoveTo(...). For a main-window targetfocusTarget()isNeo.Main.windowFocus({windowId: entry.windowId}): the popup's main thread asks its opener to take focus.src/Main.mjs:748-770—#focusWindowpollswin.document.hasFocus()six times at 50 ms; under an OS drag of another window it staysfalse.settleNativeWindowDispositionretries close/restore withnativeWindowDispositionRetryMsbackoff while the candidate is retained (DragCoordinator.mjs~:262-275).moveTostart succeeding the moment the window server lets go.The Fix
Make the refused park a retry, not a terminal, on the native route:
DragCoordinator.commitNativeWindowDrop: whenembodyNativeHover === true && suspended !== true, keep the candidate, setphase = 'park-retry', and reschedulecommitNativeWindowDropafternativeWindowDispositionRetryMswith the same backoff shape the disposition retry uses; bound it (attempt count, or a total window such as 8 s), and abandon it on the existing exits — a new geometry update for the source (the user moved on: the normalonWindowPositionChangepath replaces the candidate), source disconnect, or a different claimed target.lastVesselParkReceiptso a stalled retry is readable.park-retryis deliberately NOT added to theonWindowPositionChangeguard (:1043-1049): that guard shields phases whose geometry the app itself publishes (parking,embodied,settling-*). A geometry update duringpark-retrymeans the user moved on, so the ordinary path replaces the candidate and clears the pending retry with it.Contract Ledger
Neo.manager.DragCoordinator#commitNativeWindowDropsrc/manager/DragCoordinator.mjs:283clearNativeWindowDropCandidate+endNativeGesture)Neo.manager.DragCoordinatornative phase setonWindowPositionChangeguard:1043-1049park-retryis a retained generation that geometry REPLACES (not shielded); only app-published phases stay shieldedWorkstation.view.Workspace#parkTearOutVesselreceiptapps/workstation/view/Workspace.mjs:3059lastVesselParkReceiptgainsattemptand the refusing stepDecision Record impact
aligned-with ADR 0029 §2.8 (native-titlebar reintegration: the dwell/settle intent contract stands; only the terminal's tolerance to a held OS drag changes). No amendment.
Acceptance Criteria
DragCoordinator: a strict native candidate whosesuspendWindowDragrefuses N times and then admits commits the drop; the refusals are retried with the disposition backoff and never end the gesture.park-retryreplaces the candidate and clears the pending retry timer, so a moved popup never gets a stale park attempt.WorkstationNativeTitlebarDragNLandWorkstationNativePopupOverPopupNLstay green (the park succeeds on the first attempt in the rig, so the retry path is dormant there).refusedAt: focus / move / refocus); the attempt count lives on the coordinator candidate (parkAttempts).Out of Scope
renderCrossWindowPreviewoffers only the stored-home node, so arrows render with no region overlay behind them and a drop lands at the stored home regardless of the arrow. Real, reported in the same test, and its own leaf (UX call on whether the native path adopts the pointer path's full zone grammar).Avoided Traps
moveTofails the same way while the OS drags the window, so the terminal still needs a release detector — the retry is that detector.Related
Refs #15239 (multi-window choreography epic) · #18029 / PR #18040 (poll arming) · #18047 / PR #18051 (popup→popup witness) · #17578 (pointer path)
Origin Session ID: 93fd8de9-7231-4979-b906-c050bcfc25a4
Retrieval Hint:
query_raw_memories("native titlebar drop park focus refused OS drag holds popup retry release detector lastVesselParkReceipt focused false")