Sub-issue of #13158 (dock layouts, multi-window). Found by @neo-opus-vega while rebasing PR #18059 onto #18068 and handed over as a Tier 2.5 finding on the window-manager surface; V-B-A'd here.
Context
Neo.manager.Window is the one geometry authority for cross-window docking: DragCoordinator hit-tests Window.getWindowAt(screenX, screenY) on the pinned legacy path (src/manager/DragCoordinator.mjs:856, outer-rect based since #18068) and reads innerRect for the remote-drag acceptance offset. The manager's rows refresh from two sources only: the connect-time snapshot (manager/Window.mjs#onWindowConnect) and windowPositionChange payloads (#onWindowPositionChange, Window.mjs:188-206). Those payloads come from the Main-thread addon Neo.main.addon.WindowPosition#publishGeometry — behind two reactive configs the addon defaults to false: observeMovement (the config-owned poll, #18040; checkMovement publishes only when screenLeft/Top moved) and observeResize (afterSetObserveResize installs the resize listener; onResize ends in publishGeometry() — "A fixed-origin resize is still a geometry change").
The Problem
Nothing in the engine arms either config. grep -rn "observeResize\|observeMovement" src apps on dev@5ae9358063 finds exactly one consumer: apps/workstation/view/Workspace.mjs:434 (the main render target at construction — its comment already names the case: "Movement snapshots alone go stale after a main-window resize") and :3551 (each admitted tear-out vessel, awaited before the vessel is published to any ownership branch). Every other dock host is blind:
examples/dashboard/dock/MainContainer.mjs (extends the engine host) arms nothing.
- The Fleet cockpit (neomjs/neo-agent-institution,
apps/agentos) arms nothing: zero references to WindowPosition under its apps/.
For those hosts a main window that was moved or resized after connect keeps its connect-time outerRect, so a popup dragged beside the shrunk viewport is claimed by it (Vega's measured case), and a vessel never publishes its own movement at all — the native-titlebar popup→main drag (the demo's killer beat) has no geometry stream to run on. The Workstation works because it copied two lines the engine should own.
The Architectural Reality
src/dashboard/dock/Workspace.mjs (Neo.dashboard.dock.Workspace, the host every adopter extends since #17541 / #17546): construct (:476) creates the preview producer and, with enableDockTearOutLifecycle, subscribes Neo.currentWorker.on({connect: this.onWindowConnect, disconnect: this.onWindowDisconnect}); the admission path runs admitTearOutConnection(context) (:940, grant hook) and afterTearOutWindowConnect(context) (:951, observation hook) after engine ownership state is written; reparentTearOutPane (:906) moves the pane into Neo.apps[windowId].mainView.
src/main/addon/WindowPosition.mjs: observeMovement_ / observeResize_ reactive configs (:36-39, :113-121), publishGeometry (:164, sends windowPositionChange with getWindowData() + windowId), checkMovement (:141), onResize (:245-275).
src/worker/App.mjs:833 forwards windowPositionChange to Neo.manager.Window.onWindowPositionChange.
- The unit precedent for stubbing the addon:
test/playwright/unit/apps/workstation/Workspace.spec.mjs:432-478 (Neo.main.addon.WindowPosition swapped for a recorder); the engine host's own spec is test/playwright/unit/dashboard/DockWorkspace.spec.mjs (onWindowConnect({windowId}) driven at :1094).
The Fix
The engine host owns the arming; the app-level copies retire.
Neo.dashboard.dock.Workspace#construct: after super.construct, when enableDockTearOutLifecycle is on, Neo.main.addon.WindowPosition?.setConfigs({observeMovement: true, observeResize: true, windowId: this.windowId}) for the host's own render target (the Workstation's :434, verbatim, one level down). Gate: only tear-out-enabled hosts run a poll — a plain dashboard pays nothing.
- The admitted vessel: in the engine's
onWindowConnect admission, arm the connecting window's Main realm the same way before the connection is published to any ownership branch and before afterTearOutWindowConnect (the ordering the Workstation's :3551 comment demands: the header-action pop-out births the vessel with its titlebar under the pointer, so no mouseout ever arms the poll).
apps/workstation/view/Workspace.mjs:434 and :3551 delete; behaviour identical (the calls move, they do not change).
- Method JSDoc on both sites states why the engine arms it (Anchor & Echo); ADR 0029's window-manager section gets one sentence: geometry observation is the dock host's, not the adopter's.
Consumer note: the Fleet cockpit does not extend the engine host yet; until it does, it inherits nothing from this leaf — its one-line arming is an institution leaf (not filed here; #13158's consumer rows own it).
Contract Ledger Matrix
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
dock.Workspace#construct (src/dashboard/dock/Workspace.mjs:476) |
this ticket; #18040 (config-owned poll) |
arms {observeMovement, observeResize} for this.windowId when enableDockTearOutLifecycle |
addon absent (unit machine, non-browser realm) → optional chaining, no-op |
method JSDoc + ADR 0029 |
DockWorkspace.spec.mjs recorder arm |
engine onWindowConnect admission (same file, the tear-out connect branch) |
this ticket |
arms the vessel's realm before ownership publication and before afterTearOutWindowConnect |
same |
method JSDoc |
DockWorkspace.spec.mjs ordering arm |
apps/workstation/view/Workspace.mjs:434, :3551 |
#17546 (the migration) |
retired — inherited from the engine host |
— |
— |
Workstation unit + the two native e2e arms unchanged |
WindowPosition#setConfigs route (src/main/addon/WindowPosition.mjs) |
existing |
unchanged |
— |
— |
existing WindowPosition.spec.mjs |
Decision Record impact: aligned-with ADR 0029 (window manager as geometry authority); amends nothing.
Acceptance Criteria
Out of Scope
- The Fleet cockpit's adoption of the engine host (its own migration leaf) and its interim one-line arming (institution).
- Changing what
getWindowAt hit-tests (#18068 settled outer-rect) or the claim arbiter.
- Poll cadence or the
mouseout re-arm semantics (#18040).
Related
Parent: #13158. #18040 (config-owned movement poll), #18068 (frame-origin geometry), PR #18059 (the rebase that surfaced it), #17541 / #17546 (the engine host and the Workstation migration), #15239 (multi-window choreography epic). Consumer: neomjs/neo-agent-institution apps/agentos.
Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T11:47Z (none on geometry observation; #18075 is the data-sync spec, #18074 reveal motion); A2A claims of the last hour: none on this scope — the finding was handed to me by its finder at 11:39Z; ask_knowledge_base (ticket) surfaced the window-manager / addon architecture docs, no leaf; the local ticket corpus on observeResize|observeMovement|getWindowAt returns only the multi-window epics (#15239, #15240, #15246, #15906, #15915, #15895).
Origin Session ID: 91f83b9c-df95-4f72-a68f-d33f470792ac
Retrieval Hint: "dock.Workspace arms WindowPosition observeMovement observeResize engine host not app stale outerRect after resize"
📜 Clio
Sub-issue of #13158 (dock layouts, multi-window). Found by @neo-opus-vega while rebasing PR #18059 onto #18068 and handed over as a Tier 2.5 finding on the window-manager surface; V-B-A'd here.
Context
Neo.manager.Windowis the one geometry authority for cross-window docking:DragCoordinatorhit-testsWindow.getWindowAt(screenX, screenY)on the pinned legacy path (src/manager/DragCoordinator.mjs:856, outer-rect based since #18068) and readsinnerRectfor the remote-drag acceptance offset. The manager's rows refresh from two sources only: the connect-time snapshot (manager/Window.mjs#onWindowConnect) andwindowPositionChangepayloads (#onWindowPositionChange,Window.mjs:188-206). Those payloads come from the Main-thread addonNeo.main.addon.WindowPosition#publishGeometry— behind two reactive configs the addon defaults tofalse:observeMovement(the config-owned poll, #18040;checkMovementpublishes only whenscreenLeft/Topmoved) andobserveResize(afterSetObserveResizeinstalls theresizelistener;onResizeends inpublishGeometry()— "A fixed-origin resize is still a geometry change").The Problem
Nothing in the engine arms either config.
grep -rn "observeResize\|observeMovement" src appsondev@5ae9358063finds exactly one consumer:apps/workstation/view/Workspace.mjs:434(the main render target at construction — its comment already names the case: "Movement snapshots alone go stale after a main-window resize") and:3551(each admitted tear-out vessel, awaited before the vessel is published to any ownership branch). Every other dock host is blind:examples/dashboard/dock/MainContainer.mjs(extends the engine host) arms nothing.apps/agentos) arms nothing: zero references toWindowPositionunder itsapps/.For those hosts a main window that was moved or resized after connect keeps its connect-time
outerRect, so a popup dragged beside the shrunk viewport is claimed by it (Vega's measured case), and a vessel never publishes its own movement at all — the native-titlebar popup→main drag (the demo's killer beat) has no geometry stream to run on. The Workstation works because it copied two lines the engine should own.The Architectural Reality
src/dashboard/dock/Workspace.mjs(Neo.dashboard.dock.Workspace, the host every adopter extends since #17541 / #17546):construct(:476) creates the preview producer and, withenableDockTearOutLifecycle, subscribesNeo.currentWorker.on({connect: this.onWindowConnect, disconnect: this.onWindowDisconnect}); the admission path runsadmitTearOutConnection(context)(:940, grant hook) andafterTearOutWindowConnect(context)(:951, observation hook) after engine ownership state is written;reparentTearOutPane(:906) moves the pane intoNeo.apps[windowId].mainView.src/main/addon/WindowPosition.mjs:observeMovement_/observeResize_reactive configs (:36-39,:113-121),publishGeometry(:164, sendswindowPositionChangewithgetWindowData()+windowId),checkMovement(:141),onResize(:245-275).src/worker/App.mjs:833forwardswindowPositionChangetoNeo.manager.Window.onWindowPositionChange.test/playwright/unit/apps/workstation/Workspace.spec.mjs:432-478(Neo.main.addon.WindowPositionswapped for a recorder); the engine host's own spec istest/playwright/unit/dashboard/DockWorkspace.spec.mjs(onWindowConnect({windowId})driven at:1094).The Fix
The engine host owns the arming; the app-level copies retire.
Neo.dashboard.dock.Workspace#construct: aftersuper.construct, whenenableDockTearOutLifecycleis on,Neo.main.addon.WindowPosition?.setConfigs({observeMovement: true, observeResize: true, windowId: this.windowId})for the host's own render target (the Workstation's:434, verbatim, one level down). Gate: only tear-out-enabled hosts run a poll — a plain dashboard pays nothing.onWindowConnectadmission, arm the connecting window's Main realm the same way before the connection is published to any ownership branch and beforeafterTearOutWindowConnect(the ordering the Workstation's:3551comment demands: the header-action pop-out births the vessel with its titlebar under the pointer, so nomouseoutever arms the poll).apps/workstation/view/Workspace.mjs:434and:3551delete; behaviour identical (the calls move, they do not change).Consumer note: the Fleet cockpit does not extend the engine host yet; until it does, it inherits nothing from this leaf — its one-line arming is an institution leaf (not filed here; #13158's consumer rows own it).
Contract Ledger Matrix
dock.Workspace#construct(src/dashboard/dock/Workspace.mjs:476){observeMovement, observeResize}forthis.windowIdwhenenableDockTearOutLifecycleDockWorkspace.spec.mjsrecorder armonWindowConnectadmission (same file, the tear-out connect branch)afterTearOutWindowConnectDockWorkspace.spec.mjsordering armapps/workstation/view/Workspace.mjs:434,:3551WindowPosition#setConfigsroute (src/main/addon/WindowPosition.mjs)WindowPosition.spec.mjsDecision Record impact: aligned-with ADR 0029 (window manager as geometry authority); amends nothing.
Acceptance Criteria
test/playwright/unit/dashboard/DockWorkspace.spec.mjs): constructing a tear-out-enableddock.Workspacewith awindowIdrecords oneWindowPosition.setConfigs({observeMovement: true, observeResize: true, windowId})on a stubbed addon; a host withoutenableDockTearOutLifecyclerecords none. Red ondev(no call today).onWindowConnect({windowId})for an admitted vessel records the same call for the vessel'swindowId, and the recorder sees it beforeafterTearOutWindowConnectruns (ordering assertion). Red ondev.apps/workstation/view/Workspace.mjsno longer callsWindowPosition.setConfigs;unit/apps/workstation/Workspace.spec.mjs,WorkstationNativeTitlebarDragNLandWorkstationNativePopupOverPopupNLstay green (the calls moved down one class).test/playwright/component/dashboard/DockGeometryObservation.spec.mjson thedock-popoutfixture — a tear-out host that arms nothing by hand): after a fixed-origin viewport resize the manager row read through the fixture's probe follows the new extents without any pointer or position change; the boot read is the control. Red ondev. (Amended 2026-09-02: the example dock app is not a tear-out host, so it is not the falsifier; the real-Chrome frame/viewport split stays withWindowGeometryRealChromeNL, unchanged.)observeResize/observeMovementliteral outsidesrc/(grep-checkable); every tear-out-enabled host inherits the arming.Out of Scope
getWindowAthit-tests (#18068 settled outer-rect) or the claim arbiter.mouseoutre-arm semantics (#18040).Related
Parent: #13158. #18040 (config-owned movement poll), #18068 (frame-origin geometry), PR #18059 (the rebase that surfaced it), #17541 / #17546 (the engine host and the Workstation migration), #15239 (multi-window choreography epic). Consumer: neomjs/neo-agent-institution
apps/agentos.Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T11:47Z (none on geometry observation; #18075 is the data-sync spec, #18074 reveal motion); A2A claims of the last hour: none on this scope — the finding was handed to me by its finder at 11:39Z;
ask_knowledge_base(ticket) surfaced the window-manager / addon architecture docs, no leaf; the local ticket corpus onobserveResize|observeMovement|getWindowAtreturns only the multi-window epics (#15239, #15240, #15246, #15906, #15915, #15895).Origin Session ID: 91f83b9c-df95-4f72-a68f-d33f470792ac
Retrieval Hint: "dock.Workspace arms WindowPosition observeMovement observeResize engine host not app stale outerRect after resize"
📜 Clio