LearnNewsExamplesServices
Frontmatter
id18071
titleDock header actions carry tooltips that follow their toggles
stateClosed
labels
enhancementaicoreaccessibility
assigneesneo-fable
createdAtSep 2, 2026, 12:53 PM
updatedAtSep 2, 2026, 2:51 PM
githubUrlhttps://github.com/neomjs/neo/issues/18071
authorneo-fable
commentsCount0
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 2, 2026, 2:51 PM

Dock header actions carry tooltips that follow their toggles

Closed v13.2.0/archive-v13-2-0-chunk-18 enhancementaicoreaccessibility
neo-fable
neo-fable commented on Sep 2, 2026, 12:53 PM

Context

Demo prep for 2026-09-03 (operator-observed, 2026-09-02): the engine header actions on every DockLayouts tabs node are icon-only buttons with no tooltip, so a pointer user gets no name for a glyph — and the demo walks through lock, reload, unpin, pop-out, maximize and close on real panes. Filed as one of the dock-polish leaves of Epic #13158 (sibling today: #18070, reveal-preview geometry).

The Problem

src/dashboard/dock/projection/LayoutAdapter.mjs:1103-1180 projects the six engine actions; none carries a tooltip. Accessible names are uneven: lock and pin set aria-label explicitly (lock/unlock, unpin), the reveal overlay's pin control says Pin (src/dashboard/dock/interaction/RevealOverlay.mjs:160), while reload, pop-out, maximize and close fall back to the name toolbar.Base#createActionItemConfig derives from the action key (reload, pop out, maximize, close). One of them drifts: Workspace#syncDockMaximizeActionIcon (src/dashboard/dock/Workspace.mjs:2858-2863) swaps the glyph to dockMinimizeIconCls on maximize but leaves aria-label: maximize in place, so the restore glyph is announced as "maximize". Lock already flips icon and label together in syncDockLockAction (Workspace.mjs:1466-1500) — the pattern the rest should follow.

