Context
Operator ruling 2026-09-03: a dock workspace that configures nothing must project the full header action rail — "lock => GOAL was neo default true", "popout MUST BE THERE BY DEFAULT".
Measured on a downstream workspace that overrides none of the six enableDock*Action flags (headless Chromium against the running app, action nodes read per header): focusing a center-owned pane yields [maximize, close]; focusing an edge-owned pane yields [unpin, maximize, close]. Lock and pop-out never appear.
Both absences are correct against currently accepted ACs. That is what this ticket revises — it is a successor to two deliberate decisions, not a defect report against them.
The Problem
1. Lock. #18014 AC-1 reads: "… and retain explicit false opt-out; enableDockLockAction remains default-off." The action itself shipped complete under #17949 (fa-lock ↔ fa-lock-open, aria lock/unlock, focus-gated while unlocked and persistent while locked) and #18044 added per-pane delegation (dockLock(locked)) — but no default consumer renders it. The consequence was named on 2026-09-01: default-off plus no Workstation opt-in means the operator "has not felt it by hand", with a proposed opt-in step that never executed.
2. Pop-out. #17947 AC-1 witnessed a DOUBLE gate as correct: "Default off: byte-identical projection. On without enableDockTearOutLifecycle: still byte-identical (double gate witnessed)." The consequence is that enableDockPopOutAction defaults true while being structurally unsatisfiable: dockPopOutActionActive = enableDockPopOutAction === true && !!this.tearOutHandlers, and handlers are composed only under enableDockTearOutLifecycle, which defaults false. A host reads an enabled flag, gets no action, and receives no runtime signal.
Pop-out is the sharper half. An enabled flag that can never render is a workspace-wide constant falsehood, not per-item variance — and enableDockTearOutLifecycle's own JSDoc frames its default as temporary: "Disabled by default so ordinary workspaces and hosts carrying their own legacy lifecycle remain byte-behaviorally unchanged until their migration leaf." This is that leaf.
The Architectural Reality
src/dashboard/dock/Workspace.mjs:145 — enableDockLockAction: false; :215 — enableDockPopOutAction: true; :266 — enableDockTearOutLifecycle: false.
Workspace#dockPopOutActionActive is the single effective predicate ("Two questions must never disagree"), passed to the adapter as dockPopOutActionAvailable.
src/dashboard/dock/projection/LayoutAdapter.mjs:1113 builds headerActions: lock enters only under context.enableDockLockAction; pop-out enters under enableDockPopOutAction but with hidden: !activeItemId || !context.dockPopOutActionAvailable.
- A withdrawn focus-gated action carries no DOM node (#18015), so an absent action is indistinguishable from a disabled flag by inspection. That is why this went unnoticed by eye.
The Fix
enableDockLockAction defaults true; explicit false retained as the opt-out. Amends #18014 AC-1.
Pop-out becomes available to a default workspace. Two candidate shapes:
- (a)
enableDockTearOutLifecycle defaults true — the migration leaf its JSDoc anticipates. Moves tear-out lifecycle ownership for every existing host that carries its own.
- (b) Decouple availability from the legacy-lifecycle flag: the engine composes its own
tearOutHandlers when a host supplies none, leaving enableDockTearOutLifecycle as the opt-in for hosts that want the engine to own the WHOLE lifecycle.
(b) is the recommendation — it makes the enabled flag honest without moving lifecycle ownership under any host that already has one.
Whichever shape lands: an enabled action that cannot render must not be silently inert. A dev-mode warning names the action and the missing capability.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
Workspace#enableDockLockAction |
src/dashboard/dock/Workspace.mjs:145 |
default true |
explicit false removes the action, unchanged |
JSDoc |
default-vector arm, red when reverted |
Workspace#enableDockTearOutLifecycle |
:266 |
unchanged under (b); true under (a) |
hosts with their own lifecycle keep it |
JSDoc |
byte-identical projection arm for existing hosts |
Workspace#dockPopOutActionActive |
Workspace.mjs (getter) |
true for a default workspace |
a host-supplied bundle still wins |
JSDoc |
availability arm on a bare Workspace |
| dev-mode inert-action warning |
new |
names the action + the missing capability |
silent in production |
JSDoc |
one arm asserting the warning fires |
Decision Record impact
amends ADR 0029 — the docking design record's tear-out lifecycle and default-posture rows. Also amends the accepted ACs of #18014 (AC-1) and #17947 (AC-1); both tickets are CLOSED with merged PRs, so this is a successor, never a reopen.
Acceptance Criteria
Out of Scope
- Reload's default availability — its own successor, filed alongside this one.
- Pin's edge-ownership gate: verified working (present on an edge-owned pane, withheld on a center-owned one). A consumer layout concern, not an engine one.
- The locked-pane presentation design pass — #18044 shipped delegation; the gray-out question stays open.
Avoided Traps
- Flipping
enableDockTearOutLifecycle to true as the obvious one-line fix. Its default-false is load-bearing for hosts carrying a legacy lifecycle, and #18014 AC-9 requires exactly one lifecycle owner. A one-line flip moves ownership under those hosts silently — the reason (b) is recommended over (a).
- Reading an absent DOM node as a disabled flag. Withdrawn actions carry no node, so presence is the only honest instrument. A grep for these flags across a consumer tree returns nothing precisely because they are already
true.
Related
- #18014 (closed) — AC-1 set the default vector and kept lock off; this amends it.
- #17947 (closed) — AC-1 witnessed the pop-out double gate; this amends it.
- #17949 (closed) — the lock action itself. #18044 (closed) — per-pane
dockLock delegation.
- #18015 — withdrawn actions have zero extent and no node.
- ADR 0029 — docking design record.
Live latest-open sweep: checked the latest 25 open issues at 2026-09-02T22:12Z; A2A in-flight claim sweep over the most recent 12 messages at 22:14Z — no equivalent ticket and no competing claim.
Origin Session ID: a5db4dc8-310d-4702-9bfa-3780d32e932b
Retrieval Hint: "dock header action defaults lock pop-out enableDockTearOutLifecycle double gate bare default workspace"
Context
Operator ruling 2026-09-03: a dock workspace that configures nothing must project the full header action rail — "lock => GOAL was neo default true", "popout MUST BE THERE BY DEFAULT".
Measured on a downstream workspace that overrides none of the six
enableDock*Actionflags (headless Chromium against the running app, action nodes read per header): focusing a center-owned pane yields[maximize, close]; focusing an edge-owned pane yields[unpin, maximize, close]. Lock and pop-out never appear.Both absences are correct against currently accepted ACs. That is what this ticket revises — it is a successor to two deliberate decisions, not a defect report against them.
The Problem
1. Lock. #18014 AC-1 reads: "… and retain explicit
falseopt-out;enableDockLockActionremains default-off." The action itself shipped complete under #17949 (fa-lock↔fa-lock-open, aria lock/unlock, focus-gated while unlocked and persistent while locked) and #18044 added per-pane delegation (dockLock(locked)) — but no default consumer renders it. The consequence was named on 2026-09-01: default-off plus no Workstation opt-in means the operator "has not felt it by hand", with a proposed opt-in step that never executed.2. Pop-out. #17947 AC-1 witnessed a DOUBLE gate as correct: "Default off: byte-identical projection. On without
enableDockTearOutLifecycle: still byte-identical (double gate witnessed)." The consequence is thatenableDockPopOutActiondefaultstruewhile being structurally unsatisfiable:dockPopOutActionActive=enableDockPopOutAction === true && !!this.tearOutHandlers, and handlers are composed only underenableDockTearOutLifecycle, which defaultsfalse. A host reads an enabled flag, gets no action, and receives no runtime signal.Pop-out is the sharper half. An enabled flag that can never render is a workspace-wide constant falsehood, not per-item variance — and
enableDockTearOutLifecycle's own JSDoc frames its default as temporary: "Disabled by default so ordinary workspaces and hosts carrying their own legacy lifecycle remain byte-behaviorally unchanged until their migration leaf." This is that leaf.The Architectural Reality
src/dashboard/dock/Workspace.mjs:145—enableDockLockAction: false;:215—enableDockPopOutAction: true;:266—enableDockTearOutLifecycle: false.Workspace#dockPopOutActionActiveis the single effective predicate ("Two questions must never disagree"), passed to the adapter asdockPopOutActionAvailable.src/dashboard/dock/projection/LayoutAdapter.mjs:1113buildsheaderActions: lock enters only undercontext.enableDockLockAction; pop-out enters underenableDockPopOutActionbut withhidden: !activeItemId || !context.dockPopOutActionAvailable.The Fix
enableDockLockActiondefaultstrue; explicitfalseretained as the opt-out. Amends #18014 AC-1.Pop-out becomes available to a default workspace. Two candidate shapes:
enableDockTearOutLifecycledefaultstrue— the migration leaf its JSDoc anticipates. Moves tear-out lifecycle ownership for every existing host that carries its own.tearOutHandlerswhen a host supplies none, leavingenableDockTearOutLifecycleas the opt-in for hosts that want the engine to own the WHOLE lifecycle.(b) is the recommendation — it makes the enabled flag honest without moving lifecycle ownership under any host that already has one.
Whichever shape lands: an enabled action that cannot render must not be silently inert. A dev-mode warning names the action and the missing capability.
Contract Ledger Matrix
Workspace#enableDockLockActionsrc/dashboard/dock/Workspace.mjs:145truefalseremoves the action, unchangedWorkspace#enableDockTearOutLifecycle:266trueunder (a)Workspace#dockPopOutActionActiveWorkspace.mjs(getter)truefor a default workspaceDecision Record impact
amends ADR 0029— the docking design record's tear-out lifecycle and default-posture rows. Also amends the accepted ACs of #18014 (AC-1) and #17947 (AC-1); both tickets are CLOSED with merged PRs, so this is a successor, never a reopen.Acceptance Criteria
Neo.dashboard.dock.Workspacesubclass that overrides nothing projectslockat the frozen slot; explicitenableDockLockAction: falsestill removes it.pop-outthat is reachable (nothidden) on a focused item, with the host supplying notearOutHandlers.lock · reload · pin · pop-out · maximize · closeis unchanged; close stays persistent and last.Out of Scope
Avoided Traps
enableDockTearOutLifecycletotrueas the obvious one-line fix. Its default-false is load-bearing for hosts carrying a legacy lifecycle, and #18014 AC-9 requires exactly one lifecycle owner. A one-line flip moves ownership under those hosts silently — the reason (b) is recommended over (a).true.Related
dockLockdelegation.Live latest-open sweep: checked the latest 25 open issues at 2026-09-02T22:12Z; A2A in-flight claim sweep over the most recent 12 messages at 22:14Z — no equivalent ticket and no competing claim.
Origin Session ID: a5db4dc8-310d-4702-9bfa-3780d32e932b
Retrieval Hint: "dock header action defaults lock pop-out enableDockTearOutLifecycle double gate bare default workspace"