Context
Epic #17539 lifted the dock workspace host into the engine class Neo.dashboard.DockWorkspace (O-1, #17541 / PR #17545), and ADR 0029 §2.1 was amended with it — the record's status line now reads "Amended 2026-08-22 (#17541; §2.1): the normative workspace host becomes the engine class Neo.dashboard.DockWorkspace."
Two hosts have migrated (examples/dashboard/dock/MainContainer.mjs:96 and apps/workstation/view/Workspace.mjs:70). The two relocated demos have not, and #17539's O-4 row has been held as BLOCKER pending #16322.
That hold is released (sequencing ruling): the relocation event is complete — apps/agentos/childapps/ contains missioncontrol and widget only — and none of #16322's four open ACs can move these files again. This ticket is the first of O-4's two leaves.
The Problem
#17539's Out of Scope states that the interval between relocation and migration is protected:
the relocated demos carry an in-file marker pointing at this epic as the successor shape, so the interval mints no new copies
No such marker exists. Verified three ways: 17539 across every file type under examples/dashboard/ (0 hits); successor/migration prose across examples, apps, learn for .mjs/.md/.html (0 hits); both hosts' top-of-file docblocks read directly.
Demo A is worse than unmarked — it asserts the status the ADR retired. examples/dashboard/choreography/DemoAWorkspace.mjs:23:
This class is the normative workspace-ownership pattern (the reducer-container the docking design record fixes as canonical)
True when §2.1 said pattern; false since the amendment. A reader of the showcase example is currently told that a hand-rolled extends Container host is the canonical shape to copy — the exact copy-minting the marker existed to prevent, asserted rather than merely unmarked.
The Architectural Reality
The epic frames this migration as "a parent swap … with spec stubs by method name surviving through inheritance." The names survive; the signatures do not. Measured against dev:
| Member |
DemoAWorkspace |
DockWorkspace |
Engine call site |
resolvePane |
(componentRef) :429 |
(itemId, item) :632 |
this.resolvePane(itemId, item) — DockWorkspace.mjs:649, :660 |
refreshDockWorkspace |
(document) :359 |
(tabInsertDescriptor, document, refreshOptions) :533 |
me.refreshDockWorkspace(tabInsertDescriptor, document, refreshOptions) — DockWorkspace.mjs:464 |
projectDockModel |
(resolveComponentRef, document) :335 |
(tabInsertDescriptor, itemResolver, document) :483 |
me.projectDockModel(tabInsertDescriptor, …) — DockWorkspace.mjs:556 |
onDockZoneDocumentChange |
(document) :260 |
(document, descriptor, source) :452 |
— |
A bare parent swap leaves Demo A's overrides in place with first-positional-argument collisions in three of the four: the engine hands tabInsertDescriptor where Demo A reads document, and itemId where it reads componentRef. Nothing throws — the panes resolve to the wrong thing and the projection refreshes against a descriptor. This is the failure direction that costs most, because it is not observed.
examples/dashboard/dock/MainContainer.mjs is the precedent for the correct shape: it inherits applyDockZoneOperation, getDockZoneDocument, onDockZoneDocumentChange, projectDockModel and refreshDockWorkspace outright, overriding only resolvePane(itemId, item) (:212) and the beforeRefreshDockWorkspace(document, refreshOptions) hook (:187).
Demo A's tour surface (createTourBar, onTourBeat/onTourScene/onTourComplete/onTourError, startTour, setTourCaption, setPipProgress) is app-owned screenplay code and is untouched by this — it is what the example exists to show.
The Fix
Demo A migrates — examples/dashboard/choreography/DemoAWorkspace.mjs:
class DemoAWorkspace extends DockWorkspace (:48), construct/destroy calling super.
- Delete the five members the engine now owns; do not leave same-name overrides behind.
- Re-key
resolvePane to the engine's (itemId, item) contract.
- Move the pre-refresh sync into
beforeRefreshDockWorkspace(document, refreshOptions).
- Rewrite the
:23 docblock claim: the ADR fixes Neo.dashboard.DockWorkspace as normative; this class is a consumer of it.
Demo B carries the interval pointer — examples/dashboard/crossWindow/DemoBWorkspace.mjs gets the docblock marker #17539 already claims it has, naming DockWorkspace as the successor shape and its migration as pending. Demo B's own migration is not in this ticket.
Contract Ledger Matrix
| # |
Target surface |
Source of authority |
Before |
After |
Fallback |
Evidence |
| 1 |
DemoAWorkspace base class |
ADR 0029 §2.1 (amended #17541) |
extends Container |
extends DockWorkspace |
none — the pattern it embodied is retired |
DemoAWorkspace.mjs:48; witness MainContainer.mjs:96 |
| 2 |
resolvePane |
DockWorkspace.mjs:632, called :649/:660 |
(componentRef) |
(itemId, item) |
none — engine owns the call |
MainContainer.mjs:212 adopted the same re-key |
| 3 |
refreshDockWorkspace / projectDockModel / getDockZoneDocument / onDockZoneDocumentChange / applyDockZoneOperation |
DockWorkspace.mjs:533/:483/:289/:452/:168 |
locally implemented |
inherited, override deleted |
pre-refresh work moves to beforeRefreshDockWorkspace |
MainContainer.mjs overrides none of the five |
| 4 |
neo-dock-workspace baseCls |
DockWorkspace.mjs:94 |
absent |
present once per workspace |
none |
override anchor per #17539's theme ruling; --dock-* defaults stay on .neo-dashboard |
| 5 |
Demo B docblock |
#17539 Out of Scope |
claims a marker that does not exist |
marker present |
none |
3-layer search recorded above |
Acceptance Criteria
Out of Scope
- Demo B's migration — 4484 LOC coupled to cross-window tear-out, which sequences with O-3's tear-out half, not with Demo A. Bundling produces the ~5k-line diff #17539 already rejected under "Migrating all hosts in the engine PR". Files as O-4's second leaf.
- The FleetCockpit migration (O-3) and the tear-out/vessel engine half.
#16322's residue — the widget disposition and the #16315/#16316 spec reconciliation.
- Any change to
DockWorkspace itself. If Demo A needs a hook that does not exist, that is a #17539 class delta under the hook-admission rule, not a local patch.
DemoBWorkspace decomposition (#15614).
Avoided Traps
- Parent swap alone, trusting "names survive through inheritance." Three of four shared members collide on the first positional argument, and every collision is silent. The signature table above is the falsifier that has to be applied member by member.
- Deleting the false docblock sentence instead of migrating. That removes the wrong claim and leaves the wrong code — the example would still teach the retired pattern by being it.
- Moving
--dock-* defaults onto the new root while the root is right there. Re-opens the invisible-splitter class (#17211) for every unmigrated consumer; .neo-dashboard is the default carrier, the root is an override anchor only (#17539 theme ruling).
- Marking Demo B and calling O-4 progressed. A marker is interval protection, not migration; O-4 closes only when both hosts extend the engine class.
Decision Record impact
aligned-with ADR 0029 — executes the §2.1 amendment on a consumer; changes no ADR authority.
Structure-map gate: N/A — Body-side examples/dashboard/**, no file introduced or relocated, no ai/ or placement surface touched.
Live latest-open sweep: checked latest 20 open issues at 2026-08-23T17:10Z; no equivalent found. A2A in-flight claim sweep: latest 30 all-state messages at the same timestamp; nearest is @neo-gpt-emmy's [lane-claim][#17419] (tab close through model policy, PR #17626) — DockWorkspace-adjacent, disjoint scope, no collision.
Related
#17539 (parent epic, O-4) · #17541 / PR #17545 (the engine class + ADR amendment) · #16322 (the relocation, mine — sequencing released) · #17546 / PR #17565 (O-2 workstation migration precedent) · #15614 (Demo B decomposition) · #17211 (the invisible-splitter defect the theme ruling protects)
Origin Session ID: eb671e6e-ca17-4a53-8069-64fd5885ce84
Retrieval Hint: query_raw_memories("Demo A migrate DockWorkspace signature collision resolvePane itemId componentRef showcase teaches retired pattern")
Context
Epic #17539 lifted the dock workspace host into the engine class
Neo.dashboard.DockWorkspace(O-1, #17541 / PR #17545), and ADR 0029 §2.1 was amended with it — the record's status line now reads "Amended 2026-08-22 (#17541; §2.1): the normative workspace host becomes the engine classNeo.dashboard.DockWorkspace."Two hosts have migrated (
examples/dashboard/dock/MainContainer.mjs:96andapps/workstation/view/Workspace.mjs:70). The two relocated demos have not, and #17539's O-4 row has been held as BLOCKER pending #16322.That hold is released (sequencing ruling): the relocation event is complete —
apps/agentos/childapps/containsmissioncontrolandwidgetonly — and none of #16322's four open ACs can move these files again. This ticket is the first of O-4's two leaves.The Problem
#17539's Out of Scope states that the interval between relocation and migration is protected:
No such marker exists. Verified three ways:
17539across every file type underexamples/dashboard/(0 hits); successor/migration prose acrossexamples,apps,learnfor.mjs/.md/.html(0 hits); both hosts' top-of-file docblocks read directly.Demo A is worse than unmarked — it asserts the status the ADR retired.
examples/dashboard/choreography/DemoAWorkspace.mjs:23:True when §2.1 said pattern; false since the amendment. A reader of the showcase example is currently told that a hand-rolled
extends Containerhost is the canonical shape to copy — the exact copy-minting the marker existed to prevent, asserted rather than merely unmarked.The Architectural Reality
The epic frames this migration as "a parent swap … with spec stubs by method name surviving through inheritance." The names survive; the signatures do not. Measured against
dev:DemoAWorkspaceDockWorkspaceresolvePane(componentRef):429(itemId, item):632this.resolvePane(itemId, item)—DockWorkspace.mjs:649,:660refreshDockWorkspace(document):359(tabInsertDescriptor, document, refreshOptions):533me.refreshDockWorkspace(tabInsertDescriptor, document, refreshOptions)—DockWorkspace.mjs:464projectDockModel(resolveComponentRef, document):335(tabInsertDescriptor, itemResolver, document):483me.projectDockModel(tabInsertDescriptor, …)—DockWorkspace.mjs:556onDockZoneDocumentChange(document):260(document, descriptor, source):452A bare parent swap leaves Demo A's overrides in place with first-positional-argument collisions in three of the four: the engine hands
tabInsertDescriptorwhere Demo A readsdocument, anditemIdwhere it readscomponentRef. Nothing throws — the panes resolve to the wrong thing and the projection refreshes against a descriptor. This is the failure direction that costs most, because it is not observed.examples/dashboard/dock/MainContainer.mjsis the precedent for the correct shape: it inheritsapplyDockZoneOperation,getDockZoneDocument,onDockZoneDocumentChange,projectDockModelandrefreshDockWorkspaceoutright, overriding onlyresolvePane(itemId, item)(:212) and thebeforeRefreshDockWorkspace(document, refreshOptions)hook (:187).Demo A's tour surface (
createTourBar,onTourBeat/onTourScene/onTourComplete/onTourError,startTour,setTourCaption,setPipProgress) is app-owned screenplay code and is untouched by this — it is what the example exists to show.The Fix
Demo A migrates —
examples/dashboard/choreography/DemoAWorkspace.mjs:class DemoAWorkspace extends DockWorkspace(:48),construct/destroycalling super.resolvePaneto the engine's(itemId, item)contract.beforeRefreshDockWorkspace(document, refreshOptions).:23docblock claim: the ADR fixesNeo.dashboard.DockWorkspaceas normative; this class is a consumer of it.Demo B carries the interval pointer —
examples/dashboard/crossWindow/DemoBWorkspace.mjsgets the docblock marker #17539 already claims it has, namingDockWorkspaceas the successor shape and its migration as pending. Demo B's own migration is not in this ticket.Contract Ledger Matrix
DemoAWorkspacebase classextends Containerextends DockWorkspaceDemoAWorkspace.mjs:48; witnessMainContainer.mjs:96resolvePaneDockWorkspace.mjs:632, called:649/:660(componentRef)(itemId, item)MainContainer.mjs:212adopted the same re-keyrefreshDockWorkspace/projectDockModel/getDockZoneDocument/onDockZoneDocumentChange/applyDockZoneOperationDockWorkspace.mjs:533/:483/:289/:452/:168beforeRefreshDockWorkspaceMainContainer.mjsoverrides none of the fiveneo-dock-workspacebaseClsDockWorkspace.mjs:94--dock-*defaults stay on.neo-dashboardAcceptance Criteria
DemoAWorkspace extends DockWorkspace; the five engine-owned members carry no local override.resolvePanematches the engine's(itemId, item)contract, and pre-refresh work runs throughbeforeRefreshDockWorkspace. A control proves the re-key: an arm that fails if a pane is resolved from the wrong argument — a name-only stub passing here would make this AC vacuous, which is the whole hazard.DemoAWorkspace.mjs:23no longer claims normative status; it namesNeo.dashboard.DockWorkspaceas the normative host.DemoBWorkspace.mjscarries the successor marker #17539 asserts is already there..neo-dashboardkeeps the engine--dock-*defaults; no default moves ontoneo-dock-workspace.Out of Scope
#16322's residue — thewidgetdisposition and the #16315/#16316 spec reconciliation.DockWorkspaceitself. If Demo A needs a hook that does not exist, that is a #17539 class delta under the hook-admission rule, not a local patch.DemoBWorkspacedecomposition (#15614).Avoided Traps
--dock-*defaults onto the new root while the root is right there. Re-opens the invisible-splitter class (#17211) for every unmigrated consumer;.neo-dashboardis the default carrier, the root is an override anchor only (#17539 theme ruling).Decision Record impact
aligned-with ADR 0029— executes the §2.1 amendment on a consumer; changes no ADR authority.Structure-map gate: N/A — Body-side
examples/dashboard/**, no file introduced or relocated, noai/or placement surface touched.Live latest-open sweep: checked latest 20 open issues at 2026-08-23T17:10Z; no equivalent found. A2A in-flight claim sweep: latest 30 all-state messages at the same timestamp; nearest is @neo-gpt-emmy's
[lane-claim][#17419](tab close through model policy, PR #17626) — DockWorkspace-adjacent, disjoint scope, no collision.Related
#17539(parent epic, O-4) ·#17541/ PR#17545(the engine class + ADR amendment) ·#16322(the relocation, mine — sequencing released) ·#17546/ PR#17565(O-2 workstation migration precedent) ·#15614(Demo B decomposition) ·#17211(the invisible-splitter defect the theme ruling protects)Origin Session ID: eb671e6e-ca17-4a53-8069-64fd5885ce84
Retrieval Hint:
query_raw_memories("Demo A migrate DockWorkspace signature collision resolvePane itemId componentRef showcase teaches retired pattern")