Context
Surfaced while discharging row 2 of #17576 (the four pre-existing dock-demo journey reds on dev). That row was filed as a failing assertion — .agentos-dockdemo-counter-pane not found in the popup. Diagnosing it to the seam showed the assertion is correct and the product transfer never happens: Demo B's headline cross-window drag aborts, and the tour stops at 20 of 28 beats.
#17576 predicted rows 1–3 would be "real questions about the product, not harness artifacts". Row 2 is the confirmation.
Live latest-open sweep: checked the latest 20 open issues at 2026-08-22T21:47:30Z; A2A claim sweep over the last 30 inbox messages. No equivalent found in either substrate.
⚠️ Anchors relocated + evidence gap named (2026-08-23)
Every file path below is stale. d3f7d5a809 (#17562) moved the dock demos out of the Fleet Manager app after this ticket was filed — my own merge stranded my own anchors. The file is now examples/dashboard/crossWindow/DemoBWorkspace.mjs; the line numbers are unchanged (1757, 1965, 2218–2234) because it was a move, not a rewrite.
The candidateDiagnostics payload proves less than this ticket claimed. It is not a record of what the coordinator did. It is built by the workspace, inside the bail branch (if (!remoteSnapshot.ready)), iterating the sort group and calling zone.acceptsRemoteDrag() itself. It is a post-hoc reconstruction of what the answer would have been, computed only once readiness has already failed.
That matters because the coordinator's gate is compound (DragCoordinator.mjs:648-656):
inner?.intersects({bottom: screenY, right: screenX, x: screenX, y: screenY}) &&
zone.acceptsRemoteDrag(screenX - inner.x, screenY - inner.y)candidateDiagnostics records the second conjunct only. It is silent on whether inner resolved and whether intersects passed — and the ?. means a missing innerRect falls to the else, which actively releases the claim. So the original evidence could not distinguish "the zone was consulted and rejected" from "the zone was never reached".
Two hypotheses that gap allowed, both now refuted by reading — do not re-run them:
- Different coordinate derivation between workspace and coordinator. Refuted: both use
screen − innerRect.x/y. DragCoordinator.mjs:652 is zone.acceptsRemoteDrag(screenX - inner.x, screenY - inner.y); the workspace is targetScreenX - inner.x. Identical.
- The
intersects argument is malformed — a point object carrying bottom/right but no width/height. Refuted: util/Rectangle.mjs:270-289 has an explicit point branch ("We're dealing with a point here - zero dimensions") reading exactly other.x, other.y, other.right, other.bottom. For the recorded geometry — pointer (1122, 289) against the target inner spanning x 822–1422, y 22–542 — all four comparisons pass.
So the root cause stands and the search space is narrower: the coordinator does consult acceptsRemoteDrag, with the same coordinates, and both conjuncts of the documented gate should pass on the recorded numbers. The next instrument must observe the coordinator's own collection pass — whether inner resolves inside its realm, and whether arbiter.claim is reached — rather than reconstructing the answer after the fact. Note also that the observed candidateCount: 0 comes from the drop-indicator surface, which has not been confirmed to be the same collection as the arbiter path above.
🔬 Re-measured 2026-08-23 — geometry re-confirmed, two hypotheses killed, and a probe channel that does not work
Fresh reproduction on dev, full debug payload recovered. The recorded geometry is exactly as this ticket described, now with the pointer stated outright rather than derived:
targetPointer: {screenX: 1122, screenY: 289}
- popup
innerRect {x: 822, y: 22, w: 600, h: 520} → spans 822–1422 × 22–542, so the pointer is inside
candidateDiagnostics[0]: stableTargetId: 'demo-b-popup', accepts: true, sameAsSource: false
- both windows registered in the single
demo-b-cross-window sort group
pointerGestureToken: 'gesture-1' — a pointer gesture is live
- and still
activeTargetZone: null, activeTargetCommitEligible: false, candidateCount: 0
Two more hypotheses refuted by reading — do not re-run them
- A local-vs-screen coordinate mix-up at the claim call site.
getNativeWindowDropCandidate calls resolveClaimedTarget({screenX: localX, screenY: localY, …}), which looks like local coordinates passed into screen-named parameters — and would double-subtract the window origin inside the collection loop. Refuted: localX = popupRect.x + popupRect.width / 2 where popupRect is the window's innerRect, so those values are already screen-space. The names are misleading in a file where the distinction is load-bearing, but the behaviour is correct.
- Wrong call site. That path is
getNativeWindowDropCandidate — the native window drag. This ticket's tour scripts a pointer drag, which reaches claim resolution at DragCoordinator.mjs:689 inside onDragMove. Analysis of the native call site does not transfer.
✅ SETTLED without a probe: resolveClaimedTarget DOES execute during the gesture
The recorded snapshot already contained the proof; no instrumentation was needed.
onDragMove (DragCoordinator.mjs:676-690) calls the claim resolver unconditionally — it sits in the opening let chain with no guard in front of it:
arbiter = me.pointerClaimArbiter ??= createGestureClaimArbiter(),
claimed = me.resolveClaimedTarget({arbiter, screenX, screenY, sortGroup, sourceSortZone}), The failure snapshot reports coordinator.pointerGestureToken: 'gesture-1'. That token only exists because pointerClaimArbiter was minted at :688 — and :689 is the very next expression in the same declaration. So the collection pass ran, and returned no claim. The "does it even run" branch is closed.
That removes the last non-loop explanation. The search is now strictly inside the collection loop, where the recorded geometry says both conjuncts should pass.
5. Coordinate-space mismatch at the dispatch sites — also refuted
Candidate: the drag delivers source-window-local coordinates into parameters the loop reads as screen (screenX - inner.x), shifting everything by the source origin while still landing inside the target rect — an accepting zone, a plausible intersect, and no claim.
Refuted. There are three dispatch sites, not one (DockTabSortZone.mjs:560 vessel-conversion replay, DockTabSortZone.mjs:1339 the primary, draggable/dashboard/SortZone.mjs:435 the base onWindowDragContinue). All three pass data.screenX / data.screenY through unchanged, and a DOM MouseEvent.screenX is OS-screen space — the same space as the Window.innerRect values the loop subtracts (main at x: 22, popup at x: 822 in the snapshot). The spaces agree.
(Instrument note: my first grep for the dispatch found only one site because the pattern excluded ?. and the static-import call. A too-narrow search nearly produced "the coordinator is only reachable from the vessel-conversion replay path", which is false.)
➡️ Where the search now stands
Five hypotheses refuted, and the collection pass proven to run. Inside the loop, on the recorded numbers, every gate should pass: the source window is skipped correctly, stableTargetId is non-null, acceptsRemoteDrag exists, Window.get() resolves (the snapshot's windows payload was produced by WindowManager.toJSON() from the same App Worker realm the coordinator runs in, so the registry is populated there), the point lies inside the target rect, and the zone answers accepts: true.
The strongest remaining candidate is GestureClaimArbiter itself — arbiter.claim(stableTargetId, zone) may be reached while arbiter.resolve() still returns null (token ownership, a tiebreak, or a claim released later in the same pass by the source-window branch). That is now the narrowest unexamined surface and the place to look next.
Second candidate, weaker: acceptsRemoteDrag disagreeing between the coordinator's call and the workspace's post-hoc one — same function, marginally different coordinates and a different moment.
⚠️ The probe channel does not work — a negative instrument result, not a product finding
The next question is simply does resolveClaimedTarget execute during this gesture — still unproven, and the thing that must be settled before anyone reads further into the collection loop.
I instrumented its entry with a console.log and re-ran: no output. That proves nothing about the product, because Neo.manager.DragCoordinator runs in the App Worker, and neither test/playwright/fixtures.mjs nor the spec forwards browser console to Playwright's stdout (checked). A probe there is structurally invisible, so silence must not be read as "the function did not run".
Whoever takes this next: use a channel that reaches the spec — fold the observation into the debug payload the executor already returns, read it through the Neural Link, or run under a scratch config that forwards page.on('console'). Do not repeat the bare console.log.
The Problem
apps/agentos/tour/demoBPerspectives.mjs scene s3 step 2 scripts a real two-window pointer drag:
{
type : 'cross-window',
itemId : 'workbench',
sourceWorkspaceId: 'demo-b-main',
targetWorkspaceId: 'demo-b-popup',
targetNodeId : 'popup-tabs'
}The executor returns applied: false and Neo.ai.client.TourRunner aborts the run. Its own logged receipt, read out of worker truth:
{"applied":false,
"errors":["remote target did not expose a settled semantic preview"],
"sceneId":"s3","stepIndex":2,"type":"cross-window"}with tourRunner.running === false and log.length === 20 of 28 beats — frozen, not slow. Sampled every 700 ms for 20 s and again for 90 s; the freeze is permanent, not a pacing artifact.
The pane's observed journey, sampling all live windows plus Neo.worker.Manager.windowId in each:
| t |
CounterPane mountCount |
CounterPane window |
window holding the pane |
| 0.0 s |
1 |
cf270ded |
SOURCE |
| 0.7 s – 5.0 s |
2 |
baefb647 |
vesselFlow=tear-out |
| 5.8 s → |
3 |
cf270ded |
SOURCE |
The third window — 09220ba7, vesselFlow=workspace-target, and equal to crossWindowTargetWindowId — reports hit=0 for the pane at every sample. The pane is picked up by the pointer-follow vessel, never lands in the target, and is returned home when the gesture is cancelled.
So this is not "the pane went to the wrong window". The gesture is cancelled before commit because a readiness gate never opens.
The Architectural Reality
DemoBWorkspace.executeCrossWindowStep (apps/agentos/childapps/dockdemo/view/DemoBWorkspace.mjs:1965) polls waitForCrossWindowRemotePreview before it will dispatch mouseup, and bails at line 2218–2234 when the poll never reports ready. Readiness is computed in readCrossWindowRemoteSnapshot (:1757):
engaged : coordinator?.activeTargetZone === target,
snapshot.ready = snapshot.engaged
&& snapshot.preview?.target?.nodeId === targetNodeId
&& snapshot.rendered?.previewId === snapshot.preview?.previewIdThe bail branch's debug payload — which TourRunner discards, and which this ticket recovers by calling the executor directly — isolates the seam precisely:
candidateDiagnostics:
{stableTargetId:'demo-b-popup', windowId:'25b1bc46…', accepts:TRUE,
localX:300, localY:267, innerRect:{x:822,y:22,w:600,h:520}}
{stableTargetId:'demo-b-main', windowId:'d58c17be…', accepts:false,
localX:1100, localY:267, innerRect:{x:22,y:22,w:760,h:720}}
remoteSnapshot:
coordinator.activeTargetZone : null
coordinator.activeTargetCommitEligible: false
coordinator.sortZones : [{group:'demo-b-cross-window',
windows:['25b1bc46…','d58c17be…']}]
engaged : false
indicators : {activePreviewId:null, candidateCount:0, schema:null}
preview / rendered : null / null
ready : false
conversion:
targetId 'demo-b-popup', targetRect {x:822,y:22,w:600,h:520},
windowDragging true, converted false, targetConverted false, transitioning false
readiness: {coordinatorReady:true, dragProxyReady:true, dragging:true, ready:true}Read together these say one thing:
- Geometry is right. The pointer sits at (300, 267) inside the target's own 600×520 rect, and the target
Neo.dashboard.DockTabSortZone answers its own acceptsRemoteDrag(300, 267) with true. The source zone correctly declines.
- Registration is right. Both windows are in the coordinator's single
demo-b-cross-window sort group.
- The source is right.
readiness.ready === true; vesselConversionTargetId already resolves to demo-b-popup with the correct rect.
Neo.manager.DragCoordinator still reports activeTargetZone: null and candidateCount: 0.
The defect is therefore in the coordinator's remote candidate collection / target engagement, not in the demo, the zone's hit-testing, the window placement, or the tour script. A zone that answers acceptsRemoteDrag() === true for the live pointer position is never promoted to activeTargetZone, so no preview is ever published, so ready never becomes true, so the executor cancels.
The Fix
Close the gap between "the zone accepts this point" and "the coordinator engages this zone" for the cross-window leg.
src/manager/DragCoordinator.mjs — the remote candidate path that should populate activeTargetZone / activeTargetCommitEligible from zones in the same sort group but a different windowId. Determine why a zone whose own acceptsRemoteDrag() returns true contributes zero candidates.
src/dashboard/DockTabSortZone.mjs — the publishing side: whether the accepting zone announces itself to the coordinator on a remote pointer move at all, or only on a same-window one.
- The drop-indicator surface (
ntype: 'dashboard-dock-drop-indicators'), whose candidateSet.cross is the empty collection observed above.
The reproduction is already committed and needs no new harness: run the journey and read tourRunner.log's last entry, or call executeCrossWindowStep directly for the full debug payload.
Acceptance Criteria
Out of Scope
- The other three rows of #17576 (row 1 fixed; rows 3 and 4 keep their own dispositions there).
- The tear-out (
vesselFlow=tear-out) grammar. It works — the vessel is created, carries the live instance, and is cleanly cancelled. DemoBKeyboardDetachNL covers it and is green.
- Same-window dock drag. Unaffected; only the cross-window candidate path is implicated.
- Demo pacing. The freeze is permanent under both a 20 s and a 90 s observation window, so no amount of waiting reaches the assertion.
Avoided Traps
- Calling it flaky. It is deterministic across every run in this diagnosis, and the runner's abort receipt names the cause in words.
- Fixing the spec. The assertion encodes the tour script's own declared destination (
targetWorkspaceId: 'demo-b-popup', targetNodeId: 'popup-tabs') and the docblock's contract. Re-pointing it at the tear-out vessel would ratify the defect.
- Blaming geometry or the headless window manager. The target zone hit-tests the pointer as accepted at (300, 267) inside its own rect; CDP placement produced two correct non-overlapping rectangles. Both were the cheap wrong answers and both are refuted above.
- Trusting
readiness.ready. It reports true — it describes the source being ready to drag, not the target being ready to receive. Reading it as an all-clear hides the real gate.
Decision Record impact
none — this restores a documented behaviour, it does not change an architectural decision.
Related
#17576 (parent finding; this is its row-2 successor) · #17564 (the Workstation sibling set) · #16322 / PR #17562 (the relocation whose measurement surfaced the set; it does not touch this path)
Retrieval Hint: cross-window drag coordinator activeTargetZone null candidateCount 0 acceptsRemoteDrag true remote target did not expose a settled semantic preview DemoB s3
Origin Session ID: 1b0d28eb-3461-40b6-bb35-88d6bf09ec94
Context
Surfaced while discharging row 2 of #17576 (the four pre-existing dock-demo journey reds on
dev). That row was filed as a failing assertion —.agentos-dockdemo-counter-panenot found in the popup. Diagnosing it to the seam showed the assertion is correct and the product transfer never happens: Demo B's headline cross-window drag aborts, and the tour stops at 20 of 28 beats.#17576predicted rows 1–3 would be "real questions about the product, not harness artifacts". Row 2 is the confirmation.Live latest-open sweep: checked the latest 20 open issues at 2026-08-22T21:47:30Z; A2A claim sweep over the last 30 inbox messages. No equivalent found in either substrate.
⚠️ Anchors relocated + evidence gap named (2026-08-23)
Every file path below is stale.
d3f7d5a809(#17562) moved the dock demos out of the Fleet Manager app after this ticket was filed — my own merge stranded my own anchors. The file is nowexamples/dashboard/crossWindow/DemoBWorkspace.mjs; the line numbers are unchanged (1757,1965,2218–2234) because it was a move, not a rewrite.The
candidateDiagnosticspayload proves less than this ticket claimed. It is not a record of what the coordinator did. It is built by the workspace, inside the bail branch (if (!remoteSnapshot.ready)), iterating the sort group and callingzone.acceptsRemoteDrag()itself. It is a post-hoc reconstruction of what the answer would have been, computed only once readiness has already failed.That matters because the coordinator's gate is compound (
DragCoordinator.mjs:648-656):inner?.intersects({bottom: screenY, right: screenX, x: screenX, y: screenY}) && zone.acceptsRemoteDrag(screenX - inner.x, screenY - inner.y)candidateDiagnosticsrecords the second conjunct only. It is silent on whetherinnerresolved and whetherintersectspassed — and the?.means a missinginnerRectfalls to theelse, which actively releases the claim. So the original evidence could not distinguish "the zone was consulted and rejected" from "the zone was never reached".Two hypotheses that gap allowed, both now refuted by reading — do not re-run them:
screen − innerRect.x/y.DragCoordinator.mjs:652iszone.acceptsRemoteDrag(screenX - inner.x, screenY - inner.y); the workspace istargetScreenX - inner.x. Identical.intersectsargument is malformed — a point object carryingbottom/rightbut nowidth/height. Refuted:util/Rectangle.mjs:270-289has an explicit point branch ("We're dealing with a point here - zero dimensions") reading exactlyother.x,other.y,other.right,other.bottom. For the recorded geometry — pointer (1122, 289) against the target inner spanning x 822–1422, y 22–542 — all four comparisons pass.So the root cause stands and the search space is narrower: the coordinator does consult
acceptsRemoteDrag, with the same coordinates, and both conjuncts of the documented gate should pass on the recorded numbers. The next instrument must observe the coordinator's own collection pass — whetherinnerresolves inside its realm, and whetherarbiter.claimis reached — rather than reconstructing the answer after the fact. Note also that the observedcandidateCount: 0comes from the drop-indicator surface, which has not been confirmed to be the same collection as the arbiter path above.🔬 Re-measured 2026-08-23 — geometry re-confirmed, two hypotheses killed, and a probe channel that does not work
Fresh reproduction on
dev, fulldebugpayload recovered. The recorded geometry is exactly as this ticket described, now with the pointer stated outright rather than derived:targetPointer: {screenX: 1122, screenY: 289}innerRect{x: 822, y: 22, w: 600, h: 520}→ spans 822–1422 × 22–542, so the pointer is insidecandidateDiagnostics[0]:stableTargetId: 'demo-b-popup',accepts: true,sameAsSource: falsedemo-b-cross-windowsort grouppointerGestureToken: 'gesture-1'— a pointer gesture is liveactiveTargetZone: null,activeTargetCommitEligible: false,candidateCount: 0Two more hypotheses refuted by reading — do not re-run them
getNativeWindowDropCandidatecallsresolveClaimedTarget({screenX: localX, screenY: localY, …}), which looks like local coordinates passed into screen-named parameters — and would double-subtract the window origin inside the collection loop. Refuted:localX = popupRect.x + popupRect.width / 2wherepopupRectis the window'sinnerRect, so those values are already screen-space. The names are misleading in a file where the distinction is load-bearing, but the behaviour is correct.getNativeWindowDropCandidate— the native window drag. This ticket's tour scripts a pointer drag, which reaches claim resolution atDragCoordinator.mjs:689insideonDragMove. Analysis of the native call site does not transfer.✅ SETTLED without a probe:
resolveClaimedTargetDOES execute during the gestureThe recorded snapshot already contained the proof; no instrumentation was needed.
onDragMove(DragCoordinator.mjs:676-690) calls the claim resolver unconditionally — it sits in the openingletchain with no guard in front of it:arbiter = me.pointerClaimArbiter ??= createGestureClaimArbiter(), // :688 claimed = me.resolveClaimedTarget({arbiter, screenX, screenY, sortGroup, sourceSortZone}), // :689The failure snapshot reports
coordinator.pointerGestureToken: 'gesture-1'. That token only exists becausepointerClaimArbiterwas minted at:688— and:689is the very next expression in the same declaration. So the collection pass ran, and returned no claim. The "does it even run" branch is closed.That removes the last non-loop explanation. The search is now strictly inside the collection loop, where the recorded geometry says both conjuncts should pass.
5. Coordinate-space mismatch at the dispatch sites — also refuted
Candidate: the drag delivers source-window-local coordinates into parameters the loop reads as screen (
screenX - inner.x), shifting everything by the source origin while still landing inside the target rect — an accepting zone, a plausible intersect, and no claim.Refuted. There are three dispatch sites, not one (
DockTabSortZone.mjs:560vessel-conversion replay,DockTabSortZone.mjs:1339the primary,draggable/dashboard/SortZone.mjs:435the baseonWindowDragContinue). All three passdata.screenX/data.screenYthrough unchanged, and a DOMMouseEvent.screenXis OS-screen space — the same space as theWindow.innerRectvalues the loop subtracts (main atx: 22, popup atx: 822in the snapshot). The spaces agree.(Instrument note: my first grep for the dispatch found only one site because the pattern excluded
?.and the static-import call. A too-narrow search nearly produced "the coordinator is only reachable from the vessel-conversion replay path", which is false.)➡️ Where the search now stands
Five hypotheses refuted, and the collection pass proven to run. Inside the loop, on the recorded numbers, every gate should pass: the source window is skipped correctly,
stableTargetIdis non-null,acceptsRemoteDragexists,Window.get()resolves (the snapshot'swindowspayload was produced byWindowManager.toJSON()from the same App Worker realm the coordinator runs in, so the registry is populated there), the point lies inside the target rect, and the zone answersaccepts: true.The strongest remaining candidate is
GestureClaimArbiteritself —arbiter.claim(stableTargetId, zone)may be reached whilearbiter.resolve()still returnsnull(token ownership, a tiebreak, or a claim released later in the same pass by the source-window branch). That is now the narrowest unexamined surface and the place to look next.Second candidate, weaker:
acceptsRemoteDragdisagreeing between the coordinator's call and the workspace's post-hoc one — same function, marginally different coordinates and a different moment.⚠️ The probe channel does not work — a negative instrument result, not a product finding
The next question is simply does
resolveClaimedTargetexecute during this gesture — still unproven, and the thing that must be settled before anyone reads further into the collection loop.I instrumented its entry with a
console.logand re-ran: no output. That proves nothing about the product, becauseNeo.manager.DragCoordinatorruns in the App Worker, and neithertest/playwright/fixtures.mjsnor the spec forwards browser console to Playwright's stdout (checked). A probe there is structurally invisible, so silence must not be read as "the function did not run".Whoever takes this next: use a channel that reaches the spec — fold the observation into the
debugpayload the executor already returns, read it through the Neural Link, or run under a scratch config that forwardspage.on('console'). Do not repeat the bareconsole.log.The Problem
apps/agentos/tour/demoBPerspectives.mjsscenes3step 2 scripts a real two-window pointer drag:{ type : 'cross-window', itemId : 'workbench', sourceWorkspaceId: 'demo-b-main', targetWorkspaceId: 'demo-b-popup', targetNodeId : 'popup-tabs' }The executor returns
applied: falseandNeo.ai.client.TourRunneraborts the run. Its own logged receipt, read out of worker truth:{"applied":false, "errors":["remote target did not expose a settled semantic preview"], "sceneId":"s3","stepIndex":2,"type":"cross-window"}with
tourRunner.running === falseandlog.length === 20of 28 beats — frozen, not slow. Sampled every 700 ms for 20 s and again for 90 s; the freeze is permanent, not a pacing artifact.The pane's observed journey, sampling all live windows plus
Neo.worker.Manager.windowIdin each:mountCountcf270dedbaefb647vesselFlow=tear-outcf270dedThe third window —
09220ba7,vesselFlow=workspace-target, and equal tocrossWindowTargetWindowId— reportshit=0for the pane at every sample. The pane is picked up by the pointer-follow vessel, never lands in the target, and is returned home when the gesture is cancelled.So this is not "the pane went to the wrong window". The gesture is cancelled before commit because a readiness gate never opens.
The Architectural Reality
DemoBWorkspace.executeCrossWindowStep(apps/agentos/childapps/dockdemo/view/DemoBWorkspace.mjs:1965) pollswaitForCrossWindowRemotePreviewbefore it will dispatchmouseup, and bails at line 2218–2234 when the poll never reportsready. Readiness is computed inreadCrossWindowRemoteSnapshot(:1757):engaged : coordinator?.activeTargetZone === target, snapshot.ready = snapshot.engaged && snapshot.preview?.target?.nodeId === targetNodeId && snapshot.rendered?.previewId === snapshot.preview?.previewIdThe bail branch's
debugpayload — whichTourRunnerdiscards, and which this ticket recovers by calling the executor directly — isolates the seam precisely:candidateDiagnostics: {stableTargetId:'demo-b-popup', windowId:'25b1bc46…', accepts:TRUE, localX:300, localY:267, innerRect:{x:822,y:22,w:600,h:520}} {stableTargetId:'demo-b-main', windowId:'d58c17be…', accepts:false, localX:1100, localY:267, innerRect:{x:22,y:22,w:760,h:720}} remoteSnapshot: coordinator.activeTargetZone : null coordinator.activeTargetCommitEligible: false coordinator.sortZones : [{group:'demo-b-cross-window', windows:['25b1bc46…','d58c17be…']}] engaged : false indicators : {activePreviewId:null, candidateCount:0, schema:null} preview / rendered : null / null ready : false conversion: targetId 'demo-b-popup', targetRect {x:822,y:22,w:600,h:520}, windowDragging true, converted false, targetConverted false, transitioning false readiness: {coordinatorReady:true, dragProxyReady:true, dragging:true, ready:true}Read together these say one thing:
Neo.dashboard.DockTabSortZoneanswers its ownacceptsRemoteDrag(300, 267)withtrue. The source zone correctly declines.demo-b-cross-windowsort group.readiness.ready === true;vesselConversionTargetIdalready resolves todemo-b-popupwith the correct rect.Neo.manager.DragCoordinatorstill reportsactiveTargetZone: nullandcandidateCount: 0.The defect is therefore in the coordinator's remote candidate collection / target engagement, not in the demo, the zone's hit-testing, the window placement, or the tour script. A zone that answers
acceptsRemoteDrag() === truefor the live pointer position is never promoted toactiveTargetZone, so no preview is ever published, soreadynever becomes true, so the executor cancels.The Fix
Close the gap between "the zone accepts this point" and "the coordinator engages this zone" for the cross-window leg.
src/manager/DragCoordinator.mjs— the remote candidate path that should populateactiveTargetZone/activeTargetCommitEligiblefrom zones in the same sort group but a differentwindowId. Determine why a zone whose ownacceptsRemoteDrag()returnstruecontributes zero candidates.src/dashboard/DockTabSortZone.mjs— the publishing side: whether the accepting zone announces itself to the coordinator on a remote pointer move at all, or only on a same-window one.ntype: 'dashboard-dock-drop-indicators'), whosecandidateSet.crossis the empty collection observed above.The reproduction is already committed and needs no new harness: run the journey and read
tourRunner.log's last entry, or callexecuteCrossWindowStepdirectly for the fulldebugpayload.Acceptance Criteria
DragCoordinator.activeTargetZoneresolves to that zone whenever the zone's ownacceptsRemoteDrag(x, y)returnstruefor the same pointer position. The two must not be able to disagree.readCrossWindowRemoteSnapshotreportsready: truefor the Demo B step, andexecuteCrossWindowStepreturnsapplied: true.test/playwright/e2e/agentos/DemoBPerspectivesNL.spec.mjscompletes all 28 beats in both the paced and the spec-mode run, with the popup hosting the live pane during run 0 — i.e. row 2 of #17576 goes green without the assertion being weakened or its selector narrowed.devbehaviour. A test that passes before the fix has not captured this defect — the coordinator returningcandidateCount: 0must redden it.Out of Scope
vesselFlow=tear-out) grammar. It works — the vessel is created, carries the live instance, and is cleanly cancelled.DemoBKeyboardDetachNLcovers it and is green.Avoided Traps
targetWorkspaceId: 'demo-b-popup',targetNodeId: 'popup-tabs') and the docblock's contract. Re-pointing it at the tear-out vessel would ratify the defect.readiness.ready. It reportstrue— it describes the source being ready to drag, not the target being ready to receive. Reading it as an all-clear hides the real gate.Decision Record impact
none— this restores a documented behaviour, it does not change an architectural decision.Related
#17576 (parent finding; this is its row-2 successor) · #17564 (the Workstation sibling set) · #16322 / PR #17562 (the relocation whose measurement surfaced the set; it does not touch this path)
Retrieval Hint:
cross-window drag coordinator activeTargetZone null candidateCount 0 acceptsRemoteDrag true remote target did not expose a settled semantic preview DemoB s3Origin Session ID: 1b0d28eb-3461-40b6-bb35-88d6bf09ec94