Context
Found while building the pop-out header action (#17947), whose AC inherited the ADR's claim as a premise. The AC could not be witnessed, so the premise was checked against the code rather than restated. #17947 ships without that clause and asserts nothing in either direction; this ticket owns the gap.
Anchors verified at Engine dev@f189cc6ffa.
The Problem
ADR 0029 §2.7's state table (learn/agentos/decisions/0029-docking-design.md:324) states the rule as normative:
detached — Mutually exclusive with autoHidden: auto-hide is an in-workspace affordance; detachItem on a railed item clears autoHidden in the same commit, and setItemAutoHidden(true) rejects detached items.
Neither half is implemented.
Operations.detachItem (src/dashboard/dock/model/Operations.mjs) delegates to Document.detachFromTabs, which touches node structure only:
static detachFromTabs(document, itemId) {
let tabsId = Document.findContainingTabsId(document, itemId);
if (!tabsId) return;
let node = document.nodes[tabsId];
node.items = node.items.filter(id => id !== itemId);
if (node.activeItemId === itemId) {
node.activeItemId = node.items[0] ?? null
}
}No item field is read or written. The only site in the model that clears autoHidden is setItemPinned under pinned === true.
The validator does not catch it either: Document.mjs rejects only pinned === true && autoHidden === true, never detached && autoHidden. So a detached item sits in the catalog carrying autoHidden: true, and the document is valid.
The second half — setItemAutoHidden(true) rejecting a detached item — is also absent: that guard rejects non-booleans and pinned items only. Nothing consults detachment.
Why it matters beyond a stale doc line
autoHidden is committed, serialized item state (ADR 0029 line 87), and it is what the projection keys on to rail an item (collectAutoHiddenItems / createEdgeRail). ADR 0029 line 348 says a closing detached window reintegrates "with the committed pinned / autoHidden values its catalog record carries" — so an item torn out of a rail and then returned comes home railed, which is defensible. But the same stale flag is live the whole time it is detached: a perspective captured while the item lives in its own window records it as auto-hidden (line 347 captures committed item state), and any consumer reading the catalog to decide rail membership sees a railed item that is not in the workspace at all.
The divergence sits in the shared detach commit, so it reaches every entry point equally: the drag terminal, interaction/KeyboardCommands#detachItem, and the #17947 header action.
The Fork (needs a decision before code)
The ADR and the model disagree; exactly one of them is wrong, and this ticket must not guess.
- A — implement the ADR.
detachItem clears autoHidden in the same commit; setItemAutoHidden gains a detached-item refusal. This changes the committed grammar of every existing tear-out, so it needs the reintegration story re-read against line 348: an item torn from a rail would return unrailed, which may be the intended behaviour or may be a regression for rail users.
- B — amend the ADR. Detachment and auto-hide are orthogonal:
autoHidden is where the item lives when docked, and preserving it across a detach round-trip is what makes line 348's "returns with its committed values" meaningful. Then §2.7's mutual-exclusion row and the setItemAutoHidden clause are the error.
B has a real argument and the current code is closer to it, but the ADR is the accepted authority and only an ADR-amending decision may overturn it — which is why this is a fork and not an implementation ticket.
Acceptance Criteria
Out of Scope
- The pop-out header action itself (#17947) — it inherits whatever this decides, along with the drag terminal and the keyboard twin.
- Rail projection, reveal-overlay behaviour, and the pin control's own
autoHidden clearing (landed and correct).
Related
Authority: ADR 0029 §2.7 (learn/agentos/decisions/0029-docking-design.md). Surfaced by: #17947. Parent family: #13158. ADR origin: #14423.
Retrieval Hint: "detachItem autoHidden mutual exclusion ADR 0029 §2.7 railed item detached"
Origin Session ID: dfa1b252-76f5-4a0a-81ab-98b9a5228703
— Grace (Opus 5, Claude Code) 🖖
Context
Found while building the pop-out header action (#17947), whose AC inherited the ADR's claim as a premise. The AC could not be witnessed, so the premise was checked against the code rather than restated. #17947 ships without that clause and asserts nothing in either direction; this ticket owns the gap.
Anchors verified at Engine
dev@f189cc6ffa.The Problem
ADR 0029 §2.7's state table (
learn/agentos/decisions/0029-docking-design.md:324) states the rule as normative:Neither half is implemented.
Operations.detachItem(src/dashboard/dock/model/Operations.mjs) delegates toDocument.detachFromTabs, which touches node structure only:static detachFromTabs(document, itemId) { let tabsId = Document.findContainingTabsId(document, itemId); if (!tabsId) return; let node = document.nodes[tabsId]; node.items = node.items.filter(id => id !== itemId); if (node.activeItemId === itemId) { node.activeItemId = node.items[0] ?? null } }No item field is read or written. The only site in the model that clears
autoHiddenissetItemPinnedunderpinned === true.The validator does not catch it either:
Document.mjsrejects onlypinned === true && autoHidden === true, neverdetached && autoHidden. So a detached item sits in the catalog carryingautoHidden: true, and the document is valid.The second half —
setItemAutoHidden(true)rejecting a detached item — is also absent: that guard rejects non-booleans and pinned items only. Nothing consults detachment.Why it matters beyond a stale doc line
autoHiddenis committed, serialized item state (ADR 0029 line 87), and it is what the projection keys on to rail an item (collectAutoHiddenItems/createEdgeRail). ADR 0029 line 348 says a closing detached window reintegrates "with the committedpinned/autoHiddenvalues its catalog record carries" — so an item torn out of a rail and then returned comes home railed, which is defensible. But the same stale flag is live the whole time it is detached: a perspective captured while the item lives in its own window records it as auto-hidden (line 347 captures committed item state), and any consumer reading the catalog to decide rail membership sees a railed item that is not in the workspace at all.The divergence sits in the shared detach commit, so it reaches every entry point equally: the drag terminal,
interaction/KeyboardCommands#detachItem, and the #17947 header action.The Fork (needs a decision before code)
The ADR and the model disagree; exactly one of them is wrong, and this ticket must not guess.
detachItemclearsautoHiddenin the same commit;setItemAutoHiddengains a detached-item refusal. This changes the committed grammar of every existing tear-out, so it needs the reintegration story re-read against line 348: an item torn from a rail would return unrailed, which may be the intended behaviour or may be a regression for rail users.autoHiddenis where the item lives when docked, and preserving it across a detach round-trip is what makes line 348's "returns with its committed values" meaningful. Then §2.7's mutual-exclusion row and thesetItemAutoHiddenclause are the error.B has a real argument and the current code is closer to it, but the ADR is the accepted authority and only an ADR-amending decision may overturn it — which is why this is a fork and not an implementation ticket.
Acceptance Criteria
detachItemclearsautoHiddenin the same commit, andsetItemAutoHidden(true)refuses an item that is in no tabs node. Both witnessed by unit arms that fail against today'sdetachFromTabs.DockOperations/DockZoneModelunit coverage.Out of Scope
autoHiddenclearing (landed and correct).Related
Authority: ADR 0029 §2.7 (
learn/agentos/decisions/0029-docking-design.md). Surfaced by: #17947. Parent family: #13158. ADR origin: #14423.Retrieval Hint: "detachItem autoHidden mutual exclusion ADR 0029 §2.7 railed item detached"
Origin Session ID: dfa1b252-76f5-4a0a-81ab-98b9a5228703
— Grace (Opus 5, Claude Code) 🖖