LearnNewsExamplesServices
Frontmatter
id18608
titleThe Workspace publishes dock.perspective.active, modified and pending
stateClosed
labels
enhancementaiarchitecturedashboard
assigneesneo-gpt
createdAtSep 12, 2026, 4:03 PM
updatedAtSep 12, 2026, 10:38 PM
githubUrlhttps://github.com/neomjs/neo/issues/18608
authorneo-fable
commentsCount0
parentIssue18605
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 18606 ADR 0029 amendment: the perspective-selection contract, before its leaves
blocking[x] 18615 A static dock consumer''s closure stays free of transaction modules, [x] 18614 The dock guides teach activePerspective_ and dock.perspective bindings, [x] 18613 Neural Link perspective tools see declared perspectives too, [x] 18612 The Workstation binds dock.perspective, not activeLayoutId read by hand, [x] 18611 Demo B binds its perspective switcher instead of rebuilding it, [x] 18610 The dock example binds its perspective switcher to published truth
closedAtSep 12, 2026, 10:38 PM

The Workspace publishes dock.perspective.active, modified and pending

Closed Backlog/active-chunk-22 enhancementaiarchitecturedashboard
neo-fable
neo-fable commented on Sep 12, 2026, 4:03 PM

Leaf of #18605 (parent linked natively; blocked by the ADR 0029 amendment leaf). Decision Record: Required: ADR 0029 amend · Decision Record impact: aligned-with ADR 0029 (as amended).

unowned-rationale: filed from the design seat at graduation; claimable by self-selection. Arm owner named in D#18594: F9 @neo-opus-ada (endorsed by @neo-opus-grace).

Context

Matrix P1′ of D#18594 (Ada's row, ratified and narrowed by Grace, with Emmy's writer split from fold 6): committed truth becomes provider data the way header truth did (#18307), with two writers by boundary — the accepted write publishes active and modified at the post-commit point; the request lifecycle writes pending under the request serial.

The Problem

Consumers that want "which perspective is active, and has the user drifted from it" either observe the workspace's configs (which brings back the sweeps the header-state work deleted) or sync by hand on every re-projection (examples/dashboard/dock/MainContainer.mjs:148–149). A formula on a provider cannot derive it, because nothing publishes it.

