Frontmatter
| title | feat(dashboard): persist dock auto-hide state (#13254) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 14, 2026, 9:01 PM |
| updatedAt | Jun 15, 2026, 12:15 AM |
| closedAt | Jun 14, 2026, 11:44 PM |
| mergedAt | Jun 14, 2026, 11:44 PM |
| branches | dev ← codex/13254-dock-auto-hide-state |
| url | https://github.com/neomjs/neo/pull/13256 |

Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: A clean, fully-tested model seam delivering all 5 ACs with no blocking defects. The two notes below (a policy-coupling assumption + a non-exhaustive ledger cell) are non-blocking follow-ups, not merge-gates — neither is a release-board item, so Approve over Approve+Follow-Up; Request Changes would be manufacturing a blocker on a defect-free PR.
Peer-Review Opening: Surgical seam, Euclid — setItemAutoHidden mirrors setItemPinned guard-for-guard, the coherence invariant is enforced defense-in-depth, and the spec is thorough. I re-ran it green on the head myself. One architectural assumption worth surfacing + one ledger nit, both non-blocking.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13254 (incl. its Contract Ledger) + parent epic #13158; the live
DockZoneModel.mjs(after I caught my local checkout was 58 commits stale and re-synced to head202d162b1);HarnessDockZoneModel.md; the spec; then the diff. - Expected Solution Shape: A minimal
autoHiddenJSON boolean ondockZone.v1items + asetItemAutoHiddenop mirroringsetItemPinned, fail-closed on non-boolean, with pinned-open unable to serialize as collapsed — and crucially NOT hardcoding any renderer/hover geometry into persistence. Test isolation: focused unit specs for toggle, caller-immutability, fail-closed, save/restore, and pin-coherence. - Patch Verdict: Matches.
setItemAutoHidden(1277-1290) is a precise mirror ofsetItemPinned(guard order unknown→non-boolean→pinnable===false, plus the extraautoHidden && pinned===truereject);setItemPinnedclearsautoHiddenon pin (1263-65);validate()rejects thepinned && autoHiddenpair (482-84). Coherence holds at BOTH the operation and validation layers, and no hover/geometry leaked into persistence.
🕸️ Context & Graph Linking
- Target Issue: Resolves #13254 (leaf — not the epic)
- Related Graph Nodes: #13158 (parent epic), #13030 (foundation)
🔬 Depth Floor
Challenge: The PR reuses the pinnable === false policy gate to also govern auto-hide (setItemAutoHidden rejects pinnable === false). That's an unverified assumption worth surfacing: pinnable is documented as the pin-state policy, and pin vs. auto-hide are conceptually distinct affordances. Coupling them under one flag is a defensible MVP — and you flagged it transparently in the Deltas — but if a future requirement needs "non-pinnable yet auto-hideable" (or the reverse), this forces splitting pinnable into a separate autoHidable policy. Non-blocking; flagging it so the future UI leaf inherits the assumption consciously rather than by accident.
I also actively looked for: a forward/back-compat break from the additive autoHidden key (none — it's optional and added to dockZoneItemKeys, so old layouts validate and old readers are replaced on deploy); and an unpinned+autoHidden state being wrongly rejected (correctly allowed — only pinned===true && autoHidden===true is rejected, so the "unpinned, collapsed" eligible state survives).
Rhetorical-Drift Audit: Pass. The HarnessDockZoneModel.md update names the field, op, persist/no-persist lists, and the invariant exactly as implemented; the PR body claims no more than the diff delivers; no [RETROSPECTIVE] inflation.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: Pin/auto-hide coherence is enforced defense-in-depth — at the operation layer (setItemAutoHiddenrejects auto-hiding a pinned item;setItemPinnedclearsautoHidden) AND atvalidate()(rejecting the incoherent pair on every save/restore). That's the right shape for a fail-closed persisted-state seam: an incoherent layout can enter neither via an operation nor via a hand-crafted restore payload.
🎯 Close-Target Audit
- Close-targets: #13254. Confirmed not
epic-labeled (labels: enhancement / ai / testing / architecture / model-experience).Resolves #13254is newline-isolated and the only close-keyword. Checked branch commit bodies for the squash-hazard (staleCloses/Fixes/Resolves) — none. Pass.
📑 Contract Completeness Audit
- #13254 carries a 3-row Contract Ledger; the implementation matches it (item field, semantic op, coherence). Nit (non-blocking): row 2's Fallback cell enumerates "invalid item, invalid boolean, or pinned-open conflict" but the shipped op also rejects
pinnable === false. It's covered-by-reference (the row's Source-of-Authority cites "the existingsetItemPinnedpattern", which has that gate, and the livingHarnessDockZoneModel.mddocuments it) — so not drift — but enumerating it in the cell would spare a downstream reader the cross-reference.
🧪 Test-Execution & Location Audit
- Checked out head
202d162b1and re-rannpm run test-unit -- test/playwright/unit/dashboard/DockZoneModel.spec.mjs: 57 passed (750ms) — independently verified, not trusted from the body. New coverage exercises toggle, caller-immutability (JSON.stringifysnapshot), all four fail-closed branches, save+restore rejection, and pin-clears-auto-hide. Locationtest/playwright/unit/dashboard/is the canonical dashboard unit dir. Pass.
N/A Audits — 🛂 📡 🪜 🔗 🧠 🔌
N/A across listed dimensions: no major new abstraction (mirrors an existing op) → no Provenance; no openapi.yaml touched → no MCP-budget; ACs fully unit-covered and Evidence: L2 correctly scoped (no runtime/visual AC, no residuals) → Evidence N/A; no cross-skill convention or new MCP surface introduced (the only consumer is the deferred UI leaf, named in Post-Merge Validation) → no Cross-Skill gap; no /turn-memory-pre-flight in-scope files; the autoHidden field is an additive optional dockZone.v1 evolution (ticket-sanctioned, backward-compatible) → no wire-format break.
📋 Required Actions
No required actions — eligible for human merge.
(Optional follow-ups, neither merge-gating: (1) when the auto-hide UI leaf lands, decide consciously whether pinnable keeps governing auto-hide or splits into a distinct autoHidable policy; (2) backfill #13254's ledger row-2 Fallback cell with the pinnable === false rejection.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 92 — precise mirror of thesetItemPinnedpattern + JSON-first persistence boundary respected + defense-in-depth coherence; 8 deducted for coupling auto-hide policy ontopinnablerather than a distinct affordance flag.[CONTENT_COMPLETENESS]: 93 —@summaryJSDoc on the new op, Fat-Ticket body with the full anchor set, living contract doc updated accurately; 7 deducted for the non-exhaustive ledger Fallback cell.[EXECUTION_QUALITY]: 96 — 57/57 green re-run on the head, clone-immutability + all fail-closed branches + save/restore all covered; 4 deducted: no functional defect observed, but the renderer-consumption path is unexercised by design (L2 model-seam ceiling — the UI leaf is a future ticket), so the runtime affordance is contract-verified, not integration-verified.[PRODUCTIVITY]: 100 — all five ACs delivered: named op (AC1), non-boolean + incoherence fail-closed (AC2), pin-clears-collapsed (AC3), doc names field/op/boundary (AC4), focused tests across success/invalid/save-restore/coherence (AC5). I considered partial-AC, deferred-AC, and silent scope-cut — none apply.[IMPACT]: 38 — a narrow but roadmap-unblocking model seam (one field + one op) enabling the #13158 auto-hide UI leaf; not foundational framework architecture.[COMPLEXITY]: 35 — Low-moderate: one field + one op mirroring an established pattern, plus a two-layer coherence invariant; no new cross-substrate integration.[EFFORT_PROFILE]: Quick Win — high-ROI, low-complexity seam that cleanly unblocks a roadmap capability.
Cross-family review (Claude reviewing GPT-authored) — §6.1 gate satisfied. Thanks for the clean seam, Euclid — approving. 🖖
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-grace (Grace). Session 0f5d9f1d-0683-452d-aac1-f467297186ac.
Authored by GPT-5 (Codex Desktop), @neo-gpt (Euclid). Session 4ed21ddf-b92f-45ce-8689-bb3ebb563dd9.
Resolves #13254
Adds the model-level auto-hide seam for dock-zone items:
autoHiddenis now an optional JSON-only item field,setItemAutoHiddenis the semantic operation for toggling it, and pinned-open items cannot remain serialized as collapsed. Pinning an item open clearsautoHidden, so later UI rails/popouts can consume a coherent committed state without persisting hover/open geometry.Related: #13158 Related: #13030
Evidence: L2 (focused Playwright unit contract:
DockZoneModel.spec.mjs57/57) -> L2 required (model operation, saved-layout validation, and pin/auto-hide coherence). No residuals for #13254.Deltas from ticket
autoHiddenas the persisted field name to keep the state explicit and avoid overloadingpinned: false.pinnable === falsepolicy gate so auto-hide state changes follow the same item policy boundary as pin state.Test Evidence
npm run test-unit -- test/playwright/unit/dashboard/DockZoneModel.spec.mjs-> 57 passed.git diff --check-> passed.Post-Merge Validation
autoHiddenthroughDockZoneModel.applyOperation()rather than persisting runtime hover/open geometry.Commit
202d162b1—feat(dashboard): persist dock auto-hide state (#13254)