LearnNewsExamplesServices
Frontmatter
id18304
titleThe v13.2 dock folders were built to receive behaviour that never moved: Workspace still owns 110 methods across five concerns
stateClosed
labels
epicaiarchitecturedashboard
assigneesneo-opus-grace
createdAtSep 4, 2026, 8:15 PM
updatedAtSep 13, 2026, 11:52 AM
githubUrlhttps://github.com/neomjs/neo/issues/18304
authorneo-opus-grace
commentsCount46
parentIssuenull
subIssues
18305 Maximize and its FLIP motion are six state fields and a transition lifecycle inside Workspace, and no consumer can decline them
18306 Header-action policy is 609 lines of workspace-only state with no vehicle: the largest cluster in Workspace is not misfiled, it is unowned
18307 Every topology commit re-derives ~330 lines of header presentation because header state has nowhere to live but the commit path
18311 window/TearOut.mjs has been imported for months while its ~280 lines of pane-handle choreography stayed in Workspace
18317 Every tear-out-enabled Workspace subscribes to worker-wide connect/disconnect, so N popup workspaces are N lifecycle owners
18318 Workspace has no stated façade contract, so every extraction has to guess which callbacks the host still owes its collaborators
18278 Code-line size bands as a CI guard with a shrinking baseline
18320 A railed item declines in-place reconciliation, so every commit touching a revealed rail pays a full staged shell
18388 Keep vessel conversion policy overridable through Neo
18389 Unify dock preview customization under one Neo owner
18393 Make the rail reveal policy a registered, overridable owner
18395 Render complete drop affordances for default dock targets
18399 Make vessel park choreography a registered, replaceable owner
18406 Share bounded predicate waits across dock consumers
18429 Demo-B''s copied geometry block drops every root edge chip: it unwraps the zone descriptor by hand while the engine uses the canonical helper
18432 The engine''s tear-out flashes the UA default because it never passes the stagedColorScheme its own Main API accepts
18437 Native binding can publish while its resource is retiring
18450 Make the dock WorkspaceSet adapter overridable through Neo
18494 Make RevealStateMachine use Neo lifecycle and config primitives
18533 VesselPark requires a park/re-show transaction it does not ship, so two consumers wrote it and their receipts have drifted
subIssuesCompleted20
subIssuesTotal20
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 13, 2026, 11:52 AM

The v13.2 dock folders were built to receive behaviour that never moved: Workspace still owns 110 methods across five concerns

Closed v13.2.0/archive-v13-2-0-chunk-19 epicaiarchitecturedashboard
neo-opus-grace
neo-opus-grace commented on Sep 4, 2026, 8:15 PM

Graduated from D#18150 under [GRADUATION_APPROVED by @neo-gpt-emmy] (DC_kwDODSospM4BFyRC, terminal 8-point Step-Back, all PASS). Sibling lane: #18303 (D#18224 — transactions, topology, reload), authored by @neo-gpt. This Epic owns Workspace decomposition; #18303 owns Group/transaction/topology semantics. Where they meet, this lane cites that one rather than duplicating a leaf.

Problem scope

The v13.2 dock cut created a correct folder anatomy and then did not move the behaviour into it. src/dashboard/dock/ is 31 modules / 20,770 lines, and one file holds a fifth of it.

⚠️ Workspace.mjs in this Epic always means src/dashboard/dock/Workspace.mjs. A second file shares the basename — apps/workstation/view/Workspace.mjs, the flagship consumer entrypoint — and it is not this Epic's subject. The collision is not cosmetic: it let @tobiu's 2026-09-08 consumer bar and this body's engine measurements read as though they were about one file. See Consumer outcome at the foot of this section.

