Context
#13158 names named perspectives as one of the remaining QT-parity docking polish leaves. The saved-layout foundation is now in place: #13147 defined fail-closed saved layout helpers, and #13153 hardened saved-layout metadata framing. What is still missing is the smallest model-level collection layer that can hold multiple named neo.harness.dockLayout.v1 wrappers and switch the active one without choosing a storage backend or UI.
This ticket deliberately stays below the visual switcher and persistence backend. It gives later Agent Harness / Portal workspace UI slices a pure, unit-testable substrate for named layout perspectives.
The Problem
Neo.dashboard.DockZoneModel can create and restore a single saved-layout wrapper, but callers still have to invent their own collection shape for multiple named perspectives. If every consumer picks its own {layouts, activeLayoutId} convention, the switcher, import/export, and storage leaves will drift before the UI even lands.
Named perspectives should be a JSON-first model concern first: a validated collection of saved-layout wrappers, one active id, and pure semantic operations to upsert, remove, and select layouts. Storage and UI can then consume the same contract instead of encoding persistence-specific assumptions.
The Architectural Reality
src/dashboard/DockZoneModel.mjs owns the neo.harness.dockZone.v1 model helpers and the neo.harness.dockLayout.v1 saved-layout wrapper helpers.
learn/agentos/HarnessDockZoneModel.md already documents layoutId, title, revision, JSON-only saved layouts, and named-perspective demand under #13158.
- The correct layer is still
src/dashboard/: the helpers are pure model operations over dock layout JSON, not harness-app pane registry code and not a core src/layout/Dock lift.
- The storage backend is intentionally undecided. This ticket must not touch
localStorage, Memory Core, app preferences, or a rendered switcher.
The Fix
Add pure DockZoneModel helpers for a versioned named-layout collection around existing saved-layout wrappers:
- create/validate a
neo.harness.dockLayoutCollection.v1 collection;
- upsert a saved layout by its
layoutId;
- select an active layout id;
- remove a layout and fail closed when the active layout would become invalid;
- restore the active saved layout through the existing
restoreSavedLayout() path.
Update HarnessDockZoneModel.md with the collection shape and named-perspective boundary. Add focused unit coverage beside the existing saved-layout tests.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
DockZoneModel.LAYOUT_COLLECTION_SCHEMA |
src/dashboard/DockZoneModel.mjs saved-layout schema pattern |
New schema string neo.harness.dockLayoutCollection.v1 wraps multiple saved-layout wrappers |
Unknown schema rejected by validation/restore helpers |
HarnessDockZoneModel.md named perspectives section |
unit: rejects wrong collection schema |
| Named layout collection shape |
neo.harness.dockLayout.v1 wrapper and #13158 named-perspectives leaf |
JSON object with {schema, activeLayoutId, layouts, metadata, revision}; layouts is id-keyed by layoutId |
Invalid/missing layout ids produce errors, not implicit repair |
docs include JSON shape and field rules |
unit: creates collection from valid saved layouts |
| Upsert operation |
Existing pure model operation style in DockZoneModel |
Valid saved layout replaces/adds layouts[layoutId]; optional activation selects it |
Invalid saved layout returns errors and preserves prior collection |
docs operations table |
unit: upsert clone, replacement, activation |
| Select operation |
#13158 named perspectives, active-layout switcher consumer |
selectSavedLayout(collection, layoutId) changes active id only when the id exists |
Missing id fails closed with unchanged collection |
docs operations table |
unit: missing id rejected |
| Remove operation |
Fail-closed restore boundary from #13147/#13153 |
Removing a non-active layout succeeds; removing active layout requires explicit replacement or returns an error |
Active-layout removal without replacement denied |
docs operations table |
unit: active removal fail-closed |
| Active restore |
Existing restoreSavedLayout() helper |
restoreActiveSavedLayout(collection) restores the selected saved-layout wrapper through the existing validator |
Missing/invalid active layout returns errors and no document |
docs named-perspective boundary |
unit: restores selected layout and rejects invalid active id |
| Storage/UI boundary |
#13158 out-of-scope and HarnessDockZoneModel.md ownership boundary |
No browser storage, no UI switcher, no app-specific pane registry |
Later leaves choose storage/rendering against this pure contract |
out-of-scope section |
source diff: only model/docs/unit helpers |
Acceptance Criteria
Out of Scope
- Rendering a layout switcher UI.
- Browser/app storage, Memory Core persistence, or user preferences.
- Auto-hide/pin sidebars, grouped tab drag, or tab overflow menus.
- Any core
src/layout/Dock primitive.
- Cross-window choreography.
Avoided Traps
- Do not encode layout collections in
apps/agentos first; that would force Portal or future consumers to reverse-engineer a harness-app-private shape.
- Do not persist runtime preview geometry,
windowId, live components, callbacks, or credentials in the collection metadata.
- Do not silently pick a different active layout when the selected one is removed; that creates hidden user-workspace loss. Require an explicit replacement.
Related
Parent: #13158
Related: #13147
Related: #13153
Related: #13030
Related: #13012
Decision Record impact: aligned-with ADR 0020.
Release classification: post-release Agent Harness / M3 docking polish; not v13 release-blocking.
Live latest-open sweep: checked latest 20 open issues at 2026-06-14T01:31Z; no equivalent named-layout collection helper ticket found. Closest live/open item is parent epic #13158; closest closed foundations are #13147 and #13153.
A2A in-flight sweep: checked recent 40 A2A messages at 2026-06-14T01:31Z; no [lane-claim] / [lane-intent] overlaps named-layout collection helpers. #13167 Bridge-auth split is owned by Grace and is unrelated to this saved-layout helper lane.
Origin Session ID: 0ed5b1b0-739e-40e5-93e6-21a2e567ec24
Retrieval Hint: "DockZoneModel named layout collection helpers activeLayoutId saved-layout perspectives #13158"
Context
#13158 names named perspectives as one of the remaining QT-parity docking polish leaves. The saved-layout foundation is now in place: #13147 defined fail-closed saved layout helpers, and #13153 hardened saved-layout metadata framing. What is still missing is the smallest model-level collection layer that can hold multiple named
neo.harness.dockLayout.v1wrappers and switch the active one without choosing a storage backend or UI.This ticket deliberately stays below the visual switcher and persistence backend. It gives later Agent Harness / Portal workspace UI slices a pure, unit-testable substrate for named layout perspectives.
The Problem
Neo.dashboard.DockZoneModelcan create and restore a single saved-layout wrapper, but callers still have to invent their own collection shape for multiple named perspectives. If every consumer picks its own{layouts, activeLayoutId}convention, the switcher, import/export, and storage leaves will drift before the UI even lands.Named perspectives should be a JSON-first model concern first: a validated collection of saved-layout wrappers, one active id, and pure semantic operations to upsert, remove, and select layouts. Storage and UI can then consume the same contract instead of encoding persistence-specific assumptions.
The Architectural Reality
src/dashboard/DockZoneModel.mjsowns theneo.harness.dockZone.v1model helpers and theneo.harness.dockLayout.v1saved-layout wrapper helpers.learn/agentos/HarnessDockZoneModel.mdalready documentslayoutId,title,revision, JSON-only saved layouts, and named-perspective demand under #13158.src/dashboard/: the helpers are pure model operations over dock layout JSON, not harness-app pane registry code and not a coresrc/layout/Docklift.localStorage, Memory Core, app preferences, or a rendered switcher.The Fix
Add pure
DockZoneModelhelpers for a versioned named-layout collection around existing saved-layout wrappers:neo.harness.dockLayoutCollection.v1collection;layoutId;restoreSavedLayout()path.Update
HarnessDockZoneModel.mdwith the collection shape and named-perspective boundary. Add focused unit coverage beside the existing saved-layout tests.Contract Ledger Matrix
DockZoneModel.LAYOUT_COLLECTION_SCHEMAsrc/dashboard/DockZoneModel.mjssaved-layout schema patternneo.harness.dockLayoutCollection.v1wraps multiple saved-layout wrappersHarnessDockZoneModel.mdnamed perspectives sectionneo.harness.dockLayout.v1wrapper and #13158 named-perspectives leaf{schema, activeLayoutId, layouts, metadata, revision};layoutsis id-keyed bylayoutIdDockZoneModellayouts[layoutId]; optional activation selects itselectSavedLayout(collection, layoutId)changes active id only when the id existsrestoreSavedLayout()helperrestoreActiveSavedLayout(collection)restores the selected saved-layout wrapper through the existing validatorHarnessDockZoneModel.mdownership boundaryAcceptance Criteria
DockZoneModelexposes pure named-layout collection helpers without choosing a storage backend.HarnessDockZoneModel.mddocuments the named-perspective collection shape and storage/UI boundary.Out of Scope
src/layout/Dockprimitive.Avoided Traps
apps/agentosfirst; that would force Portal or future consumers to reverse-engineer a harness-app-private shape.windowId, live components, callbacks, or credentials in the collection metadata.Related
Parent: #13158 Related: #13147 Related: #13153 Related: #13030 Related: #13012
Decision Record impact: aligned-with ADR 0020.
Release classification: post-release Agent Harness / M3 docking polish; not v13 release-blocking.
Live latest-open sweep: checked latest 20 open issues at 2026-06-14T01:31Z; no equivalent named-layout collection helper ticket found. Closest live/open item is parent epic #13158; closest closed foundations are #13147 and #13153.
A2A in-flight sweep: checked recent 40 A2A messages at 2026-06-14T01:31Z; no
[lane-claim]/[lane-intent]overlaps named-layout collection helpers. #13167 Bridge-auth split is owned by Grace and is unrelated to this saved-layout helper lane.Origin Session ID: 0ed5b1b0-739e-40e5-93e6-21a2e567ec24
Retrieval Hint: "DockZoneModel named layout collection helpers activeLayoutId saved-layout perspectives #13158"