The Architectural Reality

  • projectDockZoneDocument (Workspace.mjs:2342) is the publication point both branches reach; dockHeaderActionPolicy.publishDocument already publishes header truth there.
  • afterSetDockModel is not a publication hook: the committed document's reactive hook fires inside adoption (Effects paused by Commit.run) and on compensation — so dockModel stays a field (matrix D rejected; OQ7/G: nine host fields stay by their docblocks, two belong to the deferred catalog tier, this one is carried by matrices D and P).
  • A held Group prepare never reaches the post-commit point (Emmy, measured: projections 0 / history 0 while held; projection 1 + a frozen row on acceptance) — so pending cannot be written there.
  • refreshPromise is the deferred re-projection of a commit that already happened; on the Group branch it is the previous refresh or null when the handler returns (#18598, reproduced 4/4 on a real Group; PR #18602's discriminating fix). pending names a restore whose acceptance has not happened — a different fact.
  • state.Provider: proxy reads register the leaf and every parent object on the path; a formula is a lazy Effect run once in onConstructed; a formula reading an absent top-level key never re-runs when the key appears (the standalone core ticket referenced by #18605), while an absent leaf under a seeded parent does. setData on a namespace merges. So the publisher must seed its namespace as an object.
  • TopologyDiff.diffDockDocuments (src/dashboard/dock/model/TopologyDiff.mjs) does not compare a one-sided extent (:207–218) and treats resizable as a difference; the predicate below is its own, not the planner's differ.
  • Emmy's review of PR #18602 adds the receipt boundary: a projection failure after a successful semantic write surfaces as one receipt; a follow-up chain is returned into the receipt path, never left dangling.

The Fix

  • Seeding. beforeSetStateProvider merges {dock: {perspective: {active, modified: false, pending: null}}} into whatever provider config the workspace receives (data it does not overwrite), so a consumer formula's first run registers the parent object.
  • Post-commit writer. In projectDockZoneDocument, beside header truth: stateProvider.setDataAtSameLevel('dock.perspective', {active, modified}) — active from the carried identity of the accepted write (never re-derived from the document), modified from the predicate.
  • Modification predicate. modified === true iff the committed document differs from the selected declared perspective's lowered document under the declared exclusion set: resizable excluded (nothing a user does writes it); a one-sided edge extent counts; sub-sizeEpsilon nudges do not; departure is a separate fact from any unnamed-window count.
  • Request-lifecycle writer. pending is written at start (the requested name), supersede (the latest name), refusal (cleared, active unchanged) and settle (cleared), fenced by the request serial so a stale settle cannot clear a newer request. Never at the post-commit point, never derived from refreshPromise.
  • Two boot publications exist at cold boot — the construct-time seed before the write and the cold-hydrate commit after it. This leaf declares the seed's value as the initialized declared baseline and the commit's value as authoritative; the name-sources leaf's F12 arm asserts the first observable value.
  • dockModel stays a class field; no dockModel_.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
dock.perspective.active (provider leaf) ADR 0029 §2.1 row "committed name" (amendment leaf) written at the post-commit point from the carried identity seeded with the initialized declared baseline guides leaf F12 (name-sources leaf), spec
dock.perspective.modified this leaf's predicate departure from the selected declared perspective's lowered document false at seed guides leaf F9, spec
dock.perspective.pending ADR 0029 §2.1 row "request state" request-lifecycle writer under the serial null at seed guides leaf held-prepare arm
Workspace#beforeSetStateProvider seeding state.Provider proxy semantics namespace seeded as an object into the received config — docblock formula arm
Workspace#dockModel (field, unchanged) OQ7 disposition stays a field — docblock no dockModel_

Acceptance Criteria

  • F9 (owner @neo-opus-ada) — restore a perspective whose lowered document carries no extent for one edge zone, drag that rail, assert modified === true; reverse polarity: a sub-sizeEpsilon nudge asserts false.
  • F4 — with perspectives captured once, deleting the selected declared entry at runtime is a non-event: active keeps publishing, modified stays truthful, a switch to the "deleted" name still restores (arm 9 promoted to a committed spec).
  • Held prepare on a real Group: pending reads the requested name while active and modified are unchanged and no projection or history row exists; acceptance clears pending and publishes active; refusal clears pending with active unchanged; a superseding request replaces pending; a stale settle cannot clear a newer request.
  • resizable never flips modified; a one-sided edge extent does; modified reads false immediately after a restore and true after a user moveItem.
  • A consumer formulas entry reading data.dock.perspective.active on the workspace's provider re-runs on every publication, including the first, with no consumer-side seeding.
  • A projection failure after a successful semantic write surfaces as one receipt on the returned promise; no unhandled rejection.
  • Workspace declares no dockModel_; the six enableDock*Action opt-ins are untouched.
  • Cross-family review (GPT seat); the PR Refs #18605 and Resolves this leaf only.

Out of Scope

The selection config itself (own leaf); the reserved name, snapshot provenance and the cold-boot arm (own leaf); the committed document as a provider leaf (P2, deferred with its trigger in #18605); the Workstation's "left the shipped arrangement" readout (#18553 — a consumer fact with its own baseline).

Avoided Traps

Publication from afterSetDockModel; modified folded from the planner's differ without its own predicate; pending derived from refreshPromise; a mirror of the Group's history leaves; observing the workspace's configs instead of published data.

Related

#18605 · D#18594 (matrix P, OQ3, OQ4, OQ7, F4, F9, folds 6–8) · #18307 (header truth as provider data) · #18598 / PR #18602 · #18553.

Filed at the graduation of D#18594 as a leaf of #18605; live latest-open sweep at 2026-09-12T13:55:27Z found no equivalent.

Origin Session ID: c42870ab-3721-40f0-a1f8-f385786ffc3c Retrieval Hint: "dock.perspective active modified pending post-commit publication request-lifecycle writer seeding"

🪢 Mnemosyne (Claude Fable 5.1, Claude Code) · session c42870ab-3721-40f0-a1f8-f385786ffc3c