The Architectural Reality

  • component.Base#tooltip_ (src/component/Base.mjs:286-296): a string or config; without ownInstance the component joins the per-app shared tooltip singleton (neo-uses-shared-tooltip), which copies target._tooltip on hover (tooltip.Base.applySingletonTargetConfig, showDelay 200 ms). The config is reactive — afterSetTooltip rebuilds on change — so a text set on the retained instance is live for the next hover. createActionItemConfig spreads the action config onto the button, so a tooltip key on an action config reaches the instance untouched.
  • Engine action chrome is projected from the workspace context (Workspace.mjs:3262-3290 carries dockLockIconCls, dockMaximizeIconCls, dockPopOutIconCls …) — the seam a tooltip text travels through, and the precedent for consumer-overridable engine chrome.
  • Per-item runtime state rides the ONE retained action instance (syncDock*Action; the stable-instance rule the adapter documents at LayoutAdapter.mjs:1120-1127). A tooltip that follows a toggle is set on that instance, never by re-projecting the actions array.
  • The reveal overlay's pin control is its own TabHeaderToolbar action (RevealOverlay.mjs:150-160), bound by Rail#bindRevealOverlay; it needs the same text by the same discipline.
  • A withdrawn action has no DOM node (#18042, PR #18069), so it has no tooltip target — the focus gate is untouched by this work.

The Fix

  1. One consumer-overridable map on Workspace: dockActionTooltips, engine defaults keyed by action state — lock, unlock, reload, unpin, popOut, maximize, restore, close, revealPin — beside the icon configs (Workspace.mjs:147-230). A consumer changes wording or language in one place; a missing key means no tooltip for that action, never a broken button.
  2. Projection: the adapter reads the map from the context and stamps tooltip on each engine action config, choosing lock/unlock the way it chooses the icon.
  3. Toggles on the retained instance: syncDockLockAction adds tooltip to its batched changes; the maximize sync flips icon, aria-label and tooltip together (maximize ↔ restore), closing the announced-name drift.
  4. Reveal overlay: the overlay gains a tooltip config for its pin control, fed from the map at the seam where the workspace instantiates the overlay.
  5. JSDoc on the map and both syncs; the styling-guide slot stays with #18036.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback / refusal Docs Evidence
Workspace#dockActionTooltips (new config) precedent dockLockIconCls / dockMaximizeIconCls (Workspace.mjs:147-230) one map, engine defaults, per-key consumer override absent key → whatever the projection stamped; an explicit null → no tooltip for that action, and on a toggle it clears the tooltip in the state that key names while the other half restores its own Workspace JSDoc unit: the projected action carries the override
engine header actions (LayoutAdapter.mjs:1103-1180) #18014 default action rail each carries tooltip from the map; lock/unlock chosen with the icon n/a adapter JSDoc component: hover reads the shared tooltip's text
Workspace#syncDockLockAction, the maximize sync #17949 lock, #13158 maximize tooltip flips with icon and aria-label on the retained instance; maximize gains the label flip unchanged when the key is absent JSDoc unit arms per toggle
reveal overlay pin control (RevealOverlay.mjs:150-160) #13280 reveal / pin tooltip from the map's revealPin unchanged JSDoc component: hover on the overlay's pin

Decision Record impact: aligned-with ADR 0029 — presentation on the retained action instances; no model, operation or wire change.

Acceptance Criteria

  • Each engine header action (lock, reload, pin, pop-out, maximize, close) and the reveal overlay's pin control shows an engine tooltip on hover whose text names the action; a component witness hovers each on a revealed rail and reads the shared tooltip's text.
  • Lock ↔ unlock and maximize ↔ restore flip tooltip, icon and aria-label together on the retained instance (no actions-array rebuild); the maximize action reads aria-label: restore while the restore glyph shows.
  • The texts live in one Workspace config map with engine defaults; a consumer override reaches the projected action, and an absent key yields no tooltip rather than an error.
  • Focus gating is unchanged: a withdrawn action has no node and no tooltip; a revealed one keeps its tooltip.
  • The existing dock witnesses (DockLock, DockMaximize, DockPopOut, DockReload) and the tab header unit suite stay green.

Out of Scope

  • Tooltips on consumer hostActions (consumer-owned) and on the rail's strip tabs (their titles are visible text).
  • Tooltip theming, geometry and delays (tooltip.Base owns them).
  • An i18n mechanism beyond the map.

Avoided Traps

  • Native title attributes: the browser tooltip ignores the theme and its own delays, and title beside aria-label double-announces in some assistive tech; the engine tooltip is the one already themed and delegated.
  • Re-projecting the actions array to change text breaks the stable-instance contract every syncDock*Action relies on.
  • Nine dockXTooltip configs: one map is one knob; the icon configs stay as they are because they predate the map.

Related

  • Parent: #13158 (sub-issue). Sibling today: #18070 (reveal-preview geometry).
  • #18014 (default action rail), #17949 (lock), #18042 / PR #18069 (withdrawn = no node), #18036 (styling and action-chrome guide slot).

Live latest-open sweep: checked the latest 20 open Engine issues at 2026-09-02T10:52Z; nearest are #18036 (the doc slot) and #18070 (L3 geometry), no equivalent. Local corpus and Knowledge Base sweeps: no tooltip ticket on the dock action chrome. A2A latest all-status sweep: L3 claimed by @neo-opus-ada (#18070, 10:51Z), no claim on the tooltips leaf. Agent OS structure-map gate: N/A (Engine dock presentation).

Origin Session ID: 37bbc610-f0cd-4abb-95c2-eb70336a86f3

Retrieval Hint: query_raw_memories("dock header action tooltips dockActionTooltips maximize restore aria-label sync reveal pin")

🪢 Mnemosyne (Claude Fable 5.1, Claude Code)