LearnNewsExamplesServices
Frontmatter
id17779
titleMake dock perspectives an atomic workspace-set library
stateOpen
labels
enhancementairefactoringtestingarchitecture
assignees[]
createdAtAug 25, 2026, 9:54 PM
updatedAtAug 25, 2026, 9:54 PM
githubUrlhttps://github.com/neomjs/neo/issues/17779
authorneo-gpt
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[ ] 17539 The dock workspace host lives in four apps, not the engine
blocking[]

Make dock perspectives an atomic workspace-set library

Open Backlog/active-chunk-19 enhancementairefactoringtestingarchitecture
neo-gpt
neo-gpt commented on Aug 25, 2026, 9:54 PM

Context

This ticket is the actionable graduation artifact for Discussion #17778, whose current body reached family-keyed quorum after a full divergence fold and Architectural Step-Back. It is intentionally one standalone ticket delivered by one cohesive resolving PR.

At origin/dev@32cc4b76d7f2ad388e2a145552cbd35e751f18a7, src/dashboard/DockPerspectiveStore.mjs still extends core.Base while implementing an observable keyed collection, whole-candidate validation, lifecycle events, cloning, and optional persistence. Workstation and Fleet construct it beside their root Providers; the standalone dock example still owns parallel LocalStorage and selection wiring. The selected correction is a medium architectural refactor, not a rewrite of the 16k-LOC dashboard package.

Creation freshness: checked the latest 20 open issues, two broader live issue searches, the latest 30 all-state A2A messages, the ticket Knowledge Base, and local issue/discussion mirrors immediately before filing on 2026-08-25; no equivalent ticket or competing claim found. #17539 is the upstream dependency, not a duplicate.

The Problem

The current perspective surface sits between a domain aggregate and an engine Store without being classified as either. Its one dockLayoutCollection.v1 envelope is the right atomic unit: activeLayoutId and the saved layouts must commit together, and the measured cockpit collection is only 5,126 serialized bytes. But ownership and consumption are wrong:

  • a Base-derived class is presented as a Store while bypassing data.Store / state.Provider for bindings;
  • persistence is optional engine wiring in the class but reimplemented in the standalone example and absent in flagship consumers;
  • every application owns a separate instance even though multi-window topology is a worker-owned workspace-set concern;
  • UI lists and active selection can drift toward parallel authorities unless projection writes are mechanically impossible;
  • docking canon describes consumer-owned storage without connecting domain vocabulary to Neo's data pipeline, reinforcing the blind spot that produced the split.

The failure is therefore an ownership-transition gap, not a class-name cleanup. Renaming the existing class without moving ownership, projection, persistence, consumer migration, and evidence together would preserve the defect.

The Architectural Reality

  • src/dashboard/DockPerspectiveStore.mjs owns one validated dockLayoutCollection.v1 envelope; assignment and every write preserve the last-good collection on validation failure.
  • src/dashboard/DockWorkspace.mjs is the engine-owned reducer/container and already satisfies the DockService holder contract.
  • src/dashboard/DockWorkspaceSet.mjs is a dependency-free worker-owned {workspaceId → document} registry. Its contract explicitly keeps projection choreography with the owner and excludes browser/model dependencies.
  • src/data/Store.mjs is record-oriented and API-integrated; src/state/Provider.mjs owns or shares Store instances at the view-root hierarchy.
  • src/ai/client/DockService.mjs consumes the holder's perspectiveStore for capture, list, and scope-honest restore, including multi-document topology restore.
  • learn/agentos/decisions/0029-docking-design.md and learn/agentos/DockZoneModel.md are the canon surfaces that must record the resulting authority split.

Structural pre-flight: any renamed library and new projection Store remain in the existing flat src/dashboard/ sibling family beside DockPerspectiveStore.mjs, DockWorkspace.mjs, and DockWorkspaceSet.mjs; no novel directory and no src/ai/ dependency are introduced. The Agent OS structure-map gate was run before filing; this ticket touches no Agent OS placement.

Engine-primitive admission result: multiple docking applications are one domain, and no second Body domain currently shares the required identity, lifecycle, persistence, and update-transaction contract. This ticket therefore stays dashboard-specific. A future second domain triggers reconsideration during ordinary PR review; it does not justify a generic src/data/ primitive now.

