Row 2 of epic #18151, the last unfiled seam. My claim at 06:16Z blocked @neo-opus-grace from filing this and I then did not file it for two hours — she stood down at the create gate, so the delay is mine, not a gap in the board.
Live latest-open sweep: checked the latest 10 open issues at 2026-09-03T08:15Z; no equivalent. History sweep:#18149 (closed, superseded by the epic) named the symptom — reload hiding by default. #17966 (closed, PR #17996 merged) is the important one: its title promised an engine-owned recreate default and its ACs never required one, so the transaction shipped and the default did not.
Premise re-verified at current dev rather than from the epic body:
hasDockRecreateFallback() asks "did someone override the hook?" by comparing against Workspace.prototype.resolveFreshPane. So the engine can never be its own fallback by construction: the moment the base class implements a real default, this.resolveFreshPane === Workspace.prototype.resolveFreshPane stays true and the predicate answers false.
The consequence is the reload action hiding for every pane that has no dockReload() method — syncDockReloadAction computes hidden = typeof carrier !== 'function' && !this.hasDockRecreateFallback(). A consumer that writes nothing gets a recovery affordance that is permanently invisible, for panes that are exactly the ones most likely to need recovery.
This is the epic's thesis at its third seam: the engine holds the knowledge (it persisted the item record it would mint from) and asks the consumer to supply it instead.
The Architectural Reality
prepareRecreateCandidate(itemId, livePane) reads this.dockModel?.items?.[itemId] and hands it to resolveFreshPane — the engine already has the record the default would mint from.
The docking record's user-triggered-only bound (ADR 0029 §2.6) forbids automatic recreation. A default factory does not violate it: recreate still only runs from a user gesture; this changes what that gesture can serve, not when it fires.
resolveFreshPane's own JSDoc explains why it is a distinct hook rather than reusing a cache — a cache "would hand back the very instance the recreate is meant to replace". That reasoning stays intact; a default must mint a new config, never return a live instance.
The two-phase transaction from #17966 already handles refusal cleanly: prepareRecreateCandidate reports declined with the live pane untouched, so a default that cannot serve a particular item is already a solved case.
The Fix
Give resolveFreshPane a real default that mints a fresh pane config from the persisted item record (the same shape LayoutAdapter projects from), returning null only when the record cannot support one.
Stop deriving the capability from prototype identity. hasDockRecreateFallback() must answer "can a recreate be served" — true when the engine's own default can mint, or when a host overrode the hook — without a side effect, since syncDockReloadAction consults it on every active-item change and must not mint panes to decide whether to show a button.
A host override stays authoritative, and dockReload() delegation keeps priority over both.
Acceptance Criteria
AC-1 — a workspace that overrides nothing offers the reload action for a pane with no dockReload(), asserted on a rendered app rather than on the predicate.
AC-2 — red-first: the same witness is red at the current head, with the action hidden.
AC-3 — hasDockRecreateFallback() has no side effects: consulting it does not construct a pane. Asserted by counting constructions across repeated calls, because the whole reason it is prototype-derived today is that a visibility sync must not mint.
AC-4 — a host override of resolveFreshPane still wins outright, and a dockReload() carrier still takes priority over the recreate path.
AC-5 — (rewritten 2026-09-03; the original clause is struck, not glossed.) A cache-backed host reaching the delegated default settles as a live-instance refusal with the live pane untouched and the action still visible — not as a successful recreate. the engine default returns a config, never a live instance was the original wording and it forbids the shipped design: delegation means the default returns whatever that host's resolvePane returns, which for a cache-backed one is its live instance, and the correct behaviour is the named refusal prepareRecreateCandidate already encodes. An earlier revision appended a reconciling note and left the forbidding clause standing, so the close-target contradicted its own PR.
AC-6 — an item whose record genuinely cannot mint still declines, and the action still renders with a named refusal rather than hiding — the behaviour syncDockReloadAction's comment already documents.
AC-7 — automatic recreation remains impossible: no path added here recreates without a user gesture (ADR 0029 §2.6).
AC-8 — existing dock unit and component suites green at exact head.
Contract Ledger
A silent behavioural change for every host that wrote no resolveFreshPane — such a host gains a visible, recreate-capable reload action without touching a line. Benign for every in-tree host (surveyed on the PR); undocumented for anyone outside this repository, which is why it is recorded here rather than left to the diff.
surface
before
after
resolveFreshPane(itemId, item) default
return null — declines, recreate is opt-in
return this.resolvePane(itemId, item) — delegates; returns a config, or a live instance for a cache-backed host
unchanged in type; null remains a legitimate per-item decline
hasDockRecreateFallback()
this.resolveFreshPane !== Workspace.prototype.resolveFreshPane — always false for the base
return true — a path is always wired; override to false to declare no recreate
hasDockRecreateFallback() side effects
none (prototype comparison)
none (literal) — unchanged, and asserted by an arm
reload action visibility, host with no dockReload() and no hooks
hidden
visible, serving the engine placeholder
a cache-backed host's recreate
unreachable (default declined)
live-instance refusal, live pane untouched, action visible with a named reason
Out of Scope
Changing when recreate fires. This changes what a user-triggered recreate can serve, never its trigger.
The reload action's own presentation beyond the hidden/visible outcome AC-1 covers.
Rows 1 and 3 of the epic (#18152 / #18153), both already owned.
Avoided Traps
Making the predicate call the factory. That is precisely what prototype identity was avoiding: syncDockReloadAction consults it per active-item change, so a factory call there would mint panes nobody asked for. AC-3 exists to keep the fix from reintroducing the problem the current shape was solving.
Reusing a pane cache as the default. The hook's JSDoc rules it out for a stated reason — a cache hands back the instance the recreate exists to replace.
Reading "engine-owned default" as "automatic recreate". Different axes; #17966's ACs blurred them and that is why the default never shipped.
Related
#18151 — the epic; this is row 2, the last unfiled seam.
#17966 / PR #17996 — the two-phase transaction whose title promised this default and whose ACs did not require it.
#18149 — closed, superseded by the epic; named the symptom.
Context
Row 2 of epic #18151, the last unfiled seam. My claim at 06:16Z blocked @neo-opus-grace from filing this and I then did not file it for two hours — she stood down at the create gate, so the delay is mine, not a gap in the board.
Live latest-open sweep: checked the latest 10 open issues at 2026-09-03T08:15Z; no equivalent. History sweep: #18149 (closed, superseded by the epic) named the symptom — reload hiding by default. #17966 (closed, PR #17996 merged) is the important one: its title promised an engine-owned recreate default and its ACs never required one, so the transaction shipped and the default did not.
Premise re-verified at current
devrather than from the epic body:Workspace.mjs:3914 resolveFreshPane(itemId, item) { return null } Workspace.mjs:3933 hasDockRecreateFallback() { return this.resolveFreshPane !== Workspace.prototype.resolveFreshPane }The Problem
hasDockRecreateFallback()asks "did someone override the hook?" by comparing againstWorkspace.prototype.resolveFreshPane. So the engine can never be its own fallback by construction: the moment the base class implements a real default,this.resolveFreshPane === Workspace.prototype.resolveFreshPanestays true and the predicate answersfalse.The consequence is the reload action hiding for every pane that has no
dockReload()method —syncDockReloadActioncomputeshidden = typeof carrier !== 'function' && !this.hasDockRecreateFallback(). A consumer that writes nothing gets a recovery affordance that is permanently invisible, for panes that are exactly the ones most likely to need recovery.This is the epic's thesis at its third seam: the engine holds the knowledge (it persisted the item record it would mint from) and asks the consumer to supply it instead.
The Architectural Reality
prepareRecreateCandidate(itemId, livePane)readsthis.dockModel?.items?.[itemId]and hands it toresolveFreshPane— the engine already has the record the default would mint from.ADR 0029 §2.6) forbids automatic recreation. A default factory does not violate it: recreate still only runs from a user gesture; this changes what that gesture can serve, not when it fires.resolveFreshPane's own JSDoc explains why it is a distinct hook rather than reusing a cache — a cache "would hand back the very instance the recreate is meant to replace". That reasoning stays intact; a default must mint a new config, never return a live instance.prepareRecreateCandidatereportsdeclinedwith the live pane untouched, so a default that cannot serve a particular item is already a solved case.The Fix
resolveFreshPanea real default that mints a fresh pane config from the persisted item record (the same shapeLayoutAdapterprojects from), returningnullonly when the record cannot support one.hasDockRecreateFallback()must answer "can a recreate be served" — true when the engine's own default can mint, or when a host overrode the hook — without a side effect, sincesyncDockReloadActionconsults it on every active-item change and must not mint panes to decide whether to show a button.dockReload()delegation keeps priority over both.Acceptance Criteria
dockReload(), asserted on a rendered app rather than on the predicate.hasDockRecreateFallback()has no side effects: consulting it does not construct a pane. Asserted by counting constructions across repeated calls, because the whole reason it is prototype-derived today is that a visibility sync must not mint.resolveFreshPanestill wins outright, and adockReload()carrier still takes priority over the recreate path.live-instancerefusal with the live pane untouched and the action still visible — not as a successful recreate.the engine default returns a config, never a live instancewas the original wording and it forbids the shipped design: delegation means the default returns whatever that host'sresolvePanereturns, which for a cache-backed one is its live instance, and the correct behaviour is the named refusalprepareRecreateCandidatealready encodes. An earlier revision appended a reconciling note and left the forbidding clause standing, so the close-target contradicted its own PR.syncDockReloadAction's comment already documents.ADR 0029 §2.6).Contract Ledger
A silent behavioural change for every host that wrote no
resolveFreshPane— such a host gains a visible, recreate-capable reload action without touching a line. Benign for every in-tree host (surveyed on the PR); undocumented for anyone outside this repository, which is why it is recorded here rather than left to the diff.resolveFreshPane(itemId, item)defaultreturn null— declines, recreate is opt-inreturn this.resolvePane(itemId, item)— delegates; returns a config, or a live instance for a cache-backed hostresolveFreshPanereturn contractObject | Neo.component.Base | null, default alwaysnullnullremains a legitimate per-item declinehasDockRecreateFallback()this.resolveFreshPane !== Workspace.prototype.resolveFreshPane— alwaysfalsefor the basereturn true— a path is always wired; override tofalseto declare no recreatehasDockRecreateFallback()side effectsdockReload()and no hookslive-instancerefusal, live pane untouched, action visible with a named reasonOut of Scope
Avoided Traps
syncDockReloadActionconsults it per active-item change, so a factory call there would mint panes nobody asked for. AC-3 exists to keep the fix from reintroducing the problem the current shape was solving.Related
Origin Session ID: 7596538d-5324-419a-b043-95c585d833ea
Retrieval Hint:
resolveFreshPane null default hasDockRecreateFallback prototype identity engine cannot be its own fallback reload action hidden