LearnNewsExamplesServices
Frontmatter
id19247
titleA vessel over a popup that covers the main window re-enters main first
stateClosed
labels
bugaidashboard
assigneesneo-fable
createdAtSep 26, 2026, 11:39 AM
updatedAtSep 26, 2026, 12:04 PM
githubUrlhttps://github.com/neomjs/neo/issues/19247
authorneo-fable
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 26, 2026, 12:04 PM

A vessel over a popup that covers the main window re-enters main first

neo-fable
neo-fable commented on Sep 26, 2026, 11:39 AM

Context

Operator hand test, 2026-09-26 ~09:20Z, on dev with #19242 and #19244 merged and themes rebuilt: a tab torn out and dragged over another popup still does not convert into the inline proxy, no drop zones show, a tab cannot be dropped into a popup this way. Every automated witness (the region probe, the film driver, WorkstationHumanPopupOverlapNL) places the target popup OUTSIDE the main window; on one screen a popup normally lies OVER the main window's dock root.

Reproduced with that placement (headed, real page.mouse drag, CDP focus emulation off; main 1320×920 pinned, target popup 700×500 placed over main's content at +300/+200, the held pointer walked from outside main onto the popup's content at fx 0.15 / fy 0.3; receipts test-results/dock-wow-demo/hand-conversion/2026-09-26/2026-09-26T09-34-36-647Z-hand-real-focus.json): during the walk the vessel window CLOSED (vessel page closed=true) before the popup could claim — the source-root re-entry fired and the vessel morphed back into main; the gesture then continued as an in-window tab drag over a popup lying on main, produced one stray converted: true frame with the native-path receipt "main-window target: nothing to park, the popup retires after the commit", and the TARGET popup closed. The same walk with the target beside main (two runs, focus emulation off and on) converts and parks cleanly. So the failure is not focus, not the admission metric, not the still pointer: it is the re-entry rule.

Measurement update (2026-09-26 09:50Z) — the premise above is NOT reproduced

Two more headed runs, target placed over main BEFORE the tear-out and the held pointer entering through the popup's title-bar strip (receipts strip-dev/ and strip-fix/ under the same folder): on dev the vessel did not morph on the strip — a 480×360 vessel covers too little of main's root for the coverage test (0.18 < 0.6) — and the same on the fix. The first overlap run's "vessel closed" was the designed return over 300 px of EXPOSED main that the walk crossed, and the target popup closing in every over-main run was an instrument artifact: placing a committed popup over main by CDP drives the native title-bar path, whose dwell commits the popup into main. So this ticket's mechanism is a derivation, not a measurement. The fix and its red-first arm stay on a local branch; no PR until the operator's own gesture receipts (park receipt, claim ring) say which admission refuses. Status: premise unverified.

The Problem

Neo.draggable.container.SortZone#checkWindowBoundary (window-drag branch) fires dragBoundaryEntry — the morph back into the source — when the vessel-sized proxy's coverage of the source root rises above reattachThreshold while armed. It reads only the source root's rect. Neo.dashboard.dock.interaction.TabSortZone#checkWindowBoundary suppresses that only while pointerClaimArbiter.resolve() returns a live claim, and a claim needs the pointer inside the target's INNER rect plus acceptsRemoteDrag. A popup covering the source root therefore has a strip — its title bar (67 px on macOS Chrome) and any exposed source root the path crosses — where the pointer is over the popup's frame, no claim exists yet, and the proxy already covers enough of the source root: the re-entry wins and the vessel retires. The hand always crosses that strip; the drivers never did.

Design authority

ADR 0029 §2.8.1: "Hit-claims on stable identity … no claim — fail closed: no preview, no commit" — a claim decides target ownership; nothing in the record makes the source root's rect an authority over frames a target window covers. §2.8.2's machine: DETACHED_MOVING → HOVERING_CLAIM; the re-entry (TabSortZone JSDoc: "the real return-to-source transition") is the source's own outcome and must not fire on a frame the pointer spends over another window. The operator's expected behavior (this morning): the vessel over another popup converts to the tab-header proxy and shows the target's drop zones. Decision Record impact: aligned-with ADR 0029 (no sentence changes).

The Architectural Reality

  • src/draggable/container/SortZone.mjs checkWindowBoundary (~:330-445): the coverage sampler and the armed re-entry.
  • src/dashboard/dock/interaction/TabSortZone.mjs checkWindowBoundary (~:362): the claim-only suppression.
  • src/manager/DragCoordinator.mjs: sortZones (Map: sortGroup → Map: windowId → zone) is the registry of every window that can be a target; getWindowAtExcept(x, y, excluded) already tests Window.items[].outerRect against a point.
  • src/main/addon/DragDrop.mjs (~:576, :640): the window-drag move payload carries screenX / screenY.
  • src/manager/Window.mjs: outerRect is the frame incl. chrome; the vessel window itself has no registered zone in the sort group.

The Fix

TabSortZone#checkWindowBoundary: while isWindowDragging, treat a frame as remote territory not only when a claim resolves but also when the pointer (data.screenX / screenY) lies inside the OUTER rect of any window registered in the zone's sort group other than the source window (the vessel has no registered zone, so it never matches itself) — return true without consulting the base sampler. The popup's content then claims on the next frame the pointer reaches it; its title-bar strip becomes a hold; leaving the popup onto exposed source root still re-enters (the designed return).

Acceptance Criteria

  • AC-1 (unit, red-first): DockTabSortZone.spec.mjs › checkWindowBoundary: with no claim, a pointer inside a registered sibling window's outer rect suppresses the base sampler (no dragBoundaryEntry, no onWindowDragContinue); with the pointer outside every registered window the base sampler runs as before; the source window's own rect never suppresses.
  • AC-2 (headed, one run, real windows): the hand-conversion probe in overlap placement at the fix head — the vessel stays open through the walk, the popup claims, converted: true, parked: true, the preview renders inside the popup; both beside-main runs unchanged.
  • AC-3 (CI): unit/dashboard green.

Out of Scope

  • The claim tie when the source is itself a popup and the target popup lies over main: both main and the target claim, and §2.8.1's lexicographic tiebreak lets workstation-main win over any workstation-vessel:* id although the popup is the visible window. Derived, not measured; noted for the record's author as a §2.8.1 amendment candidate (a nested candidate outranks the enclosing one).
  • The embodiment shape (the target-side proxy must be the tab header, not the window content): the operator's design call, filed separately.

Related

#19241 / PR #19242 (admission), #19243 / PR #19244 (still pointer), #19186 (stand-in over another popup), #16117 (human popup-over-popup witness, placed beside main), ADR 0029 §2.8.1 / §2.8.2.

Live latest-open sweep: latest 20 open issues at 2026-09-26T09:37:50Z, no equivalent. A2A sweep (last 8 at 09:35Z, all read-states): Grace's #17844 teardown class is the only dock claim, disjoint. Memory sweep: today's probes only. Own-assignment sweep: #19245, #19186 — neither covers it.

Origin Session ID: 519ec01f-3aec-4247-a34a-e8741fd4757e Retrieval Hint: "vessel re-entry under a popup covering main, checkWindowBoundary outer rect"

🪢 Mnemosyne (Claude Fable 5.1 · Claude Code) · session 519ec01f-3aec-4247-a34a-e8741fd4757e