Part of #18474.
Context
Stage one of D#18468 lets a standalone DockWorkspace consume pane declarations and a nested initial arrangement. This leaf delivers only the pure model conversion and its model documentation. The host integration is a separate dependent leaf.
The Problem
The committed document is a flat, id-keyed graph. A consumer currently maintains structural names and references even when describing only an initial layout. The authoring conversion must remove that burden without introducing a second runtime document, changing the persisted schema, or losing valid catalog state.
The Architectural Reality
Source baseline: Engine dev b537d0a3c8101c7d9fcb2a23317b6133d8d356ec.
- WorkspaceDocument owns the document/item/node allowlists, validation, normalization and node-ID generation. Its model directory also contains Operations, Persistence and TopologyDiff.
- normalizeTree preserves catalog-only items and surviving node IDs. An empty edge-zone and a tabs root are legitimate shapes.
- Persistence rejects non-JSON values. Module functions and live pane/store objects remain runtime configuration.
- The reviewed scratch helper/corpus plus declared corrections are prior art, not shipped implementation. Use both; the earlier attachment alone lacks the explicit-ID reservation repair.
The Fix
Place the conversion in src/dashboard/dock/model. Before implementation, apply structural-pre-flight to decide between methods on WorkspaceDocument and a cohesive model sibling; do not place parsing in Workspace, projection, or a consumer. The public model helper names are chosen and documented in this leaf; callers must not depend on scratch filenames.
The input is a panes record and one nested root. Pane keys identify catalog items. Tabs accept an items array and activeItemId; splits accept children, orientation and sizes; edge nodes contain edge children with extent/resizable beside the child. A string or array is a tabs shorthand. An explicit type remains available, including for empty edge nodes; all three node types are legal roots. Structural id is optional.
For example, panes: {nav: {header: {text: 'Navigator'}}, editor: {header: {text: 'Editor'}}} with zones: {left: {items: ['nav'], extent: 0.2}, center: 'editor'} produces a root edge-zone, two tabs nodes and JSON catalog records. Structural node IDs are generated; the author supplies none. The host, not this converter, constructs the corresponding components.
Lower pane header.text to title, default componentRef/title from the pane key, and preserve accepted JSON catalog fields. Ordinary component configuration is not copied into the wire. Export retains the complete catalog, including unplaced items, and preserves explicit original IDs when requested. Define the comparison precisely as equality after the documented catalog defaulting and tree normalization; do not claim byte identity for records that omitted defaulted fields.
Reserve explicit IDs before allocating generated ones. Reject duplicate/reserved collisions with an authored path. Generated names use an occupancy suffix whenever needed; delimiter-joined pane names are not a uniqueness proof.
Contract Ledger Matrix
| Target surface |
Source of authority |
Required behavior |
Refusal / fallback |
Docs |
Evidence |
| Nested authoring input |
D#18468 stage-one grammar; WorkspaceDocument node keys |
All three roots, nested edges/splits/tabs, optional IDs and shorthands lower to one valid document |
Invalid shape or unknown pane names report the authored path; no partial output |
Model helper JSDoc; DockZoneModel authoring section |
Positive grammar cases and malformed-input controls |
| Structural identity |
WorkspaceDocument.genId and document reference integrity |
Explicit IDs reserved first; generated IDs deterministic and collision-safe |
Duplicate explicit ID names its path; never overwrite a prior node |
Naming/defaulting contract |
Nested ancestors, delimiter collision, mixed explicit/generated and root-name cases |
| Catalog/export |
WorkspaceDocument item allowlist and normalizeTree |
Preserve catalog-only items and accepted fields; equality after declared defaults; optional preserved node IDs |
Non-JSON catalog payload rejected; runtime constructor configuration stays outside document |
Export/defaulting contract |
Detached versus closed; minimal records; preserved-ID and canonical comparisons |
| Persisted wire |
Persistence validation |
neo.dock.zone.v1 unchanged; conversion output passes existing save/read validation |
Existing fail-closed schema behavior retained |
learn/agentos/DockZoneModel.md |
Save/read control plus model corpus |
Discussion Criteria Mapping
Source criteria 1, 4, 5 and 6: the authoring model, named errors, reduced consumer structural plumbing and unchanged wire. Host/runtime behavior belongs to #18476; guides and example are separate leaves.
Acceptance Criteria
Validation
Load the repository unit-test skill before authoring/running tests. New coverage belongs with the existing dashboard model specs. After implementation, run:
npx playwright test -c test/playwright/playwright.config.unit.mjs unit/dashboard --reporter=list
Report exact-head results and the named red controls in the PR. This leaf has no rendered UI AC; model execution and schema evidence cover its boundary.
Dependencies and Decision Record
No implementation blocker. This leaf is the prerequisite of the host leaf; attach it natively to #18474.
Decision Record: REQUIRED for the parent capability. Decision Record impact: aligned-with ADR 0029's JSON-first model; the consuming-surface amendment ships with #18476 before host adoption, not as a model wire-format change.
Out of Scope
Workspace configuration/lifecycle, module loading, pane-instance ownership, example migration, storage, perspectives, topology partition, popup windows and reactive/bound panes.
Avoided Traps
No consumer-visible compile step; no second persisted catalog; no mandatory structural IDs; no root edge wrapper imposed on a tabs/split root; no prototype transplanted without its corrections; no universal-totality claim inferred from a finite generated corpus.
Related and Pickup
Related: #18474; D#18468.
unowned-rationale: This complete model leaf is available for peer self-selection; its dependent host work does not imply assignment.
Retrieval Hint: D18468 stage-one lowering, explicit ID prepass, equality after defaulting; public helper attachment 18347868 and correction 18348464.
Origin Session ID: 4f9b7bbe-9dbc-4d36-a167-38fe0d288113
Creation checks
Live latest-20 open issues and latest-30 all-state A2A checked immediately before filing; #18474 is the approved parent, no equivalent model leaf or competing filing found. Vega explicitly confirmed he is not filing this packet. MC problem sweep: handwritten structural IDs / resolver / constructor burden; no additional actionable ruling surfaced. Own-assignment sweep: eight open including #18474; #18303's Group/history work remains separate. Structure-map command is unavailable in Engine (no output, exit 1); model placement is grounded in WorkspaceDocument, Persistence and TopologyDiff's existing pure model role. Structural fast-path: a same-directory pure model sibling, if chosen, follows those statics; no novel directory is prescribed.
Part of #18474.
Context
Stage one of D#18468 lets a standalone DockWorkspace consume pane declarations and a nested initial arrangement. This leaf delivers only the pure model conversion and its model documentation. The host integration is a separate dependent leaf.
The Problem
The committed document is a flat, id-keyed graph. A consumer currently maintains structural names and references even when describing only an initial layout. The authoring conversion must remove that burden without introducing a second runtime document, changing the persisted schema, or losing valid catalog state.
The Architectural Reality
Source baseline: Engine dev b537d0a3c8101c7d9fcb2a23317b6133d8d356ec.
The Fix
Place the conversion in src/dashboard/dock/model. Before implementation, apply structural-pre-flight to decide between methods on WorkspaceDocument and a cohesive model sibling; do not place parsing in Workspace, projection, or a consumer. The public model helper names are chosen and documented in this leaf; callers must not depend on scratch filenames.
The input is a panes record and one nested root. Pane keys identify catalog items. Tabs accept an items array and activeItemId; splits accept children, orientation and sizes; edge nodes contain edge children with extent/resizable beside the child. A string or array is a tabs shorthand. An explicit type remains available, including for empty edge nodes; all three node types are legal roots. Structural id is optional.
For example,
panes: {nav: {header: {text: 'Navigator'}}, editor: {header: {text: 'Editor'}}}withzones: {left: {items: ['nav'], extent: 0.2}, center: 'editor'}produces a root edge-zone, two tabs nodes and JSON catalog records. Structural node IDs are generated; the author supplies none. The host, not this converter, constructs the corresponding components.Lower pane header.text to title, default componentRef/title from the pane key, and preserve accepted JSON catalog fields. Ordinary component configuration is not copied into the wire. Export retains the complete catalog, including unplaced items, and preserves explicit original IDs when requested. Define the comparison precisely as equality after the documented catalog defaulting and tree normalization; do not claim byte identity for records that omitted defaulted fields.
Reserve explicit IDs before allocating generated ones. Reject duplicate/reserved collisions with an authored path. Generated names use an occupancy suffix whenever needed; delimiter-joined pane names are not a uniqueness proof.
Contract Ledger Matrix
Discussion Criteria Mapping
Source criteria 1, 4, 5 and 6: the authoring model, named errors, reduced consumer structural plumbing and unchanged wire. Host/runtime behavior belongs to #18476; guides and example are separate leaves.
Acceptance Criteria
AC-1: Implement and document pure lowering/export without importing host, projection, Group, history or storage owners; inputs remain unchanged.
AC-2: Ship the named corpus: representative dock example, Demo A, Demo B and Workstation documents; detached versus closed catalog; empty nested edge; tabs root; nested splits sharing a first pane; delimiter collision; explicit/generated collision; duplicate/root explicit IDs; minimal item records. The seeded property corpus must exercise its documented domain, not be described as proof of every possible input.
AC-3: Both comparison directions pass: retained-ID export equals normalized input after documented defaults; IDs-dropped export preserves the same canonical structure/catalog after those defaults. A complete-field control remains unchanged.
AC-4: Duplicate explicit IDs fail before lowering can overwrite data. Mixed explicit/generated IDs preserve every authored pane; include a positive control because current document validation alone does not detect the scratch overwrite case.
AC-5: Authored errors identify a pane or config path. Valid output passes WorkspaceDocument and Persistence checks; executable pane configuration never enters the persisted record.
AC-6: Update learn/agentos/DockZoneModel.md and model JSDoc in this PR. Add the authoring/defaulting/export contract and remove kind from the required-field presentation without removing its existing optional wire field.
AC-7: The PR includes a deletion-ledger row for the normalizer addition: it replaces the consumer's manual structural graph, which #18477 removes. Name the concrete consumer responsibility and its owning leaf; do not claim a pending deletion has already shipped.
Validation
Load the repository unit-test skill before authoring/running tests. New coverage belongs with the existing dashboard model specs. After implementation, run:
Report exact-head results and the named red controls in the PR. This leaf has no rendered UI AC; model execution and schema evidence cover its boundary.
Dependencies and Decision Record
No implementation blocker. This leaf is the prerequisite of the host leaf; attach it natively to #18474.
Decision Record: REQUIRED for the parent capability. Decision Record impact: aligned-with ADR 0029's JSON-first model; the consuming-surface amendment ships with #18476 before host adoption, not as a model wire-format change.
Out of Scope
Workspace configuration/lifecycle, module loading, pane-instance ownership, example migration, storage, perspectives, topology partition, popup windows and reactive/bound panes.
Avoided Traps
No consumer-visible compile step; no second persisted catalog; no mandatory structural IDs; no root edge wrapper imposed on a tabs/split root; no prototype transplanted without its corrections; no universal-totality claim inferred from a finite generated corpus.
Related and Pickup
Related: #18474; D#18468. unowned-rationale: This complete model leaf is available for peer self-selection; its dependent host work does not imply assignment. Retrieval Hint: D18468 stage-one lowering, explicit ID prepass, equality after defaulting; public helper attachment 18347868 and correction 18348464.
Origin Session ID: 4f9b7bbe-9dbc-4d36-a167-38fe0d288113
Creation checks
Live latest-20 open issues and latest-30 all-state A2A checked immediately before filing; #18474 is the approved parent, no equivalent model leaf or competing filing found. Vega explicitly confirmed he is not filing this packet. MC problem sweep: handwritten structural IDs / resolver / constructor burden; no additional actionable ruling surfaced. Own-assignment sweep: eight open including #18474; #18303's Group/history work remains separate. Structure-map command is unavailable in Engine (no output, exit 1); model placement is grounded in WorkspaceDocument, Persistence and TopologyDiff's existing pure model role. Structural fast-path: a same-directory pure model sibling, if chosen, follows those statics; no novel directory is prescribed.