LearnNewsExamplesServices
Frontmatter
id17419
titleRoute Dock tab close actions through model policy
stateClosed
labels
enhancementaitestingcoreaccessibility
assigneesneo-gpt-emmy
createdAtAug 20, 2026, 3:54 PM
updatedAtAug 23, 2026, 8:18 PM
githubUrlhttps://github.com/neomjs/neo/issues/17419
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 17418 Add flat actions to TabContainer header toolbars
blocking[]
closedAtAug 23, 2026, 8:18 PM

Route Dock tab close actions through model policy

Closed Backlog/active-chunk-17 enhancementaitestingcoreaccessibility
neo-gpt-emmy
neo-gpt-emmy commented on Aug 20, 2026, 3:54 PM

Context

Discussion #17415 graduates DockLayout integration as the second bounded leaf after the generic flat TabContainer-actions contract.

The generic blocker is delivered: #17418 closed through PR #17423. The Dock workspace authority also moved after this ticket was filed: PR #17545 introduced Neo.dashboard.DockWorkspace as the engine-owned reducer/projection/reconciliation host, and PR #17565 migrated Workstation onto it. This ticket now extends that class instead of adding another consumer-owned close loop.

The Dock item schema already carries closable, but only through the serialization allow-list. No runtime path reads it. DockZoneModel.closeItem currently permits every item, and projected tab chrome must never remove a pane before the committed Dock document changes.

Live latest-open and all-state duplicate sweeps remain clean; no equivalent implementation ticket or PR exists.

The Problem

An optional persistent close action in a projected TabContainer needs Dock semantic authority:

  • the active Dock item must be resolved at dispatch time, after activation or reorder;
  • explicit closable: false must fail closed in the operation layer, not only disappear visually;
  • an absent closable field must preserve current behavior and remain closeable;
  • the model must commit exactly once before projection changes chrome;
  • action visibility must follow the live active item while the action instance survives reconciliation;
  • closing the focused active item must land focus on a deterministic successor rather than document.body.

A direct TabContainer.removeAt() would let runtime chrome outrun model truth and would bypass serialized policy.

The Architectural Reality

  • DockZoneModel.closeItem is the semantic mutation and successor-selection authority.
  • DockWorkspace owns the one committed document, the reducer call, view-sync scheduling, policy-visibility resync, and focus after the reconciler settles.
  • DockLayoutAdapter projects the persistent action and forwards clone-safe runtime intent. It never owns the close effect.
  • DockProjectionReconciler owns chrome convergence after the document commit and preserves the stable action instance on retained tab containers.
  • closable adds no schema field or migration; this ticket gives an existing serialized field runtime meaning.
  • The generic action capability emits intent and preserves explicit handler precedence. Dock supplies the effect without serializing functions into the document.

The Fix

  1. Add an opt-in DockWorkspace close-action configuration, disabled by default and threaded into every projection as class-owned policy.
  2. Let DockLayoutAdapter materialize one persistent close action and forward headerAction / active-index intent through runtime closures only.
  3. At dispatch, let DockWorkspace resolve the current item from the live tab container's activeIndex plus its reconciled dockItemIds; never capture an index or pre-projection item array.
  4. Route exactly one {operation: 'closeItem', itemId} through applyDockZoneOperation() and call onDockZoneDocumentChange() only on success.
  5. Enforce closable inside DockZoneModel.closeItem: absent means allowed; explicit false returns a named failure with no document change.
  6. Keep the exact action instance across retained-tab reconciliation. Synchronize its hidden/available state on active-index changes and after every settled projection from the current model policy.
  7. Make closeItem choose the deterministic same-stack successor: the item now occupying the closed index, otherwise the preceding item, otherwise null.
  8. After the committed projection settles, focus the successor tab header; if closing the last item makes normalization prune that tabs node, focus the surviving DockWorkspace root. Failed operations change neither chrome nor focus.
  9. Prove reorder, Overflow, orientation, retained-topology, cross-stack, and tear-out/re-entry paths do not capture stale identities or double-commit.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
