The operator's stated prio-zero for dock layouts is: save the layout, reload the multi-window app with the saved layout, get the same multi-window setup back — and "i am 99% sure this is not the case."
He is right, and the reason is not a missing feature. Every piece is built. The gap is that nothing implements the two seams that connect them, so the whole path is unreachable from any component.
Measured at dev@274b63beac
The engine ships a complete multi-window save/restore chain:
built, unit-tested, fails the ENTIRE restore closed on any invalid slot
plan toward a captured layout
persistence/RestorePlanner.mjs
built
the routing that drives all of it
ai/client/DockService.mjs:184, :379
built
DockService reaches a live app through exactly two holder seams — getDockTopologyDocuments() (ordered committed documents, primary first) and commitDockTopologyDocuments().
Both definitions are stub objects inside DockService's own unit spec. Every reference in the repo lives in exactly two files: the service that requires them (9 hits) and the spec that stubs them (9 hits). dashboard/dock/Workspace.mjs implements neither; apps/workstation implements neither.
So the multi-window restore path is green, validated, and has been exercised only against test doubles. DockService:171 even handles the absence gracefully — it reports that the component "exposes no getDockTopologyDocuments() seam" — which is why this has never surfaced as an error.
Why this is structural, not one missing method
This is the second instance of one shape found tonight. On #18233 the finding was that interaction/Preview and interaction/DragAffordances each describe themselves as "the app-neutral piece every docking workspace composes" and no engine workspace composes either. This is the same defect in the persistence tier: the engine builds the machinery, documents the seam, tests it against stubs, and assembles nothing.
The pattern matters more than either instance. A capability can be fully implemented, fully covered, and still deliver nothing, because coverage against a stub proves the machinery and never the reach. Whatever the fix here, the epic should treat "is there an implementer, or only a requirer?" as a standing question.
Landing-site constraint
There is a standing operator directive on src/dashboard/dock/Workspace.mjs — stop adding to this layer (PR #18213, recorded in its closure comment; see #18208). Adding two methods there is the idiomatic-looking choice and is what the directive forbids.
window/WorkspaceSet.mjs already holds what the read seam needs: ids(), getDocument(workspaceId), and adoptTransfer() for the write half. Whether the seams are answered from the set, from a thin binding, or elsewhere is the implementing PR's decision — but it needs to be made deliberately, and dock/Workspace.mjs is not it.
Acceptance Criteria
RESTATED 2026-09-04 after cross-lab review of PR #18253 (@neo-gpt-emmy). Two ACs were unachievable as written and one was measuring the wrong thing. Both restatements are narrowing, not weakening — each names what it was protecting and what now witnesses it.
AC-1 — RESTATED. The MODEL half closes here; the VIEW half moves to #18272. A dock host captures a multi-window topology and an N-slot commit restores every slot's document atomically — all or none. Witnessed, not inferred.
⚠️ What this ticket can no longer claim: that the restored composition is visible in the second window. TopologySeams converges the primary projection only, and that is structural: all seven workspaceSet.register sites in the repository supply plain-assignment writers, dockModel is not a reactive config, and a vessel's targetState.document is written in five places and read only by its own getDocument. There is no sibling projection to trigger. Building one is the popup/document-ownership question live on D#18224, so it is owned by #18272 rather than decided here by implementation accident.
The original wording ("restore it after a reload, with the same composition") reads as an operator-visible outcome. It was never achievable from a seams PR, and closing #18252 on it would have recorded a screen-level guarantee the code does not make.
AC-2 — RESTATED. Measure concern growth, not line count. The seams' implementation lives outside src/dashboard/dock/Workspace.mjs (window/TopologySeams.mjs, 105 lines), and Workspace.mjs gains only the composition cost — one import plus one mixin config, 7 lines, owning no state, no methods and no configs for the concern.
The original "line count unchanged" forbids the very vehicle the architecture review endorsed: a mixin cannot be composed without a composition line. It was protecting against the god object absorbing another concern, and 7 lines of composition against 105 lines of concern placed elsewhere is that protection satisfied, not evaded. Graded by the Stage-0-gated Ownership test (D#18150): TopologySeams owns no state or lifecycle, so it is judged as a protocol surface — minimal, mandatory, resolved by a named external consumer.
AC-3 — Restore does not replay history. The captured record is a composition state and is applied as one, per the operator's "reloading an app must not walk through the entire history."
AC-4 — A host that implements neither seam behaves exactly as today. Verified: DockService.restoreTopologyPerspective filters for missing seams and returns a structured refusal(...); nothing throws. The capability is additive.
AC-5 — RESTATED to follow AC-1. Mutation-verified: removing the implementation reds the N-slot atomicity arm. A green arm over a capability that resolves to a stub is the exact failure this ticket documents — and that bar now applies to the model half, with the view half's mutation bar carried by #18272.
AC-6 — The windowFingerprint stays shape-only. No window ids, no coordinates — ADR 0029's rule. Verified: model/Persistence.mjs:72 validates it as a JSON record and nothing in the restore path reads geometry.
Contract Ledger
Backfilled 2026-09-04 (@neo-gpt-emmy, RA-2). Three methods enter a contract with this ticket; a future consumer or reviewer detects drift against this table rather than against the diff.
method
direction
contract
getDockTopologyDocuments()
exposed — resolved by DockService
Returns the ordered live committed documents, primary first. A set-less workspace returns [dockModel]; an empty set returns []. The read seam that topology CAPTURE and topology RESTORE share, so both see one ordering.
commitDockTopologyDocuments(documents)
exposed — resolved by DockService
Atomic multi-document write: every registered slot adopts or none does. Returns {errors}; empty means committed. Returns a refusal rather than throwing — restoreTopologyPerspective branches on commit?.errors?.length and does not wrap the call, so a throw escapes the refusal shape as an RPC crash. Converges the primary projection only (see AC-1 / #18272).
workspaceSet.adoptAll(documents)
consumed — from window/WorkspaceSet.mjs
Slot-ordered, both-or-neither, with two-sided compensation on a writer failure. Refuses on slot-count mismatch, a null slot, or a read-only workspace. Absence is a valid state: a single-document workspace has no set, and commitDockTopologyDocuments falls back to the one-document path rather than treating it as an error.
Non-goals
Pixel geometry as durable state. Whether window position/size should persist is D#18224's open question to the operator and is not settled here. This ticket restores composition — which windows exist and what is in them.
Undo/redo across windows. The transaction/history half of the operator's ask is a separate axis; this one is the reload path, which explicitly must not replay history.
Changing TopologyReconciler, RestorePlanner or the capture format. They are built and tested; this is about reaching them.
Decision Record impact
aligned-with ADR 0029 — the docking design record. No new docking concept; this connects seams the record already defines.
Related
#18151 (epic — the same "engine holds it, consumer cannot reach it" thesis) · #18233 / PR #18251 (the same assemble-nothing shape in the drag tier) · #18208 (the layer directive that constrains the landing site) · D#18150, D#18224 (the multi-window architecture discussion and its open operator question)
Sweep before filing
Live latest-20 open at 2026-09-04T01:5xZ — no equivalent. Full-text perspective restore multi-window over open (nothing; #15614 and #13158 are adjacent, neither this). Closed-queue RestorePlanner OR capturePerspective — #14652 (single-window capture, closed), #16315, #16316 (demo-scoped, closed); none covers the missing implementer. A2A claim sweep over the last 14 messages: the only active lane claim is @neo-opus-vega's #18241 (toolbar actions), no overlap. Memory Core rationale sweep on the same nouns surfaced the DockLayouts v13.2 arc, no prior decision on this seam gap. Own-assignment sweep: none of my open tickets covers it.
Authored by Grace (Claude Opus 5, Claude Code). Session 118d8435-8d23-4745-a28a-8d22da918aac.
Context
The operator's stated prio-zero for dock layouts is: save the layout, reload the multi-window app with the saved layout, get the same multi-window setup back — and "i am 99% sure this is not the case."
He is right, and the reason is not a missing feature. Every piece is built. The gap is that nothing implements the two seams that connect them, so the whole path is unreachable from any component.
Measured at
dev@274b63beacThe engine ships a complete multi-window save/restore chain:
model/Persistence.mjs:133captureTopologyPerspective()model/Persistence.mjswindowFingerprintmodel/TopologyReconciler.mjs(712 lines)persistence/RestorePlanner.mjsai/client/DockService.mjs:184, :379DockServicereaches a live app through exactly two holder seams —getDockTopologyDocuments()(ordered committed documents, primary first) andcommitDockTopologyDocuments().Neither has an implementation anywhere.
$ grep -rnE "^\s+(async )?getDockTopologyDocuments\s*\(" src/ apps/ examples/ test/ --include="*.mjs" (none) $ grep -rnE "^\s+(async )?commitDockTopologyDocuments\s*\(" src/ apps/ examples/ test/ --include="*.mjs" test/playwright/unit/ai/client/DockService.spec.mjs:544 test/playwright/unit/ai/client/DockService.spec.mjs:631Both definitions are stub objects inside
DockService's own unit spec. Every reference in the repo lives in exactly two files: the service that requires them (9 hits) and the spec that stubs them (9 hits).dashboard/dock/Workspace.mjsimplements neither;apps/workstationimplements neither.So the multi-window restore path is green, validated, and has been exercised only against test doubles.
DockService:171even handles the absence gracefully — it reports that the component "exposes nogetDockTopologyDocuments()seam" — which is why this has never surfaced as an error.Why this is structural, not one missing method
This is the second instance of one shape found tonight. On #18233 the finding was that
interaction/Previewandinteraction/DragAffordanceseach describe themselves as "the app-neutral piece every docking workspace composes" and no engine workspace composes either. This is the same defect in the persistence tier: the engine builds the machinery, documents the seam, tests it against stubs, and assembles nothing.The pattern matters more than either instance. A capability can be fully implemented, fully covered, and still deliver nothing, because coverage against a stub proves the machinery and never the reach. Whatever the fix here, the epic should treat "is there an implementer, or only a requirer?" as a standing question.
Landing-site constraint
There is a standing operator directive on
src/dashboard/dock/Workspace.mjs— stop adding to this layer (PR #18213, recorded in its closure comment; see #18208). Adding two methods there is the idiomatic-looking choice and is what the directive forbids.window/WorkspaceSet.mjsalready holds what the read seam needs:ids(),getDocument(workspaceId), andadoptTransfer()for the write half. Whether the seams are answered from the set, from a thin binding, or elsewhere is the implementing PR's decision — but it needs to be made deliberately, anddock/Workspace.mjsis not it.Acceptance Criteria
TopologySeamsconverges the primary projection only, and that is structural: all sevenworkspaceSet.registersites in the repository supply plain-assignment writers,dockModelis not a reactive config, and a vessel'stargetState.documentis written in five places and read only by its owngetDocument. There is no sibling projection to trigger. Building one is the popup/document-ownership question live on D#18224, so it is owned by #18272 rather than decided here by implementation accident.src/dashboard/dock/Workspace.mjs(window/TopologySeams.mjs, 105 lines), andWorkspace.mjsgains only the composition cost — one import plus one mixin config, 7 lines, owning no state, no methods and no configs for the concern.TopologySeamsowns no state or lifecycle, so it is judged as a protocol surface — minimal, mandatory, resolved by a named external consumer.DockService.restoreTopologyPerspectivefilters for missing seams and returns a structuredrefusal(...); nothing throws. The capability is additive.windowFingerprintstays shape-only. No window ids, no coordinates — ADR 0029's rule. Verified:model/Persistence.mjs:72validates it as a JSON record and nothing in the restore path reads geometry.Contract Ledger
Backfilled 2026-09-04 (@neo-gpt-emmy, RA-2). Three methods enter a contract with this ticket; a future consumer or reviewer detects drift against this table rather than against the diff.
getDockTopologyDocuments()DockService[dockModel]; an empty set returns[]. The read seam that topology CAPTURE and topology RESTORE share, so both see one ordering.commitDockTopologyDocuments(documents)DockService{errors}; empty means committed. Returns a refusal rather than throwing —restoreTopologyPerspectivebranches oncommit?.errors?.lengthand does not wrap the call, so a throw escapes the refusal shape as an RPC crash. Converges the primary projection only (see AC-1 / #18272).workspaceSet.adoptAll(documents)window/WorkspaceSet.mjscommitDockTopologyDocumentsfalls back to the one-document path rather than treating it as an error.Non-goals
TopologyReconciler,RestorePlanneror the capture format. They are built and tested; this is about reaching them.Decision Record impact
aligned-with ADR 0029— the docking design record. No new docking concept; this connects seams the record already defines.Related
Sweep before filing
Live latest-20 open at 2026-09-04T01:5xZ — no equivalent. Full-text
perspective restore multi-windowover open (nothing; #15614 and #13158 are adjacent, neither this). Closed-queueRestorePlanner OR capturePerspective— #14652 (single-window capture, closed), #16315, #16316 (demo-scoped, closed); none covers the missing implementer. A2A claim sweep over the last 14 messages: the only active lane claim is @neo-opus-vega's #18241 (toolbar actions), no overlap. Memory Core rationale sweep on the same nouns surfaced the DockLayouts v13.2 arc, no prior decision on this seam gap. Own-assignment sweep: none of my open tickets covers it.Authored by Grace (Claude Opus 5, Claude Code). Session 118d8435-8d23-4745-a28a-8d22da918aac.