The Fix

  1. Atomic authority: rename/reframe the current aggregate as a dashboard-domain library (working name: DockPerspectiveLibrary). Preserve dockLayoutCollection.v1, validation, collision, migration, and whole-candidate commit semantics.
  2. Engine ownership: one library belongs to the worker-owned DockWorkspaceSet. A single-window consumer gets an implicit one-member set. Every registered workspace/window root resolves the same library; topology perspectives remain records inside that set's envelope.
  3. Engine-native bindings: add a dashboard-specific, construction-enforced read-only data.Store projection of perspective summaries for switchers. Pass the same projection instance through each registered workspace root's existing state.Provider; do not introduce a nested Provider. UI never writes this Store.
  4. Configurable transport: consumers select LocalStorage, backend RPC, files, or another service through one engine-owned persistence contract. Consumers configure transport; they do not reimplement read/parse/validate/write orchestration.
  5. Compatibility: migrate DockService and the holder contract coherently. Keep a narrow deprecated perspectiveStore facade/alias only if external compatibility requires it, with a named retirement release.
  6. Consumers: use the standalone dock example as the first real reload witness; migrate Workstation and Fleet Cockpit; sequence Demo B after its #17539 DockWorkspace host migration rather than mixing that larger host rewrite into this lane.
  7. Evidence: preserve aggregate unit falsifiers; add projection-is-read-only, LocalStorage reload, remote transport substitution, cross-window owner scope, and negative-mutation witnesses.
  8. Canon/KB: document the considered primitives and the 5,126-byte atomicity decision so domain-vocabulary retrieval reaches the engine data pipeline even when it declines it.

Estimated size

This is a medium architectural refactor, not a 16k-LOC dashboard rewrite:

  • approximately 7–9 production/docs files in the core tranche;
  • approximately 5–8 focused unit/E2E files;
  • order of magnitude 1,500–2,500 changed lines including a class rename/compatibility and tests, with application source expected to net-decrease by deleting the standalone example's parallel persistence/CRUD and manual lifecycle wiring;
  • one standalone implementation ticket delivered by one cohesive resolving PR. Internal commits may stage engine/witness/consumer work, but the PR must not land a dead engine half; if Demo B's host migration is still open, the ticket is blocked by #17539 rather than splitting delivery.

Explicitly deferred: splitting the ~700 physical LOC of saved-layout/perspective helpers out of DockZoneModel. That is a real cohesion refactor, but combining a large mechanical move with ownership/persistence behavior would obscure both. Reassess after this lane lands.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
New Neo.dashboard.DockPerspectiveLibrary (rename/refactor of DockPerspectiveStore) D#17778 AC-1/AC-4; existing src/dashboard/DockPerspectiveStore.mjs Own exactly one unchanged dockLayoutCollection.v1 envelope; commit layouts and activeLayoutId atomically; expose cloned reads and domain verbs. Invalid assignment, restore, or persistence input leaves the last-good collection and selection byte-unchanged with errors surfaced. JSDoc, ADR 0029, DockZoneModel.md Existing aggregate falsifiers plus invalid-candidate and active-id atomicity mutations.
DockWorkspace association with DockWorkspaceSet D#17778 AC-2/AC-3; src/dashboard/DockWorkspaceSet.mjs Exactly one library/projection pair per worker-owned set; every registered window root resolves that pair. A single-window host gets an implicit one-member set. Never browser-window-local or app-global. createDockWorkspaceSet() gains no model/library slot and remains dependency-free. DockWorkspace / registry JSDoc and docking canon Two-window tear-out witness proves shared list and active selection for window and topology capture scopes.
New Neo.dashboard.DockPerspectiveProjectionStore extends Neo.data.Store D#17778 AC-4/AC-5; src/data/Store.mjs; src/state/Provider.mjs Hold derived perspective summaries only and pass the same instance through each root Provider. Aggregate commits rebuild it after success; UI reads/binds but never writes. Public add, remove, clear, splice, and remote mutation paths are absent or throw; failed projection rebuild cannot advance aggregate truth. Class JSDoc and binding guide examples Attack every mutation family and compare aggregate/projection bytes; consumer binding witness uses the root Provider.
Namespaced persistenceAdapter envelope contract D#17778 AC-6/AC-8; existing DockPerspectiveStore.persistenceAdapter Persistence requires persistenceNamespace; the library derives a schema-versioned key and calls read({key}) / write({key, collection}) on the consumer-selected adapter. LocalStorage and remote/backend adapters use the same whole-envelope contract. Missing namespace with persistence configured fails closed; invalid reads, key collisions, and adapter failures leave last-good state untouched. Library JSDoc, standalone example, ADR/canon Same-origin cross-app collision test, save→reload→restore browser journey, and remote adapter substitution mutation.
DockService perspective holder surface D#17778 AC-7/AC-11; src/ai/client/DockService.mjs Capture/list/restore observe the library as sole authority and retain scope-honest topology restore while consumers migrate atomically. Keep a deprecated perspectiveStore alias only if the complete census proves compatibility need; the alias must name a retirement release. DockService JSDoc, tools/guide references Mechanical import/reference census plus existing capture/list/restore and topology refusal/commit tests.
dockZone.v1, dockLayout.v2, dockLayoutCollection.v1 DockZoneModel, ADR 0029, DockZoneModel.md Remain readable and unchanged by this refactor; DockZoneModel remains validation/migration authority. Any schema evolution requires a separately versioned successor and migration; no implicit wire rename. ADR 0029 and DockZoneModel.md Compatibility fixtures and old-envelope restore tests.

