Context
Ada's review of PR #19212 (approved, review 5317488350) left three optional notes. The polish for notes 1 and 3 was committed as 90174829da on the PR's branch three minutes after the operator merged it at def5b265ab, so it never reached dev. This leaf lands it.
The Problem
VesselWorkspace#resolveVesselStageTarget replaces a chrome that lost its viewport (destroyed, or re-homed) with chrome?.isDestroyed || chrome?.destroy(). That branch destroys a live-but-detached chrome without the body check retireProvisionalVesselChrome enforces: if the chrome still held the staged pane, the live pane would be destroyed with it — the invariant the PR's own docblock states never happens. No flow reaches it today (a destroyed viewport destroys its children first), so this is a latent contradiction between two paths, not a live defect. The same methods also guard shapes the class guarantees (getCardContainer?.(), items?.length), which reads as uncertainty about the chrome's own type.
The Architectural Reality
apps/workstation/view/VesselWorkspace.mjs: resolveVesselStageTarget(windowId) creates one Neo.dashboard.dock.interaction.TabContainer per vessel window as the tear-out embodiment's stage target; retireProvisionalVesselChrome(windowId, {releasePanes}) is the one place that knows how to end it — refusing while the body holds a pane unless the caller takes the pane over, then removing and destroying.
- A tab container's
items are its bar and its body; the staged pane lives in getCardContainer().items.
The Fix
- The replacement branch calls
retireProvisionalVesselChrome(windowId, {releasePanes: true}) instead of destroying directly, so a chrome that lost its viewport hands any pane it still holds over detached and retires through the same guard.
getCardContainer() and items.length are called plainly; the one remaining optional chain guards a real null (no body once the chrome is gone).
- A unit arm pins the replacement: a chrome removed from its viewport while holding the staged pane is replaced on the next resolve, the pane survives detached, and the old chrome is destroyed.
Acceptance Criteria
Out of Scope
Note 2 of the same review (the resolver creates before the engine's stage validates): kept as the seam's design, see the PR's disposition comment.
Related
#19210 / PR #19212 (the chrome) · #15252 (the film epic) · #19188 / PR #19208
Live latest-open sweep: checked the latest 20 open issues at 2026-09-25T12:22:29Z; no equivalent. A2A in-flight sweep: no claim on this surface (the last hour's mailbox read). Own-assignment sweep: #19186, #19101, #15252 — none covers it.
Origin Session ID: 4c0a5550-17ba-4752-9852-846afa537c86
Retrieval Hint: query_raw_memories("provisional vessel chrome replacement branch retire guard releasePanes")
🪢 Mnemosyne (Claude Fable 5.1 · Claude Code)
Context
Ada's review of PR #19212 (approved, review 5317488350) left three optional notes. The polish for notes 1 and 3 was committed as 90174829da on the PR's branch three minutes after the operator merged it at def5b265ab, so it never reached
dev. This leaf lands it.The Problem
VesselWorkspace#resolveVesselStageTargetreplaces a chrome that lost its viewport (destroyed, or re-homed) withchrome?.isDestroyed || chrome?.destroy(). That branch destroys a live-but-detached chrome without the body checkretireProvisionalVesselChromeenforces: if the chrome still held the staged pane, the live pane would be destroyed with it — the invariant the PR's own docblock states never happens. No flow reaches it today (a destroyed viewport destroys its children first), so this is a latent contradiction between two paths, not a live defect. The same methods also guard shapes the class guarantees (getCardContainer?.(),items?.length), which reads as uncertainty about the chrome's own type.The Architectural Reality
apps/workstation/view/VesselWorkspace.mjs:resolveVesselStageTarget(windowId)creates oneNeo.dashboard.dock.interaction.TabContainerper vessel window as the tear-out embodiment's stage target;retireProvisionalVesselChrome(windowId, {releasePanes})is the one place that knows how to end it — refusing while the body holds a pane unless the caller takes the pane over, then removing and destroying.itemsare its bar and its body; the staged pane lives ingetCardContainer().items.The Fix
retireProvisionalVesselChrome(windowId, {releasePanes: true})instead of destroying directly, so a chrome that lost its viewport hands any pane it still holds over detached and retires through the same guard.getCardContainer()anditems.lengthare called plainly; the one remaining optional chain guards a realnull(no body once the chrome is gone).Acceptance Criteria
Workspace.spec.mjscarries the replacement arm (the pane survives, the old chrome is destroyed, a new chrome sits in the viewport), red ondev, green at the PR head; the whole spec green.destroy()from the replacement branch and the class-shape optional chains; no behaviour of the stage, mount or retirement paths changes (the five-beat morph arms stay green headless).Out of Scope
Note 2 of the same review (the resolver creates before the engine's
stagevalidates): kept as the seam's design, see the PR's disposition comment.Related
#19210 / PR #19212 (the chrome) · #15252 (the film epic) · #19188 / PR #19208
Live latest-open sweep: checked the latest 20 open issues at 2026-09-25T12:22:29Z; no equivalent. A2A in-flight sweep: no claim on this surface (the last hour's mailbox read). Own-assignment sweep: #19186, #19101, #15252 — none covers it.
Origin Session ID: 4c0a5550-17ba-4752-9852-846afa537c86 Retrieval Hint:
query_raw_memories("provisional vessel chrome replacement branch retire guard releasePanes")🪢 Mnemosyne (Claude Fable 5.1 · Claude Code)