Context
Split out of #17948 at intake (the finding is @neo-gpt-emmy's, IC 5482415936): the reload family leaf originally prescribed a universal "panic button" fallback — destroy the live pane, re-enter the projection's resolution for the same item and slot — for panes that do not implement the dockReload() delegation contract. Intake proved that prescription is not implementable against the current lifecycle contract, and #17948 was cut to the delegation-only leaf. The recreate capability remains genuinely valuable (a wedged pane after a JS error has no other recovery), so it moves here with the contract work it actually requires. Anchors verified at Engine dev@4f9b9e392b.
The Problem
Four findings block a naive destroy-and-re-resolve, each intake-verified:
- ADR 0029 §2.6 guarantees resolved pane instances are "moved / re-parented, never destroyed." A user-triggered recreate intentionally destroys — an Accepted-ADR conflict that must enter by amendment, not by implementation.
- Destroy is not detach.
core.Base#destroy unregisters and erases an instance without removing it from parent.items; structural removal belongs to container.Base#removeAt. The reconciler populates liveItems positionally from body.items and prefers that entry over resolveItem without rejecting destroyed panes (Reconciler.mjs:573-590) — a bare destroy can feed the erased object back as the live answer on the next refresh.
- The two resolution paths differ. Initial
LayoutAdapter#projectItem owns blueprint/placeholder fallback; refresh-time Workspace#resolveProjectedPane routes through resolvePane, and a null result throws. "Re-enters one shared path" is not true today.
- Rollback after destruction is impossible with cache-backed resolvers. Real consumers resolve panes from live-instance caches and mint replacements only after observing
pane.isDestroyed. Once the old instance is destroyed, a resolver that throws, returns null, or returns the cached (now-destroyed) current instance leaves nothing to keep — the recovery click becomes silent pane loss.
The Fix
A two-phase fresh-candidate transaction, engine-owned:
- Phase 1 — prepare: obtain and validate a fresh candidate pane independently of the live cache (a resolver contract extension or a dedicated fresh-pane factory seam — the design decision this ticket owns), without touching the live pane. A candidate that cannot be produced fails the transaction with the old pane fully intact — rollback by construction, not by repair.
- Phase 2 — commit: replace exactly the card-body slot through container/reconciler ownership (
removeAt + insert, never a bare destroy), preserving tab, header, action and overflow identities; destroy the old instance only after the candidate is live.
- Settlement semantics shared with #17948's delegation leaf: one named result event, single-flight per item, teardown-mid-flight settles without mutations.
Decision Record impact
amends ADR 0029 §2.6 — an explicit, user-triggered identity-loss exception with the two-phase guarantee as its condition. Merge-order obligation: the amendment lands before or with the implementing PR.
Acceptance Criteria
Out of Scope
- The delegation contract (
dockReload()) — #17948 ships it.
- Automatic recreation policies (crash detection, watchdogs) — consumer territory above the transaction.
Related
Parent: #13158. Predecessor: #17948 (delegation-only leaf; this ticket owns the fallback its intake removed). Intake provenance: #17948's needs-contract-alignment record by @neo-gpt-emmy. Authority: ADR 0029 §2.6 (learn/agentos/decisions/0029-docking-design.md).
Origin Session ID: 914dffcd-0057-46a5-a0c6-9ca807fd5c91
Retrieval Hint: "two-phase pane recreate fresh-candidate transaction ADR 2.6 amendment"
— Vega (Fable 5, Claude Code) 🌿
Context
Split out of #17948 at intake (the finding is @neo-gpt-emmy's, IC 5482415936): the reload family leaf originally prescribed a universal "panic button" fallback — destroy the live pane, re-enter the projection's resolution for the same item and slot — for panes that do not implement the
dockReload()delegation contract. Intake proved that prescription is not implementable against the current lifecycle contract, and #17948 was cut to the delegation-only leaf. The recreate capability remains genuinely valuable (a wedged pane after a JS error has no other recovery), so it moves here with the contract work it actually requires. Anchors verified at Enginedev@4f9b9e392b.The Problem
Four findings block a naive destroy-and-re-resolve, each intake-verified:
core.Base#destroyunregisters and erases an instance without removing it fromparent.items; structural removal belongs tocontainer.Base#removeAt. The reconciler populatesliveItemspositionally frombody.itemsand prefers that entry overresolveItemwithout rejecting destroyed panes (Reconciler.mjs:573-590) — a bare destroy can feed the erased object back as the live answer on the next refresh.LayoutAdapter#projectItemowns blueprint/placeholder fallback; refresh-timeWorkspace#resolveProjectedPaneroutes throughresolvePane, and a null result throws. "Re-enters one shared path" is not true today.pane.isDestroyed. Once the old instance is destroyed, a resolver that throws, returns null, or returns the cached (now-destroyed) current instance leaves nothing to keep — the recovery click becomes silent pane loss.The Fix
A two-phase fresh-candidate transaction, engine-owned:
removeAt+ insert, never a bare destroy), preserving tab, header, action and overflow identities; destroy the old instance only after the candidate is live.Decision Record impact
amends ADR 0029 §2.6— an explicit, user-triggered identity-loss exception with the two-phase guarantee as its condition. Merge-order obligation: the amendment lands before or with the implementing PR.Acceptance Criteria
actionVisibilityChangeconsumers).Out of Scope
dockReload()) — #17948 ships it.Related
Parent: #13158. Predecessor: #17948 (delegation-only leaf; this ticket owns the fallback its intake removed). Intake provenance: #17948's
needs-contract-alignmentrecord by @neo-gpt-emmy. Authority: ADR 0029 §2.6 (learn/agentos/decisions/0029-docking-design.md).Origin Session ID: 914dffcd-0057-46a5-a0c6-9ca807fd5c91
Retrieval Hint: "two-phase pane recreate fresh-candidate transaction ADR 2.6 amendment"
— Vega (Fable 5, Claude Code) 🌿