Context
Dock pane headers gate their engine actions (lock, reload, pin, pop-out, maximize) and their host actions behind focus. Those actions appear only while the pane contains focus, so a pointer user has to click into a pane before seeing what it offers. This came up in manual testing of apps/workstation (see #19982 step 2). The request is an opt-in that also reveals the gated actions while the pointer is over the pane. Focus stays the default trigger.
The Problem
Focus is the only trigger that reveals the gated actions. That is right for keyboard users and for a quiet header, but it hides the pane's capabilities from a pointer user until they click. There is no configuration that reveals them on hover.
The Architectural Reality
src/toolbar/Base.mjs: contextualActionsVisible_ is derived from the focus subject's containsFocus. focusSubjectId resolves the subject; for a tab header that is the TabContainer, i.e. the whole pane. isFocusGatedAction() is showOnFocus === true && pressed !== true. applyContextualActionState() withholds an inactive gated action from the DOM (removeDom, inert, aria-hidden, tabIndex: -1) and publishes one owner update.
src/tab/header/Toolbar.mjs: actionDefaults: {showOnFocus: true}.
src/dashboard/dock/projection/LayoutAdapter.mjs: projects the engine action set with the focus-gating default, and already threads a hover opt-in for another surface (autoHideRevealOnHover, for auto-hidden rails). That is the precedent for a workspace-level option.
src/tab/plugin/Overflow.mjs: withheld actions contribute no extent, so revealing them shrinks the tab strip and re-partitions the tabs. Its comment calls that intended for focus changes.
The Fix
toolbar.Base: add a second reveal trigger. A pointer entering or leaving the focus subject feeds the same contextualActionsVisible gate, so visible = focus OR hover OR pressed. It is off by default, so current behavior is unchanged. For the name, showOnHover (form.field.trigger.Base, default false) is the engine's existing spelling, and showOnFocus was named to match it (#17874).
- Dock: a workspace option, threaded through
LayoutAdapter to every projected tab header in the style of autoHideRevealOnHover.
- Layout stability (required): with hover reveal on, the tab partition must not move when the pointer crosses a pane. Partition against the revealed action extent whenever the option is on, so tabs never jump into or out of the overflow menu under the pointer. Keyboard focus behaves as it does today.
Acceptance Criteria
Contract Ledger
Backfilled by the ticket author during the review of #20011 (the ticket shipped without one).
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
Neo.toolbar.Base#showOnHover_ (new) |
Fix 1; spelling from Neo.form.field.trigger.Base#showOnHover |
true: the pointer over the focus subject (focusSubjectId, else parent) also reveals showOnFocus actions. Visible = focus OR hover; a pressed action stays revealed. |
Default false: focus-only, behavior unchanged. |
JSDoc |
HeaderActions.spec.mjs truth table |
Neo.dashboard.dock.Workspace#headerActionsShowOnHover_ (new) |
Fix 2; precedent LayoutAdapter's autoHideRevealOnHover |
Sets showOnHover on every projected tab header toolbar; a runtime change reaches the live headers. |
Default false. |
JSDoc + DockLayoutsFeatures.md |
WorkstationHeaderActionsHoverNL.spec.mjs |
LayoutAdapter.project(model, {headerActionsShowOnHover}) (new option) |
Fix 2 |
Sets showOnHover on each projected headerToolbar. |
Absent: false. One spelling, no alias. |
JSDoc |
DockLayoutAdapter.spec.mjs |
Neo.tab.plugin.Overflow partition under owner.showOnHover |
Fix 3 |
With the option on, partition against the revealed action extent: the same visible-header set before, during and after a hover. |
Option off: today's behavior, a withheld action contributes no extent. |
JSDoc |
Overflow.spec.mjs arm + the e2e above |
Out of Scope
- Changing the default trigger. Focus-only stays the default.
- Whether Workstation ships with the option on. That is a product call once the option exists.
- Touch input. It has no hover, so focus remains the trigger there.
Avoided Traps
- Hover toggling the action cluster in the layout. This repartitions tabs on every pointer pass, so the header flaps under the cursor. A focus-driven repartition was accepted in #18015, but focus changes on a click; hover changes on every pass. That is why the third Fix item is required, not optional.
- A CSS-only
:hover rule. Inactive gated actions are withheld from the DOM (removeDom), not hidden by style, so there is nothing for CSS to reveal. The state has to go through the existing gate.
Related
- #19982: the bug found in the same testing pass; step 2 is the focus reveal.
- #17874: introduced
showOnFocus and the containsFocus gate. #18015 made inactive actions collapse.
autoHideRevealOnHover: the existing hover-option precedent in LayoutAdapter.
Live latest-open sweep: checked latest 20 open issues at 2026-10-22T09:04:47Z; no equivalent found.
A2A claim sweep: no overlapping lane-claim.
MC sweep: "show focus-gated dock header actions on hover…", 6 results; prior art #17874 / #18015 / #17876, and no decision against a hover trigger. GitHub search "header actions hover" / "showOnFocus" / "contextual actions focus": all closed, none equivalent.
Own-assignment sweep: 6 open, none overlapping.
unowned-rationale: an opt-in enhancement queued behind the Workstation bug batch (#19982 and its siblings); no release goal names it yet.
Origin Session ID: 70e59a84-efd6-4e7a-9262-c5478bd314ff
Retrieval Hint: "reveal focus-gated dock header actions on hover, showOnFocus, contextualActionsVisible"
Context
Dock pane headers gate their engine actions (lock, reload, pin, pop-out, maximize) and their host actions behind focus. Those actions appear only while the pane contains focus, so a pointer user has to click into a pane before seeing what it offers. This came up in manual testing of
apps/workstation(see #19982 step 2). The request is an opt-in that also reveals the gated actions while the pointer is over the pane. Focus stays the default trigger.The Problem
Focus is the only trigger that reveals the gated actions. That is right for keyboard users and for a quiet header, but it hides the pane's capabilities from a pointer user until they click. There is no configuration that reveals them on hover.
The Architectural Reality
src/toolbar/Base.mjs:contextualActionsVisible_is derived from the focus subject'scontainsFocus.focusSubjectIdresolves the subject; for a tab header that is the TabContainer, i.e. the whole pane.isFocusGatedAction()isshowOnFocus === true && pressed !== true.applyContextualActionState()withholds an inactive gated action from the DOM (removeDom,inert,aria-hidden,tabIndex: -1) and publishes one owner update.src/tab/header/Toolbar.mjs:actionDefaults: {showOnFocus: true}.src/dashboard/dock/projection/LayoutAdapter.mjs: projects the engine action set with the focus-gating default, and already threads a hover opt-in for another surface (autoHideRevealOnHover, for auto-hidden rails). That is the precedent for a workspace-level option.src/tab/plugin/Overflow.mjs: withheld actions contribute no extent, so revealing them shrinks the tab strip and re-partitions the tabs. Its comment calls that intended for focus changes.The Fix
toolbar.Base: add a second reveal trigger. A pointer entering or leaving the focus subject feeds the samecontextualActionsVisiblegate, so visible = focus OR hover OR pressed. It is off by default, so current behavior is unchanged. For the name,showOnHover(form.field.trigger.Base, defaultfalse) is the engine's existing spelling, andshowOnFocuswas named to match it (#17874).LayoutAdapterto every projected tab header in the style ofautoHideRevealOnHover.Acceptance Criteria
toolbar.Base.test/playwright/e2e/workstation/turns the option on, moves a real pointer across a 12-tab pane, and asserts both the reveal and the stable partition.Contract Ledger
Backfilled by the ticket author during the review of #20011 (the ticket shipped without one).
Neo.toolbar.Base#showOnHover_(new)Neo.form.field.trigger.Base#showOnHovertrue: the pointer over the focus subject (focusSubjectId, elseparent) also revealsshowOnFocusactions. Visible = focus OR hover; a pressed action stays revealed.false: focus-only, behavior unchanged.HeaderActions.spec.mjstruth tableNeo.dashboard.dock.Workspace#headerActionsShowOnHover_(new)LayoutAdapter'sautoHideRevealOnHovershowOnHoveron every projected tab header toolbar; a runtime change reaches the live headers.false.DockLayoutsFeatures.mdWorkstationHeaderActionsHoverNL.spec.mjsLayoutAdapter.project(model, {headerActionsShowOnHover})(new option)showOnHoveron each projectedheaderToolbar.false. One spelling, no alias.DockLayoutAdapter.spec.mjsNeo.tab.plugin.Overflowpartition underowner.showOnHoverOverflow.spec.mjsarm + the e2e aboveOut of Scope
Avoided Traps
:hoverrule. Inactive gated actions are withheld from the DOM (removeDom), not hidden by style, so there is nothing for CSS to reveal. The state has to go through the existing gate.Related
showOnFocusand thecontainsFocusgate. #18015 made inactive actions collapse.autoHideRevealOnHover: the existing hover-option precedent inLayoutAdapter.Live latest-open sweep: checked latest 20 open issues at 2026-10-22T09:04:47Z; no equivalent found. A2A claim sweep: no overlapping lane-claim. MC sweep: "show focus-gated dock header actions on hover…", 6 results; prior art #17874 / #18015 / #17876, and no decision against a hover trigger. GitHub search "header actions hover" / "showOnFocus" / "contextual actions focus": all closed, none equivalent. Own-assignment sweep: 6 open, none overlapping.
unowned-rationale: an opt-in enhancement queued behind the Workstation bug batch (#19982 and its siblings); no release goal names it yet.
Origin Session ID: 70e59a84-efd6-4e7a-9262-c5478bd314ff Retrieval Hint: "reveal focus-gated dock header actions on hover, showOnFocus, contextualActionsVisible"