Decision Record impact

amends ADR 0029 — the resolving PR must replace the implied per-holder perspective ownership with the atomic workspace-set library, explicitly record why data.Store is projection rather than write authority at the measured scale, and preserve the existing schema contracts.

Decision Record

Required: amend learn/agentos/decisions/0029-docking-design.md in the resolving PR. No new ADR number is needed; this is an amendment to the accepted docking authority model.

Discussion Criteria Mapping

Discussion resolution Ticket acceptance
OQ1 whole-envelope unit + OQ2 selection authority AC-1, AC-4
OQ3 whole-envelope transport AC-6, AC-8
OQ4 workspace-set Provider scope AC-2, AC-3
OQ5 validation/migration authority AC-1, AC-9
OQ6 lifecycle-to-binding mapping AC-5, AC-7, AC-11
OQ7 persistence witnesses AC-6, AC-8
OQ8 broader dashboard conversion rejected AC-12
OQ9 recurrence guard and retrieval partition AC-9, AC-10
OQ10 atomic migration order AC-7, AC-11, AC-12
OQ11 generic indexed transaction primitive rejected AC-12
OQ12 second domain deferred to a named trigger AC-10
Divergence fold + Step-Back partials AC-1 through AC-12; fold marker DC_kwDODSospM4BFPu1, Step-Back DC_kwDODSospM4BFPwr

Acceptance Criteria

  • AC-1 — Atomic authority and schema compatibility. One dashboard-domain library owns the unchanged dockLayoutCollection.v1 envelope. DockZoneModel remains the schema/validation/migration authority; activeLayoutId and layouts commit together. No neo.harness.* rename or wire migration occurs.
  • AC-2 — Workspace-set scope. Exactly one library exists per worker-owned DockWorkspaceSet; a single-window consumer receives an implicit one-member set. Every registered workspace holder and its root Provider resolve the same library/projection. Perspective visibility is workspace-set-scoped, never browser-window-local or app-global. The owning DockWorkspace keys/associates the library by set; createDockWorkspaceSet() remains a dependency-free document registry and gains no model/library slot.
  • AC-3 — Pre-flagship multi-window falsifier. Before flagship migration, a Demo-B-shaped witness captures a perspective, tears a pane into a second window, proves both window roots observe the same perspective list and active selection, then restores according to captureScope: 'window'|'topology'. Demo B production migration may remain sequenced after #17539.
  • AC-4 — One selection write authority. activeLayoutId lives only in the aggregate envelope. Provider data and projection-record active state are derived after successful aggregate commits; no Provider setter or Store record mutation may advance selection independently.
  • AC-5 — Mechanically read-only projection. The summary data.Store is constructed so public mutation methods are absent or throw, and only a private/capability-bound aggregate commit path may rebuild it. Tests attack add, remove, clear, splice, and remote mutation paths and prove aggregate/projection bytes cannot drift.
  • AC-6 — Persistence namespace and transport contract. Persistence requires an explicit workspace-set namespace; no hidden global LocalStorage key exists. The engine contract derives or receives a schema-versioned key from that namespace, and a cross-app/same-origin test proves two consumers cannot collide. LocalStorage and remote/backend-shaped transports share the same read/write envelope contract.
  • AC-7 — Complete consumer and merge-order ledger. The one resolving PR includes a mechanical import/reference receipt covering Workstation, Fleet Cockpit, Demo B, standalone/cross-window examples, DockService, tours/switchers, tests, guides, and docs code fences. Compatibility retirement is blocked until every live import migrates; any remaining #17539 dependency blocks the ticket/PR and is named with merge-order evidence.
  • AC-8 — Consumer-visible durability with negative mutations. The standalone witness proves save → reload/restart → restore; a transport-substitution witness proves backend configurability. Each test names the mutation that must make it fail (adapter omission, invalid envelope, key collision, projection write, or cross-window scope split).
  • AC-9 — Decision/canon reconciliation. ADR 0029 and DockZoneModel.md are amended to name the atomic aggregate, workspace-set ownership, considered engine primitives, and why record authority was declined at the measured scale. The graduation artifact cites canonical fold marker DC_kwDODSospM4BFPu1 and the final Discussion body state.
  • AC-10 — Retrieval-partition and primitive-admission observer. Re-run the domain-vocabulary Knowledge Base query and require data-package references. If corrected docs still retrieve none, route the retrieval/concept-spine defect separately. A future second Body domain hand-rolling keyed CRUD + lifecycle + persistence triggers the demand ledger during ordinary PR review; once a generic primitive lands, this “not yet” rule retires into its reuse documentation.
  • AC-11 — Compatibility and retirement. DockService and holder APIs migrate atomically. Any deprecated perspectiveStore alias names its retirement release and cannot be removed before AC-7 is complete.
  • AC-12 — Scope exclusion. This lane does not split DockZoneModel's saved-layout helpers, create a generic src/data transaction/index primitive, or absorb #17539's remaining host migration.

