[x] 18308 Hard-cut dock topology persistence to keyed workspace records
blocking
[x] 18311 window/TearOut.mjs has been imported for months while its ~280 lines of pane-handle choreography stayed in Workspace, [x] 18317 Every tear-out-enabled Workspace subscribes to worker-wide connect/disconnect, so N popup workspaces are N lifecycle owners, [x] 18316 Add Workstation multi-window undo, redo, save, and reload, [x] 18272 An N-slot topology commit restores N documents and one view: no consumer has a sibling projection to converge, [x] 18284 Two independent Workstation roots share one drag namespace, so a cross-root drag commits into the wrong document, [x] 18315 Hydrate cold dock topologies and rebind warm reloads, [x] 18314 Unify Dock and Neural Link undo on the group cursor, [x] 18313 Record observed popup placement as a transaction participant, [x] 18312 Commit multi-workspace dock transactions atomically, [x] 18310 Add immutable per-group transaction history and cursor
A warm reload produces a new windowId while the App Worker heap, Workspace instances, and topology may survive. Independent roots of the same app can share one SharedWorker, so appName cannot identify a transaction authority. D#18224 resolved four separate coordinates and a token-matched binding lifecycle.
The Problem
Today Neo.appsByName[appName] is an array and fallback lookup selects [0]; same-name generations can coexist after reload. Popup/workspace state is connected by consumer-specific URLs and lifecycle hooks. A late disconnect can therefore target the wrong generation, and two independent Workstation roots can collapse into one identity.
The Architectural Reality
Neo.manager.Base is already a collection. A generic Neo.manager.Transaction registry matches manager.Store / manager.Window; its Group children must remain dock-blind. Dock Workspaces register as participants through domain adapters. appName stays routing metadata.
The Fix
Add the collection-backed singleton registry plus generic Group identity/binding lifecycle. Main-thread boot injects a root-minted groupId; popups inherit it and carry one workspaceKey and spawn claimToken; windowId is only the replaceable runtime generation. Bindings are (groupId, workspaceKey) -> {windowId, generationToken} and unregister only on an exact token match. Duplicate live roots fork; stale popup URLs fail closed.
This increment also moves semantic participant membership into the Group. The existing dock-side WorkspaceSet may remain as a domain adapter, but it owns no independent entry map. Its adoption/compensation behavior stays intact until #18312 moves the write protocol; releasing a window binding does not retire its semantic participant.
Structural Placement
Full structural pre-flight selected src/manager/Transaction.mjs plus src/manager/transaction/ for generic Group support; dock adapters stay under src/dashboard/dock/. Update the Runtime Engine inventory because it currently has no src/manager/ row. No dock imports enter the manager.
same registry observed through Group and adapter; headless participant survives binding expiry
app identity
Application / appsByName reality
appName routes only
never implicit Group identity
ADR
two same-app roots
Decision Record impact
Depends on and implements amended ADR 0029.
Decision Record
Required prerequisite: the #18303 hard-cut ADR leaf.
Discussion Criteria Mapping
Owns criterion 6 and the binding half of criterion 13; provides criterion 3's first manager ownership cut under the bounded accounting amendment below.
Acceptance Criteria
manager.Transaction registers distinct logical Groups and is exported through the normal manager surface.
Root reload retains groupId, popup birth/reload inherits it, and an independent same-app root receives a different id.
One workspaceKey identifies each full Workspace slot; no parallel workspaceId/window-slot identity survives without a proven non-1:1 consumer. Group owns semantic participant membership in this increment; any surviving WorkspaceSet delegates to that registry and has no independent entry map. Window unbind or binding-lease expiry must not discard a Group that still owns participants.
A replacement generation supersedes only the matching (groupId, workspaceKey) binding; a late old-token disconnect cannot unbind its successor.
A copied Group/slot presented by a second live root forks rather than hijacking; a stale bookmarked popup URL fails closed.
The two-Workstation falsifier proves A+popup share one Group, B shares only appName, and reloading A touches neither B nor a second Group.
The first manager increment carries a measured implementation code-line addition/deletion ledger with a maximum +65 net implementation-line increase (explicit author amendment), replacing the original net-negative constraint for this ownership cut. Re-measure the final review head; this is not a general growth exemption. The ledger covers src/ + apps/ + examples/, using the repository's Acorn-based check-file-sizes.mjs counter. This population covers the engine and both named consumers, Workstation and DemoB; tests, generated assets and documentation do not pay for the increment. Report raw added/deleted lines separately, and identify each replaced parsing/admission/subscription/membership path with its single successor owner; formatting-only or unrelated deletions do not discharge the ownership cut. The shared TearOut identity contract, both consumers' admission/retirement callers, and their window carriers change together: both consumer popups join the opener's Group and stale-generation retirement remains refused. The #18317 subscriber deletion is included here; no compatibility branch, retained consumer alias or independent legacy membership registry remains.
ArchitectureOverview.md adds the missing src/manager/ inventory row and links ADR 0029.
Out of Scope
History rows/cursor, dock commit semantics, cross-Group bridging, or cold topology hydration.
Avoided Traps
No groupId = appName; no Group id in two carriers; no destroy-on-unbind; no realm-wide geometry disarm; no dock-aware manager.
Related
Part of #18303. Blocked by the hard-cut ADR/wire leaf. Enables #18284 and every Group consumer.
Context
A warm reload produces a new
windowIdwhile the App Worker heap, Workspace instances, and topology may survive. Independent roots of the same app can share one SharedWorker, soappNamecannot identify a transaction authority. D#18224 resolved four separate coordinates and a token-matched binding lifecycle.The Problem
Today
Neo.appsByName[appName]is an array and fallback lookup selects[0]; same-name generations can coexist after reload. Popup/workspace state is connected by consumer-specific URLs and lifecycle hooks. A late disconnect can therefore target the wrong generation, and two independent Workstation roots can collapse into one identity.The Architectural Reality
Neo.manager.Baseis already a collection. A genericNeo.manager.Transactionregistry matchesmanager.Store/manager.Window; its Group children must remain dock-blind. Dock Workspaces register as participants through domain adapters.appNamestays routing metadata.The Fix
Add the collection-backed singleton registry plus generic Group identity/binding lifecycle. Main-thread boot injects a root-minted
groupId; popups inherit it and carry oneworkspaceKeyand spawnclaimToken;windowIdis only the replaceable runtime generation. Bindings are(groupId, workspaceKey) -> {windowId, generationToken}and unregister only on an exact token match. Duplicate live roots fork; stale popup URLs fail closed.This increment also moves semantic participant membership into the Group. The existing dock-side
WorkspaceSetmay remain as a domain adapter, but it owns no independent entry map. Its adoption/compensation behavior stays intact until #18312 moves the write protocol; releasing a window binding does not retire its semantic participant.Structural Placement
Full structural pre-flight selected
src/manager/Transaction.mjsplussrc/manager/transaction/for generic Group support; dock adapters stay undersrc/dashboard/dock/. Update the Runtime Engine inventory because it currently has nosrc/manager/row. No dock imports enter the manager.Contract Ledger
manager.TransactionWorkspaceSetadapterworkspaceKey; the dock adapter delegates membership and lookupApplication/appsByNamerealityappNameroutes onlyDecision Record impact
Depends on and implements amended ADR 0029.
Decision Record
Required prerequisite: the #18303 hard-cut ADR leaf.
Discussion Criteria Mapping
Owns criterion 6 and the binding half of criterion 13; provides criterion 3's first manager ownership cut under the bounded accounting amendment below.
Acceptance Criteria
manager.Transactionregisters distinct logical Groups and is exported through the normal manager surface.groupId, popup birth/reload inherits it, and an independent same-app root receives a different id.workspaceKeyidentifies each full Workspace slot; no parallelworkspaceId/window-slot identity survives without a proven non-1:1 consumer. Group owns semantic participant membership in this increment; any survivingWorkspaceSetdelegates to that registry and has no independent entry map. Window unbind or binding-lease expiry must not discard a Group that still owns participants.(groupId, workspaceKey)binding; a late old-token disconnect cannot unbind its successor.appName, and reloading A touches neither B nor a second Group.src/ + apps/ + examples/, using the repository's Acorn-basedcheck-file-sizes.mjscounter. This population covers the engine and both named consumers, Workstation and DemoB; tests, generated assets and documentation do not pay for the increment. Report raw added/deleted lines separately, and identify each replaced parsing/admission/subscription/membership path with its single successor owner; formatting-only or unrelated deletions do not discharge the ownership cut. The shared TearOut identity contract, both consumers' admission/retirement callers, and their window carriers change together: both consumer popups join the opener's Group and stale-generation retirement remains refused. The #18317 subscriber deletion is included here; no compatibility branch, retained consumer alias or independent legacy membership registry remains.ArchitectureOverview.mdadds the missingsrc/manager/inventory row and links ADR 0029.Out of Scope
History rows/cursor, dock commit semantics, cross-Group bridging, or cold topology hydration.
Avoided Traps
No
groupId = appName; no Group id in two carriers; no destroy-on-unbind; no realm-wide geometry disarm; no dock-aware manager.Related
Part of #18303. Blocked by the hard-cut ADR/wire leaf. Enables #18284 and every Group consumer.
Origin Session ID: 01a06b6d-3323-7fa0-a10b-bbc4871c8780
Retrieval Hint:
D18224 groupId workspaceKey generationToken warm reload carrierCreation freshness: live latest-open and all-status A2A sweeps found no equivalent; #18284 consumes this identity but does not own it.