Dock close action config DockWorkspace runtime policy opt-in persistent flat action; disabled by default omitted config preserves current UI class config + projection JSDoc workspace/adapter projection specs
Active target live TabContainer activeIndex + reconciled dockItemIds resolve current itemId at dispatch no active item = no-op/unavailable handler JSDoc reorder-before-click arm
closable policy DockZoneModel.closeItem absent allows; explicit false returns named refusal current documents remain closeable model JSDoc direct operation specs
Commit ordering DockWorkspace holder pipeline one semantic commit, then reconciliation operation failure leaves chrome untouched workspace/reconciler JSDoc trace/count witness
Policy visibility committed model + live active identity stable action instance mirrors current item policy missing/non-active item hides action adapter/workspace JSDoc active-switch + retained-projection specs
Focus successor model successor + settled DockWorkspace.refreshPromise successor tab header, else surviving DockWorkspace root after empty-node pruning never document.body focus policy JSDoc pointer/keyboard close matrix
Serialized document existing item allow-list no new field and no function/action config persisted N/A N/A round-trip equality

Decision Record impact

None. The ticket assigns runtime semantics to the existing closable field and consumes the landed DockWorkspace authority without changing the serialized Dock schema.

Decision Record

Not needed, inherited from Discussion #17415 and accepted ADR 0029 as amended by PR #17545.

Signal Ledger

  • GPT author family: low-blast graduation fold in D#17415.
  • Claude non-author family: substantive peer cycle at DC_kwDODSospM4BFBWY.

Unresolved Dissent

None.

Unresolved Liveness

None for this low-blast gate.

Discussion Criteria Mapping

  • OQ5 → generic intent, Dock-owned explicit effect.
  • OQ6 → absent=true and explicit-false model refusal.
  • OQ7 → successor header/root focus after committed projection.
  • OQ8 → every orientation.
  • OQ10 → action-space/local-sort geometry remains distinct from outer Dock boundaries.
  • Invariants 8–10 → semantic commit authority and dispatch-time identity.

Acceptance Criteria

  • Feature disabled: projected Dock tabs render no close action and current behavior is unchanged.
  • Feature enabled: one persistent close action is visible whenever an active, closeable item exists, independent of contextual focus state.
  • DockZoneModel.closeItem accepts missing closable and rejects explicit false with a stable named error and byte-identical document.
  • Projection visibility mirrors policy, but a forged/direct operation against explicit false still fails at the model layer.
  • The handler resolves the current active itemId at dispatch time after activation/reorder and commits exactly one closeItem operation.
  • Failed operations leave pane, tab header, active index, action instance, and focus unchanged; successful operations update chrome only through reconciliation.
  • Closing the first, middle, last, only, and reordered active item selects the deterministic surviving item and never closes a stale target.
  • Focus moves after the projection settles to the successor tab header, or to the surviving DockWorkspace root when empty-node normalization removes the tab shell; it never falls to document.body.
  • Top/right/bottom/left, Overflow-active, retained-topology, cross-stack, and tear-out/re-entry cases preserve action placement and exactly-once semantics.
  • No handler/function/action config enters the serialized Dock document.
  • Focused unit/model/workspace/reconciliation coverage plus a Whitebox E2E close journey pass at exact head.

Out of Scope

  • Generic toolbar/TabContainer action materialization (delivered by #17418 / PR #17423).
  • Custom application action semantics.
  • Inline close icons per tab.
  • Changing the Dock document schema or adding a migration.
  • Automatic persistence of action configs/functions.
  • Broad focus, reconciliation, or drag-system refactoring.

Avoided Traps

  • Direct TabContainer.removeAt(): removes chrome before model truth and bypasses policy.
  • Adapter-owned effect: makes a pure projector a stateful host and duplicates DockWorkspace.
  • Visual-only closable: hides one button but leaves every operation caller able to close.
  • Captured active index/item array: closes the wrong item after reorder, activation, or reconciliation.
  • Replacing the action group on every projection: destroys the stable action instance PR #17423 established.
  • Chrome-first focus repair: can focus an element reconciliation immediately destroys.
  • Absent=false default: would silently make every existing Dock item non-closeable.

Related

Origin Session ID: 0f8b5b8e-3f01-45c8-889e-1c2fd90b0584

Retrieval Hint: "DockWorkspace TabContainer close action closable semantic model focus successor"

Emmy (GPT-5.6 Sol Ultra, Codex) · @neo-gpt-emmy 🪡

tobiu referenced in commit d35de7e - "feat(dashboard): add model-authoritative dock close actions (#17419) (#17626) on Aug 23, 2026, 8:18 PM
tobiu closed this issue on Aug 23, 2026, 8:18 PM