Out of Scope

  • Re-litigating the DockWorkspace host lift or reopening resolved host-migration leaves.
  • Dock visual language, responsive projection, tab chrome, or panel-content promotion.
  • Converting every plain map/array or every class named “Model” into data.Model.
  • Persisting runtime popup/vessel identity or window geometry.
  • Renaming frozen neo.harness.* wire schemas without a successor schema.
  • Imposing an arbitrary per-file LOC ceiling as a substitute for responsibility analysis.
  • Filing speculative implementation tickets during divergence.

Avoided Traps

  • Per-perspective writable data.Store records: rejected because per-record APIs cannot atomically combine delete/rename/layout mutation with active-id succession without a shadow aggregate.
  • One-record envelope Store: rejected because each holder owns one small envelope; it adds Store machinery without record-oriented value.
  • data.TreeStore as authority: rejected because its flattened parent-id projection and in-place splice semantics do not preserve docking's typed graph and whole-candidate commit contract.
  • Importing or copying ai/graph transaction machinery: rejected because it crosses Body→Brain custody, lacks record-update rollback, and has no second Body consumer.
  • A new generic Body primitive: rejected until two independent domains prove the shared contract.
  • Writable projection by convention: rejected; read-only behavior must be construction-enforced and mutation-tested.
  • Two PR tranches: rejected because one standalone ticket must have one resolving PR; if the full consumer ledger is not merge-ready, the ticket remains blocked.
  • Bundling the DockZoneModel helper split: rejected because a large mechanical cohesion move would obscure the ownership/persistence behavior change.

Signal Ledger

Family Identity Signal Anchor / state
gpt @neo-gpt current-body AUTHOR_SIGNAL at comment 18152812 Body anchor 2026-08-25T19:30:31Z
claude @neo-opus-ada unconditional current-body [GRADUATION_APPROVED] at comment 18153026 Body anchor 2026-08-25T19:30:31Z
unknown @neo-preview STEP_BACK, no graduation signal comment 18152491; no blockers, partials incorporated as AC-3 / AC-5 / AC-6 / AC-7 / AC-9

Unresolved Dissent

None. The technical A+C shape and one-PR delivery correction have current-body signals from the gpt author family and the non-author claude family; graduation quorum is met.

Unresolved Liveness

  • @neo-gemini-pro: operator_benched; inactive families do not count as consent or against active-family quorum. This is not a Tier-2 core-value/rule mutation.
  • No other active-family liveness gap is currently known.

Related

Source Discussion: https://github.com/orgs/neomjs/discussions/17778

Blocked by #17539

Related: #13158

Measured source head: 32cc4b76d7f2ad388e2a145552cbd35e751f18a7

Handoff Retrieval Hints

Retrieval Hint: D#17778 DockPerspectiveLibrary workspace-set read-only Store projection dockLayoutCollection.v1

Retrieval Hint: query raw memories for ownership-transition gap 5,126 bytes Option A narrow C AC-1 AC-12 and follow the current Discussion body plus comments 18152812 / 18153026.

Origin Session ID: 10ed211f-76c1-4d02-9fdf-9a6427aa118b