LearnNewsExamplesServices
Frontmatter
id19248
titleThe target-side proxy of a converted vessel must be the tab header
stateClosed
labels
bugaiarchitecturedashboard
assigneesneo-opus-ada
createdAtSep 26, 2026, 11:39 AM
updatedAtSep 26, 2026, 8:30 PM
githubUrlhttps://github.com/neomjs/neo/issues/19248
authorneo-fable
commentsCount5
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 26, 2026, 8:30 PM

The target-side proxy of a converted vessel must be the tab header

Closed v13.2.0/archive-v13-2-0-chunk-23 bugaiarchitecturedashboard
neo-fable
neo-fable commented on Sep 26, 2026, 11:39 AM

Context

Operator, 2026-09-26 ~09:3xZ, watching the conversion probe on the capture display: "a popup resolved to a window content as the drag proxy … this is a design fail: it should convert to the tab header. we drag a tab header within the main window. we drag outside and get the popup proxy. now, on entering the popup window, adding a 3rd proxy type would be insane. poor UX." And the rationale: "consistency: it should feel the same like dragging a tab inside a main window. and popups can have a big size and their own dock layout." — a window-sized content proxy covers exactly the dock layout the hand is trying to aim at.

Also observed by the operator: the tab header inside that content proxy renders without the compact styling (taller). The header comparison probe shows a committed popup's dock header identical to main's (33 px toolbar, 32 px button); the tall header is the content proxy's own, rendered outside the dock host's class chain. Retiring the content proxy retires that symptom with it.

The three-phase model, in the operator's words (09:4xZ): "in app => tab header proxy => outside apps popup proxy (this part is good, since a drop keeps the visual state as is). re-entering an app => tab header proxy again." Re-entering MAIN already does this (the boundary re-entry retires the vessel and the in-window tab-header proxy resumes); re-entering a POPUP is what this ticket makes consistent.

The Problem

After the park admits, the target-side embodiment moves the SAME live pane into a DragProxyContainer in the target window (src/dashboard/dock/window/VesselEmbodiment.mjs: "the SAME live pane renders inside one target-window DragProxyContainer"; .workstation-vessel-dragproxy { opacity: .7 }), sized by the engaged record's sourceRect — the vessel's live extent — through DragCoordinator#onDragMove (targetProxyRect takes transitionSourceRect.width / height when the transition owns the frame, the ordinary proxyRect otherwise). That is the third proxy type: the in-window drag shows the tab-header proxy, the void shows the popup, the target shows a window-sized pane.

Design authority

The operator's statement above is the product intent (a UX call, human-owned). ADR 0029 §2.8.6 names the transition ("popup-over-vessel converts to a proxy over the target while the park keeps the real vessel alive") without prescribing the proxy's shape. Decision Record impact: amends ADR 0029 §2.8.6 with one sentence naming the target-side proxy as the tab-header proxy of the in-window drag.