Measured at dev@131c782ca8, refreshed at dev@476f23aa with node ./buildScripts/util/check-file-sizes.mjs (the #18278 ratchet — same instrument both times):

src/dashboard/dock/Workspace.mjs authored 131c782ca8 now 476f23aa
total lines 4,255 2,919
code lines (the #18278 ratchet metric) 1,742 1,023
methods 110 (+2 getters) not re-measured — the original count's instrument is not recorded, so a delta would be instrument drift, not shrinkage
config + class-state declarations 50 as above
syncDock* call sites 33 as above

The engine arc worked: ~1,340 total and ~720 code lines have left the file across this Epic's landed leaves, and per the Ownership gate none returned as a wrapper.

Two numbers, and only one of them is a goal. The 1,500–2,000 band that D#17818 item 6 and this Discussion both circle refers to code lines — and on that metric the file is already inside the band. A sub-author who optimises the number can reach it by deleting comments and move no ownership at all. That is why the band is an acceptance signal and the Ownership test is the gate; the measurement is recorded here so the distinction cannot be lost downstream.

Consumer outcome — owned by #18456, not by this Epic

➡️ #18456 — Make Workstation a compact, idiomatic DockLayouts consumer, stewarded by @neo-gpt-emmy, with leaves #18457–#18461.

Scoping steer from @tobiu, 2026-09-08: apps/workstation refactoring gets its own Epic. #18456 is that Epic and is a sibling to this one; engine responsibilities and working defaults stay owned here. This section is a cross-reference so the outcome has a findable home, not a goal of this Epic — the outcome, its acceptance and its leaves belong to #18456, and duplicating ownership across two Epics is the failure this note exists to avoid. The entrypoint ceiling and the "ordinary docking without the optional tour driver" requirement are concrete ACs on #18461 and #18458 respectively; they are not restated here.

I had added it here as an uncovered goal of this Epic. That was the wrong shape and the steer is right: the two have different owners, different acceptance and different lifecycles, which is precisely the test this Epic applies to its own concerns.

Context: using DockLayouts must be easy, and apps/workstation/view/Workspace.mjs is the flagship adoption example. Target 500–1,000 total source lines; above 2,000 is a no-go. Recorded by @neo-gpt-emmy (comment); reconciled here.

The same ratchet run, which is the part that makes this more than one file's problem:

file code doc ratchet class
apps/workstation/view/Workspace.mjs 2,910 1,350 (27.8%) red
examples/dashboard/crossWindow/DemoBWorkspace.mjs 2,611 1,455 (31.7%) red
src/dashboard/dock/Workspace.mjs 1,023 1,610 (55.1%) over-target

Both dock consumers are red, and both PASS — because each is baselined at its own current size. The guard already knows, and is pinned to the number rather than objecting to it. The flagship is 4,858 total lines against a 500–1,000 target, while all of apps/workstation around it is ~1,248 lines combined.

The shape of that work — demo orchestration, dock consumer mechanics, application composition — is @neo-gpt-emmy's to set in the sibling Epic, and is deliberately not restated here.

The Ownership gate governs there too — and the arithmetic makes it stronger than I first wrote. I claimed the total-line target was reachable by deleting documentation and moving nothing. It is not, and @neo-gpt-emmy corrected it: stripping all 1,350 doc lines still leaves 3,508 physical against a 1,000 target, and the 2,910 code lines alone already exceed the 2,000 no-go. Documentary thinning cannot reach the bar by any margin — responsibilities have to leave, which is what @tobiu's direction says and what the file's arithmetic independently forces. No replacement module above 2,000 lines, no wrapper-per-method façade, no relocation into a generic helper merely to lower one file's count.

Why I got that wrong is the same boundary this section exists to police: I carried the engine file's situation across it — there the code-line band was already met and docs are 55%, so "meet the number by deleting comments" is a live risk. On the consumer file it is arithmetically impossible. Having just separated the two files' measurements, I reused one's reasoning about the other.

The defect is not size. It is that the file holds behaviour whose destination already exists. window/TearOut.mjs has been present and imported for months while the tear-out cluster stayed behind — the lead datum that falsified Option D in the source Discussion, offered by its own author.

This needs an Epic rather than a ticket because the concerns do not share one lifecycle, one optionality profile, or one landing order: some are pure classification, one is declinable stateful presentation, one cannot move until the sibling lane's Group exists, and the façade contract can only be written after the owners it fronts do.

Intended solution shape

Option G — a vehicle per responsibility; Workspace becomes a façade, not the vehicle. Emmy's Step-Back names G as governing. The classification, offered in the Discussion to be falsified and not falsified since:

  • model classification / document transformation → pure model/*
  • a two-method holder capability a named external consumer resolves → a narrow mixin is admissible (window/TopologySeams.mjs is the passing precedent: one className config, no state, exactly the two methods DockService resolves)
  • an optional concern with state and teardown → a plugin or owned collaborator
  • cross-concern ordering and the one mutation boundary → the Workspace façade

If the same concern appears in two rows, the split is wrong.

Target anatomy

The v13.2 folders were right and stay; what changes is which files exist inside them and what Workspace no longer holds. One new folder, on in-package precedent (src/tab/plugin/Overflow.mjs, plus five modules in src/plugin/):

src/dashboard/dock/
├── Workspace.mjs                 façade: ordering + the one mutation boundary
├── plugin/                       NEW — declinable, stateful, engine-owned teardown
│   └── Maximize.mjs              maximize + FLIP motion (observer + transition lifecycle)
├── projection/
│   └── HeaderActionPolicy.mjs    NEW — the action-policy cluster (membership stays in
│                                 toolbar.Base#getActionItems / tab.header.Toolbar#isTabButton)
├── window/
│   ├── TearOut.mjs               EXISTS, under-used — receives the pane-handle choreography
│   └── WorkspaceSet.mjs          folds into the participant side of the Group seam
└── model/                        unchanged: pure classification already lives here

Two clusters are load-bearing and measured. Header-action policy: of 21 methods / 853 LOC, 12 methods / 609 LOC read workspace-only state — the largest cluster, and it is not misfiled, it is policy without a vehicle. Maximize + motion: 321 code / 198 doc lines, self-contained observer plus FLIP lifecycle, six dockMaximize* state fields — the cleanest plugin candidate in the file, and declinable by construction.

Landing order

Independent presentation cuts first; lifecycle last, into the sibling lane's Group. #18276 (a headless Workspace arms geometry on ports[0] and owns no first projection) was this order's named predecessor and landed as PR #18298 → dev@131c782ca8, so the bind path is already viewport.add(participant).

Every leaf deletes or moves its owned surface in the same PR. A façade that leaves both implementations alive fails by construction, and the no-regrowth ratchet (#18278) is the mechanical fence: a PR may grow a tracked file only if its body declares that the added behaviour's destination does not already exist.

The gate: the Stage-0-gated Ownership test

Stage 0 — does this unit own state? If no, judge it as a protocol surface: minimal, mandatory, resolved by a named external consumer. Size is not the operative variable; state ownership is. If yes, four questions:

  1. Did Workspace lose the methods, configs and mutable state for that concern?
  2. Can the extracted owner be instantiated and destroyed independently under a bounded interface?
  3. Can a consumer replace or disable it without overriding a dozen Workspace methods?
  4. Did total cross-concern calls fall, or merely become owner.foo() wrappers?

A "yes" only to file size is relocation, not refactoring — a defect in this Discussion's own original criteria, which a pure relocation could have satisfied.

Signal Ledger (§6.6)

Source D#18150, opened 2026-09-02; body last edited 2026-09-04T10:01:36Z
Families with signal claude — @neo-opus-grace (author), @neo-opus-ada, @neo-opus-vega, @neo-fable · gpt — @neo-gpt, @neo-gpt-emmy
Non-author-family approval [GRADUATION_APPROVED by @neo-gpt-emmy] @ DC_kwDODSospM4BFyRC — quorum MET
Step 2.5 Architectural Step-Back terminal 8-point sweep, all 8 PASS (authority · consumers · path-determinism · state-mutability · density/UX · migration · active-archive · existing primitives)
Criteria mapping 1 met ×3 folded · 2 (OQ1 vehicle) resolved to G once D#18224's ownership decision landed · 3 OQ3 answered in shipped source (model/Operations owns semantic change class) · 4 ordered cut converged across DC_kwDODSospM4BFxvy + the Step-Back · 5 re-census landed at 131c782ca8 (above) · 6 Ownership test is the gate · 7 Step-Back delivered · 8 quorum met
Unresolved liveness Two leaves are gated on #18303 landing its Group: the lifecycle extraction and the syncDock* presentation sweeps that bind header state to Group provider state. Neither may start before that contract exists; both are native-linked blocked-by.
Decision Record depends-on — the ADR 0029 amendment is owned by #18303's freeze; no second ADR. This lane consumes the amendment, never amends it.

Out of scope

  • Group, transaction, history, topology and reload semantics — #18303 (D#18224). Including the four-surface windowDocuments retirement freeze, which @neo-fable has offered to take there.
  • A migration or compatibility path. The operator ruled zero backcompat, ADRs can be amended first-hand. No parallel implementation, no façade preserving a retired subclass API, and no seam surviving merely because a consumer overrides it today — that preserves the ownership defect this Epic exists to remove. Every surviving override seam owes a current product reason.
  • A planning, census or "map the current state" ticket. The census is in this body, not a deliverable.
  • Changing the v13.2 folder anatomy. It was right; it is under-used. New files, not new structure — with the single plugin/ addition above, on existing in-package precedent.
  • The chrome extraction ordering. [OQ_RESOLVED] as independent — no shared file. Both can run.

Avoided traps / rejected shapes

  • A (Neo.mixin.*) — Neo.mixin.VdomLifecycle is composed at component/Abstract.mjs:80 unconditionally for every component, so the precedent is silent on declinable concerns. enableDockTearOutLifecycle defaults false; a mixin cannot be declined per instance. Its Guard cell was never filled.
  • C (more static modules) — falsified by the lead datum: window/TearOut.mjs exists, is imported, and the cluster stayed behind.
  • D (finish the moves the anatomy started) — falsified by its own author with that same datum: the destination existed for months and the behaviour did not move. Destination-exists is not a mechanism.
  • B vs E as a global choice — both remain admissible per concern under G. component/Base.mjs:1322 already destroys owned plugins, so B's "owner must destroy its collaborators" obligation is discharged by existing engine code for the plugin-shaped ones; #18143 is that guard's red-first case.
  • Optimising the LOC number. See the two-metric measurement above. The band can be met once and regrow; "another feature cannot add 200 lines to Workspace merely because it coordinates with docking" cannot be met by relocation at all.
  • An intermediate "dock controller." Named and rejected in the Step-Back: it keeps today's ownership under a new noun.

Related

D#18150 (source, to be closed RESOLVED) · #18303 (sibling lane, D#18224) · #18278 (the no-regrowth ratchet) · #18276 → PR #18298 (landed predecessor) · #18151 (the dock asks consumers to re-derive engine knowledge) · #18274 → PR #18279 (stand-in churn, landed) · ADR 0029 (amended by #18303's freeze)


Structure-map gate: npm run --silent ai:structure-map -- --files --loc produces no output in this repository — independently observed by @neo-gpt during #18276 intake. Recorded as N/A (command silent) rather than as a pass; placement claims above rest on the in-package precedent cited inline (src/tab/plugin/, src/plugin/, window/TopologySeams.mjs).

Origin Session ID: b983b2a8-18bc-4dd0-a63f-23380dc3a49d

tobiu cross-referenced by PR #18333 on Sep 4, 2026, 10:35 PM
tobiu cross-referenced by PR #18374 on Sep 5, 2026, 8:48 PM
tobiu referenced in commit 87e3786 - "The topology bar's undo and redo show how far they reach (#18558) (#18560) on Sep 9, 2026, 11:09 PM