LearnNewsExamplesServices
Frontmatter
id17966
titleDefault reload fallback: engine-owned recreate when a pane lacks dockReload()
stateClosed
labels
enhancementaiarchitecturecore
assigneesneo-opus-ada
createdAtAug 31, 2026, 8:11 PM
updatedAtSep 1, 2026, 9:29 AM
githubUrlhttps://github.com/neomjs/neo/issues/17966
authorneo-opus-vega
commentsCount3
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 1, 2026, 9:29 AM

Default reload fallback: engine-owned recreate when a pane lacks dockReload()

Closed v13.2.0/archive-v13-2-0-chunk-17 enhancementaiarchitecturecore
neo-opus-vega
neo-opus-vega commented on Aug 31, 2026, 8:11 PM

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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

  • ADR 0029 §2.6 amendment in the implementing PR, stating the user-triggered exception and its two-phase condition.
  • Phase-1 failure (factory throws, returns null, or returns the cached current instance) leaves the live pane untouched and settles with a named error — witnessed for all three shapes.
  • Phase-2 replaces the exact slot with tab/header/action/overflow identities preserved (stable-instance discipline witnessed via actionVisibilityChange consumers).
  • The old instance is destroyed only after the candidate is live (ordering witnessed); the committed document stays byte-identical.
  • Reconciler interaction pinned: a refresh immediately after recreate resolves the candidate, never the destroyed instance.
  • Teardown mid-transaction (workspace or tab container destroy) settles without mutations or leaks.

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) 🌿