The Architectural Reality

  • src/manager/DragCoordinator.mjs onDragMove (~:1100-1140): targetProxyRect sizing from transitionSourceRect; the ordinary path sizes from proxyRect (during a window drag the source proxyRect is the future vessel, src/main/addon/DragDrop.mjs ~:575, so the tab-header size must come from the zone's item rect).
  • src/dashboard/dock/interaction/TabSortZone.mjs resolveRemoteDragTransition: the engaged record's sourceRect ("the exact live source extent for target-proxy embodiment").
  • src/dashboard/dock/window/VesselEmbodiment.mjs + Participation#defaultStageDragEmbodiment / resolveProxyConfig: the pane move into the target's DragProxyContainer.
  • apps/workstation/view/VesselWorkspace.mjs vesselProxyEmbodiment: the Workstation's binding.
  • The commit path (commitTransfer / transferItem) adopts the pane from the parked vessel; with a header proxy the pane simply stays in the parked vessel until the drop.

The Fix

  1. The engaged record carries the dragged tab's header rect (the zone's itemRects[startIndex] extent) instead of the vessel's extent; the coordinator sizes targetProxyRect from it at localX − offsetX, localY − offsetY like the ordinary proxy.
  2. The target-side embodiment renders a tab-header proxy (the same DragProxyComponent shape the in-window drag uses: the button's vdom clone, dock header classes, compact height) in the target window; the live pane stays in the parked vessel; restore retires the header proxy.
  3. The stand-in placeholder logic that reserves the pane's slot in the vessel is unnecessary for a header proxy — retire what no longer has a reader (Accretion Defense: name the deletion).
  4. ADR 0029 §2.8.6: one sentence.

Acceptance Criteria

  • AC-1 (unit, red-first): the engaged record's sourceRect is the tab header's extent; the coordinator's targetProxyRect equals the ordinary proxy sizing for a transition-owned frame.
  • AC-2 (unit): staging the embodiment creates a header proxy in the target window and leaves the live pane in the vessel; restore removes it; no placeholder stand-in is created.
  • AC-3 → transferred to #19278 AC-5 (hand, on the operator's display), delivered with the target-clear park. Annotated 2026-09-26 by the author: under a hand drag the parked vessel covers the header proxy as it covers the zones (the operator's recording, #19186), so this witness cannot be delivered from PR #19254 alone. Was: (headed, one run) the hand-conversion probe shows a tab-header-sized proxy inside the target (bounding box ≈ the source tab button's), the target's drop zones visible around it, drop commits the pane.
  • AC-4 (docs): ADR 0029 §2.8.6 carries the sentence; .workstation-vessel-dragproxy's comment no longer speaks of the live pane.

Contract Ledger

Claimer's section (@neo-opus-ada, from the review of PR #19254), accepted by the author 2026-09-26. Surfaces are anchored at the PR head 5b4a8095f1.

Target Surface Source of Authority Behavior Fallback / Edge Docs Evidence
TabSortZone#resolveRemoteDragTransition(): the engaged record's proxyRect, from getVesselConversionProxyRect() (new) this ticket {height, offsetX, offsetY, width}: the dragged tab header's measured extent (dragElementRect) and the grab offset inside it null before the drag element is measured. The sensor's vesselConversionSourceRect, the park's anchor, is unchanged JSDoc DockTabSortZone.spec conversion-binding arms
DragCoordinator#onDragMove → onRemoteDragMove: proxyRect sizing and embodyHeader (new) this ticket A transition-owned frame: the target proxy is the header's extent at the pointer minus the grab offset, with embodyHeader: true A non-finite or non-positive extent or offset cancels the conversion before preview. Every other remote drag keeps the ordinary proxy rect and embodyHeader: false inline comment DragCoordinator.spec: "conversion resolver receives one live INNER-viewport frame…"; "an engaged record whose header extent or grab offset is not finite cancels the conversion before preview (#19248)"
DragZone#getDragProxyVdom() (new, public) this ticket An id-free copy of the configured proxy vdom, else of the drag element, wrapped unless useProxyWrapper is off. createDragProxy renders the same content null with neither JSDoc DragZone.spec: "getDragProxyVdom answers an id-free copy of the drag element, wrapped unless useProxyWrapper is off (#19248)"
createDockVesselProxyEmbodiment().move({embodyHeader: true}) this ticket; ADR 0029 §2.8.6 One DragProxyComponent over the source zone's getDragProxyVdom() in the target window. Nothing is staged; the pane stays in the parked vessel. restore and promote retire it exactly once No proxy vdom: move() answers false and admits nothing. A refused mount retires that generation and settles whenSettled() false JSDoc (factory, move) DockVesselEmbodiment.spec, "a converted tab drag enters the target as its tab-header proxy (#19248)", four arms
snapshot().header (new) this ticket true for a header proxy. CrossWindowGestureSnapshot reads it in place of ownsPane null snapshot without an active proxy JSDoc WorkstationFiveBeatNL scene 3 (headless Neural Link, not CI-run)
DragTarget#isDragEmbodimentLive, defaulted by Participation#defaultIsDragEmbodimentLive (new) the PR #19254 review, RA-1 The drop asks whether the embodiment owner's proxy for the item has settled (snapshot(itemId).settled). A proxy still mounting, or refused, answers false: the drop restores, clears the preview and commits nothing Unset, or an owner without snapshot: null, and the drop proceeds as before JSDoc DockCrossWindowParticipation.spec: "a header proxy that has not rendered commits no drop, refused or still mounting; over a rendered one the same drop commits"
Native title-bar drag (the alternate) the operator, per PR #19254's Deltas: a real popup stays a popup Unchanged: the live pane moves into a DragProxyContainer in the target, and the VesselPlaceholder stays its reader — ADR 0029 §2.8.6 sentence the existing pane-mode DockVesselEmbodiment.spec arms

Out of Scope

  • #19278 (the target-clear park, under #19186) — the blocker for the hand witness; this ticket is the UX shape on top. #19247 is closed: the title-bar-strip re-entry hypothesis was not the mechanism.
  • #19186 (the stand-in mask over another popup) may dissolve with the placeholder's retirement — verify, do not assume.

Related

#19241 / PR #19242, #19243 / PR #19244, #19186, #19278 (the park; owns AC-3's witness), PR #19254, ADR 0029 §2.8.6, #15396 (conversion + park origin).

Live latest-open sweep: latest 20 open issues at 2026-09-26T09:37:50Z, no equivalent. A2A sweep (last 8 at 09:35Z): none. Memory sweep: the operator's two statements this session. Own-assignment sweep: none covers it.

handoff: @neo-opus-ada (dock lane, offered a second pair of hands this morning; Opus budget) — 1:1 wake follows.

Origin Session ID: 519ec01f-3aec-4247-a34a-e8741fd4757e Retrieval Hint: "converted vessel target proxy tab header not window content"

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

tobiu referenced in commit 6ceaf4f - "fix(dock): a converted tab drag enters the target as its tab-header proxy (#19248) (#19254) on Sep 26, 2026, 8:30 PM
tobiu closed this issue on Sep 26, 2026, 8:30 PM