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:
- Did
Workspace lose the methods, configs and mutable state for that concern?
- Can the extracted owner be instantiated and destroyed independently under a bounded interface?
- Can a consumer replace or disable it without overriding a dozen
Workspace methods?
- 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
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.mjsin this Epic always meanssrc/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 atdev@476f23aawithnode ./buildScripts/util/check-file-sizes.mjs(the #18278 ratchet — same instrument both times):src/dashboard/dock/Workspace.mjs131c782ca8476f23aasyncDock*call sitesThe 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/workstationrefactoring 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.mjsis 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:
apps/workstation/view/Workspace.mjsexamples/dashboard/crossWindow/DemoBWorkspace.mjssrc/dashboard/dock/Workspace.mjsBoth 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 ofapps/workstationaround 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.mjshas 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;
Workspacebecomes 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/*window/TopologySeams.mjsis the passing precedent: oneclassNameconfig, no state, exactly the two methodsDockServiceresolves)WorkspacefaçadeIf 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
Workspaceno longer holds. One new folder, on in-package precedent (src/tab/plugin/Overflow.mjs, plus five modules insrc/plugin/):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 onports[0]and owns no first projection) was this order's named predecessor and landed as PR #18298 →dev@131c782ca8, so the bind path is alreadyviewport.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:
Workspacelose the methods, configs and mutable state for that concern?Workspacemethods?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)
[GRADUATION_APPROVED by @neo-gpt-emmy]@ DC_kwDODSospM4BFyRC — quorum METmodel/Operationsowns semantic change class) · 4 ordered cut converged across DC_kwDODSospM4BFxvy + the Step-Back · 5 re-census landed at131c782ca8(above) · 6 Ownership test is the gate · 7 Step-Back delivered · 8 quorum metsyncDock*presentation sweeps that bind header state to Group provider state. Neither may start before that contract exists; both are native-linkedblocked-by.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
windowDocumentsretirement freeze, which @neo-fable has offered to take there.zero backcompat, ADRs can be amendedfirst-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.plugin/addition above, on existing in-package precedent.[OQ_RESOLVED]as independent — no shared file. Both can run.Avoided traps / rejected shapes
Neo.mixin.*) —Neo.mixin.VdomLifecycleis composed atcomponent/Abstract.mjs:80unconditionally for every component, so the precedent is silent on declinable concerns.enableDockTearOutLifecycledefaultsfalse; a mixin cannot be declined per instance. ItsGuardcell was never filled.window/TearOut.mjsexists, is imported, and the cluster stayed behind.component/Base.mjs:1322already 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.Workspacemerely because it coordinates with docking" cannot be met by relocation at all.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 --locproduces 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