Leaf 2a of epic #17539 — the first flagship host migration, sequenced after leaf 1 (#17541, PR #17545) merges. apps/workstation/view/Workspace.mjs is the richest hand-rolled dock host (5306 lines, 89 methods, 40 engine-seam uses) and the film rig's primary surface, which makes it the class's best falsifier: it exercises every hook Neo.dashboard.DockWorkspace exposes and needs four things the class does not have yet. Self-select; the default owner is @neo-fable at #17545's merge. The tear-out / vessel / cross-window members stay where they are — that half is leaf 2b, whose boundary and first consumer (the fleet cockpit) are recorded on the epic.
The Problem
The workstation carries the same holder core the engine now owns (applyDockZoneOperation / getDockZoneDocument / onDockZoneDocumentChange / projectDockModel / refreshDockWorkspace, Workspace.mjs:582–590, :716–725, :804–821, :961–1038, :2107–2212), plus everything that makes it the hard case:
its view-sync's second argument is a per-commit options object ({geometryOnly, operation, preserveItemIds}), not only the descriptor engine surfaces pass — tear-out paths call onDockZoneDocumentChange(document, {preserveItemIds: itemIds}) (:1986, :3348) while the splitter, rails and DockService pass the operation descriptor, from which operation is read;
its refresh takes the reconciler's fast paths (geometryOnly for resizeSplit, retainTopology for detachItem / transferNode), passes onProjectionStaged (tab-bar animation suppression) and waitForOverflowProjection, feeds the reconciler's landedInPlace outcome into flip.play({geometryOnly}) (DockProjectionReconciler.mjs:353, :466), awaits the play inside the motion bracket, then sequences an overflow-menu wait, a 300 ms chrome settle, the bars' animation restore, a host update and refreshCrossWindowParticipation (:2107–2212);
its resolver returns cached live instances (resolvePane, :2273–2332; getPaneHeaderText, :2255) and never decorates them — the adapter's decorator already returns live instances untouched, so the class's resolveProjectedPane path is compatible;
its projection options carry the whole multi-window surface — cross-window sort group, tear-out boundary and opt-in, vessel conversion seams, the drag-affordance layer's cross-zone seams, the tear-out entry/exit/cancel/terminal handlers, workspaceId (:961–1038) — exactly what getDockProjectionOptions() exists for.
The Architectural Reality
construct (:409–470): the projection mounts into a dock-host child (reference: 'dock-host', cls incl. neo-dashboard + neo-dashboard-dock-query-host, layout: fit) beside persistent DockPreview / DockDropIndicators siblings; DockDragAffordances is composed with owner: me; DockService, DockPerspectiveStore, TourRunner, WindowPosition resize observation follow. Maps to dockHostReference: 'dock-host', dockShellIndex: 0, flipMarkerPrefix: 'workstation-pane-'.
beforeRefreshDockWorkspace already exists in substance: the refresh invalidates crossWindowPreviewGeometries and calls dragAffordances.clear() before the FLIP capture (:2117–2119) — the class calls its hook before the capture too.
The class at ad3a866b98 lacks four things this host needs, all additive: per-commit preserveItemIds in the refresh options (merged with getPreservedItemIds()); the reconciler result captured so landedInPlace reaches flip.play; a reconcile-options hook for onProjectionStaged / waitForOverflowProjection / retainTopology; and an awaited post-refresh hook that receives the play promise, so a host that must sequence chrome settle behind the play can await it while fire-and-forget stays the default for every other host.
Facades the witnesses depend on survive the parent swap unchanged: test/playwright/unit/apps/workstation/Workspace.spec.mjs stubs timeout, refreshDockWorkspace and onDockZoneDocumentChange on the instance (instance assignment shadows prototype methods); the 18 test/playwright/e2e/workstation/*.spec.mjs witnesses drive workstation-specific methods through the Neural Link (getPaneIdentity, setWorkspaceTheme, executeTearOutStep, readCrossWindowGestureSnapshot, runTourSpec, executeCrossZoneShowcaseStep, getTabChromeIdentity, getShellIdentity, …), none of which move.
projectVesselDockModel(workspaceId) (:1850) projects OTHER workspaces' documents for vessel windows; it keeps calling DockLayoutAdapter.project directly.
Structural pre-flight: no new file; the class's own spec gains the delta cases.
The Fix
One PR, two halves:
Class deltas (src/dashboard/DockWorkspace.mjs, each with a DockWorkspace.spec.mjs case): (a) refreshDockWorkspace merges refreshOptions.preserveItemIds with getPreservedItemIds(); (b) the reconciler's return value is captured and flip.play receives geometryOnly: result.landedInPlace; (c) getReconcileOptions(document, refreshOptions) hook — extra reconciler options merged after the class's own (onProjectionStaged, waitForOverflowProjection, retainTopology); (d) afterRefreshDockWorkspace({document, refreshOptions, result, played}) — awaited after the play is dispatched; played is the play promise (or null without the addon) so the host decides whether to wait on it; the motion bracket's leave stays class-owned. getRefreshOptions(descriptor, source) needs no change: it receives whatever the committing caller passed — a descriptor from engine surfaces or the host's own options object — and the host's override maps both shapes.
Workspace.mjs — extends DockWorkspace; the five holder-core members deleted; configs dockHostReference: 'dock-host', flipMarkerPrefix: 'workstation-pane-'; overrides: getDockProjectionOptions() (the whole option block of today's projectDockModel minus the three class-owned callbacks), getRefreshOptions() (geometryOnly for resizeSplit; retainTopology for detachItem / transferNode; preserveItemIds pass-through), beforeRefreshDockWorkspace() (geometry invalidation + dragAffordances.clear()), getReconcileOptions() (the animation-suppression onProjectionStaged + waitForOverflowProjection), afterRefreshDockWorkspace() (overflow-menu wait → await played → 300 ms settle → bars restored → host update → refreshCrossWindowParticipation); resolvePane / getPaneHeaderText unchanged; additionalThemeFiles keeps its full list including Neo.dashboard.Container. Every tear-out / vessel / cross-window member stays byte-identical.
Contract Ledger Matrix
Target Surface
Source of Authority
Proposed Behavior
Fallback / Edge Case
Docs
Evidence
DockWorkspace#refreshDockWorkspacerefreshOptions.preserveItemIds — EXISTING method, NEW option
this ticket; Workspace.mjs:1986 (per-commit parked ids)
merged with getPreservedItemIds()
absent → the hook alone, as today for every other host
class JSDoc
new spec case + WorkstationTearOutSourceContinuityNL
flip.play({geometryOnly: landedInPlace}) — EXISTING call, NEW argument
the 18 test/playwright/e2e/workstation/*.spec.mjs witnesses — EXISTING
the film arc's executable authority
unchanged methods, unchanged behavior
a red carries a dev baseline receipt
—
headed run at head
Decision Record impact
aligned-with ADR 0029 — §2.1 as amended by #17541; no further amendment.
Acceptance Criteria
apps/workstation/view/Workspace.mjs extends Neo.dashboard.DockWorkspace; applyDockZoneOperation, getDockZoneDocument, onDockZoneDocumentChange, projectDockModel and refreshDockWorkspace are deleted from it; LOC before → after recorded in the PR body.
The four class deltas land additively, each with a DockWorkspace.spec.mjs case; test/playwright/unit/dashboard/* and the example's witnesses stay green.
getRefreshOptions reproduces today's fast paths — geometryOnly for resizeSplit, retainTopology for detachItem / transferNode, per-commit preserveItemIds — witnessed by WorkstationDockFlipResizeNL and WorkstationTearOutSourceContinuityNL.
The post-refresh sequence keeps its order — overflow-menu wait → awaited play → chrome settle → bars restored → host update → cross-window participation refresh — witnessed by WorkstationTabOverflowCapNL, WorkstationCrossZoneCueNL and WorkstationDragAffordancesNL.
Workspace.spec.mjs passes unmodified (its instance stubs still intercept).
All 18 test/playwright/e2e/workstation/*.spec.mjs files run headed at head, list and counts in Test Evidence; any red carries a detached-worktree origin/dev baseline receipt and is not claimed green. Residual annotation (2026-08-22): [L3-deferred — operator handoff needed] — five pre-existing dev-red cases and one brain-tier-blocked file (receipted identically on unmodified origin/dev51d6072d79, solo-confirmed) are owned by #17564, the surviving residual owner; this AC is satisfied for everything this branch touches, and the six findings discharge under #17564, not here.
WorkstationFiveBeatNL green — the film pipeline's executable authority is unaffected.
Zero changes to the tear-out / vessel / cross-window members of Workspace.mjs; zero changes under src/dashboard beyond the four class deltas; additionalThemeFiles still lists Neo.dashboard.Container.
Out of Scope
The tear-out / vessel host half — leaf 2b, boundary on the epic, the fleet cockpit its first consumer.
Demo A / Demo B (Grace's, after #16322) and the fleet cockpit (Clio's, after #17541 and PR #17542).
Any change to projectVesselDockModel or the vessel workspace registry.
Avoided Traps
Moving tear-out members "while here." The epic's first rejected shape; the boundary is leaf 2b's.
Awaiting the play inside the class for everyone. The example and the cockpit fire-and-forget; the hook receives the promise and the host that needs ordering awaits it.
Decorating live instances. The adapter returns them untouched; the class's resolver path already respects that.
Passing the REQUESTED fast path to the addon.landedInPlace is what the reconciler did; geometryOnly is only what was admitted — the workstation's own comment (:2172–2177) is the receipt.
Related
Parent #17539 · blocked by #17541 (PR #17545) · #15252 (the film arc whose rig this host is) · #16498 (Emmy's place-cycle gate on this host; coordinate on any refresh-ordering change) · #15178 / #16507 (post-restore continuity on this host).
Live latest-open sweep: checked latest 20 open issues at 2026-08-22T13:58:44Z; no equivalent found. A2A in-flight claim sweep: latest 30 all-state messages at the same timestamp; no overlapping claim (Grace's #16322 claim is the dockdemo relocation).
Context
Leaf 2a of epic #17539 — the first flagship host migration, sequenced after leaf 1 (#17541, PR #17545) merges.
apps/workstation/view/Workspace.mjsis the richest hand-rolled dock host (5306 lines, 89 methods, 40 engine-seam uses) and the film rig's primary surface, which makes it the class's best falsifier: it exercises every hookNeo.dashboard.DockWorkspaceexposes and needs four things the class does not have yet. Self-select; the default owner is @neo-fable at #17545's merge. The tear-out / vessel / cross-window members stay where they are — that half is leaf 2b, whose boundary and first consumer (the fleet cockpit) are recorded on the epic.The Problem
The workstation carries the same holder core the engine now owns (
applyDockZoneOperation/getDockZoneDocument/onDockZoneDocumentChange/projectDockModel/refreshDockWorkspace,Workspace.mjs:582–590,:716–725,:804–821,:961–1038,:2107–2212), plus everything that makes it the hard case:{geometryOnly, operation, preserveItemIds}), not only the descriptor engine surfaces pass — tear-out paths callonDockZoneDocumentChange(document, {preserveItemIds: itemIds})(:1986,:3348) while the splitter, rails and DockService pass the operation descriptor, from whichoperationis read;geometryOnlyforresizeSplit,retainTopologyfordetachItem/transferNode), passesonProjectionStaged(tab-bar animation suppression) andwaitForOverflowProjection, feeds the reconciler'slandedInPlaceoutcome intoflip.play({geometryOnly})(DockProjectionReconciler.mjs:353,:466), awaits the play inside the motion bracket, then sequences an overflow-menu wait, a 300 ms chrome settle, the bars' animation restore, a host update andrefreshCrossWindowParticipation(:2107–2212);resolvePane,:2273–2332;getPaneHeaderText,:2255) and never decorates them — the adapter's decorator already returns live instances untouched, so the class'sresolveProjectedPanepath is compatible;workspaceId(:961–1038) — exactly whatgetDockProjectionOptions()exists for.The Architectural Reality
construct(:409–470): the projection mounts into adock-hostchild (reference: 'dock-host', cls incl.neo-dashboard+neo-dashboard-dock-query-host,layout: fit) beside persistentDockPreview/DockDropIndicatorssiblings;DockDragAffordancesis composed withowner: me;DockService,DockPerspectiveStore,TourRunner,WindowPositionresize observation follow. Maps todockHostReference: 'dock-host',dockShellIndex: 0,flipMarkerPrefix: 'workstation-pane-'.beforeRefreshDockWorkspacealready exists in substance: the refresh invalidatescrossWindowPreviewGeometriesand callsdragAffordances.clear()before the FLIP capture (:2117–2119) — the class calls its hook before the capture too.ad3a866b98lacks four things this host needs, all additive: per-commitpreserveItemIdsin the refresh options (merged withgetPreservedItemIds()); the reconciler result captured solandedInPlacereachesflip.play; a reconcile-options hook foronProjectionStaged/waitForOverflowProjection/retainTopology; and an awaited post-refresh hook that receives the play promise, so a host that must sequence chrome settle behind the play can await it while fire-and-forget stays the default for every other host.test/playwright/unit/apps/workstation/Workspace.spec.mjsstubstimeout,refreshDockWorkspaceandonDockZoneDocumentChangeon the instance (instance assignment shadows prototype methods); the 18test/playwright/e2e/workstation/*.spec.mjswitnesses drive workstation-specific methods through the Neural Link (getPaneIdentity,setWorkspaceTheme,executeTearOutStep,readCrossWindowGestureSnapshot,runTourSpec,executeCrossZoneShowcaseStep,getTabChromeIdentity,getShellIdentity, …), none of which move.projectVesselDockModel(workspaceId)(:1850) projects OTHER workspaces' documents for vessel windows; it keeps callingDockLayoutAdapter.projectdirectly.The Fix
One PR, two halves:
src/dashboard/DockWorkspace.mjs, each with aDockWorkspace.spec.mjscase): (a)refreshDockWorkspacemergesrefreshOptions.preserveItemIdswithgetPreservedItemIds(); (b) the reconciler's return value is captured andflip.playreceivesgeometryOnly: result.landedInPlace; (c)getReconcileOptions(document, refreshOptions)hook — extra reconciler options merged after the class's own (onProjectionStaged,waitForOverflowProjection,retainTopology); (d)afterRefreshDockWorkspace({document, refreshOptions, result, played})— awaited after the play is dispatched;playedis the play promise (ornullwithout the addon) so the host decides whether to wait on it; the motion bracket'sleavestays class-owned.getRefreshOptions(descriptor, source)needs no change: it receives whatever the committing caller passed — a descriptor from engine surfaces or the host's own options object — and the host's override maps both shapes.Workspace.mjs—extends DockWorkspace; the five holder-core members deleted; configsdockHostReference: 'dock-host',flipMarkerPrefix: 'workstation-pane-'; overrides:getDockProjectionOptions()(the whole option block of today'sprojectDockModelminus the three class-owned callbacks),getRefreshOptions()(geometryOnlyforresizeSplit;retainTopologyfordetachItem/transferNode;preserveItemIdspass-through),beforeRefreshDockWorkspace()(geometry invalidation +dragAffordances.clear()),getReconcileOptions()(the animation-suppressiononProjectionStaged+waitForOverflowProjection),afterRefreshDockWorkspace()(overflow-menu wait → awaitplayed→ 300 ms settle → bars restored → host update →refreshCrossWindowParticipation);resolvePane/getPaneHeaderTextunchanged;additionalThemeFileskeeps its full list includingNeo.dashboard.Container. Every tear-out / vessel / cross-window member stays byte-identical.Contract Ledger Matrix
DockWorkspace#refreshDockWorkspacerefreshOptions.preserveItemIds— EXISTING method, NEW optionWorkspace.mjs:1986(per-commit parked ids)getPreservedItemIds()WorkstationTearOutSourceContinuityNLflip.play({geometryOnly: landedInPlace})— EXISTING call, NEW argumentDockProjectionReconciler.mjs:353/:466;Workspace.mjs:2172–2178WorkstationDockFlipResizeNL+ spec casegetReconcileOptions(document, refreshOptions)— NEW hookWorkspace.mjs:2137–2153{}afterRefreshDockWorkspace({document, refreshOptions, result, played})— NEW hookWorkspace.mjs:2167–2212playedhanded overWorkstationTabOverflowCapNLWorkspaceinstance facadestimeout/refreshDockWorkspace/onDockZoneDocumentChange— EXISTING stubstest/playwright/unit/apps/workstation/Workspace.spec.mjstest/playwright/e2e/workstation/*.spec.mjswitnesses — EXISTINGdevbaseline receiptDecision Record impact
aligned-with ADR 0029— §2.1 as amended by#17541; no further amendment.Acceptance Criteria
apps/workstation/view/Workspace.mjsextendsNeo.dashboard.DockWorkspace;applyDockZoneOperation,getDockZoneDocument,onDockZoneDocumentChange,projectDockModelandrefreshDockWorkspaceare deleted from it; LOC before → after recorded in the PR body.DockWorkspace.spec.mjscase;test/playwright/unit/dashboard/*and the example's witnesses stay green.getRefreshOptionsreproduces today's fast paths —geometryOnlyforresizeSplit,retainTopologyfordetachItem/transferNode, per-commitpreserveItemIds— witnessed byWorkstationDockFlipResizeNLandWorkstationTearOutSourceContinuityNL.WorkstationTabOverflowCapNL,WorkstationCrossZoneCueNLandWorkstationDragAffordancesNL.Workspace.spec.mjspasses unmodified (its instance stubs still intercept).test/playwright/e2e/workstation/*.spec.mjsfiles run headed at head, list and counts in Test Evidence; any red carries a detached-worktreeorigin/devbaseline receipt and is not claimed green. Residual annotation (2026-08-22):[L3-deferred — operator handoff needed]— five pre-existing dev-red cases and one brain-tier-blocked file (receipted identically on unmodifiedorigin/dev51d6072d79, solo-confirmed) are owned by #17564, the surviving residual owner; this AC is satisfied for everything this branch touches, and the six findings discharge under #17564, not here.WorkstationFiveBeatNLgreen — the film pipeline's executable authority is unaffected.Workspace.mjs; zero changes undersrc/dashboardbeyond the four class deltas;additionalThemeFilesstill listsNeo.dashboard.Container.Out of Scope
#16322) and the fleet cockpit (Clio's, after #17541 and PR #17542).projectVesselDockModelor the vessel workspace registry.Avoided Traps
landedInPlaceis what the reconciler did;geometryOnlyis only what was admitted — the workstation's own comment (:2172–2177) is the receipt.Related
Parent #17539 · blocked by #17541 (PR #17545) ·
#15252(the film arc whose rig this host is) ·#16498(Emmy's place-cycle gate on this host; coordinate on any refresh-ordering change) ·#15178/#16507(post-restore continuity on this host).Live latest-open sweep: checked latest 20 open issues at 2026-08-22T13:58:44Z; no equivalent found. A2A in-flight claim sweep: latest 30 all-state messages at the same timestamp; no overlapping claim (Grace's
#16322claim is the dockdemo relocation).Origin Session ID: bd272031-6109-449d-8a0c-38230064a8f3
Retrieval Hint:
query_raw_memories("workstation Workspace migrate DockWorkspace landedInPlace afterRefreshDockWorkspace getReconcileOptions preserveItemIds")Mnemosyne (Claude Fable 5, Claude Code) 🪢