#18071 / PR #18072 gave the dock header actions tooltips, so apps/workstation renders Neo tooltips for the first time. It renders them in the stock theme blue.
Measured (dev server, apps/workstation, dev@fabb61f001, dark theme): a .neo-tooltip element rendered against the live tokens computes background rgb(62, 99, 221) — #3E63DD, declared as --tooltip-bg in resources/scss/theme-neo-light/tooltip/Base.scss and reached through the neo-dark theme's own value — with color rgb(255,255,255), border none, box-shadow none, border-radius 4px. The app's chrome around it is --workstation-ground #0b0e13, --workstation-panel #141a23, --workstation-signal #5eead4 (teal). Operator-reported from the running app.
The Workstation is its own theme authority — theme-neo-{dark,light}/apps/workstation/Viewport.scss open with "App-owned palette: Workstation is its own theme authority" — so a stock-blue overlay is the one piece of chrome that does not belong to it.
The Problem
Nothing is broken; a surface the app never consumed before is now visible and unclaimed. The Workstation already answers this exact question for every other reused engine surface: it projects its palette into the engine's token slots rather than re-declaring rules. --agent-dock-preview-*, --agent-dock-proxy-border and the dock rail/reveal families are all filled that way. Tooltips are simply the newest member of that set and have no projection yet.
The scope matters and is the one non-obvious part. Neo.tooltip.Base.createSingleton builds one shared instance per app with componentId: app.mainView.id, and the node is created lazily on first target-over — it does not live inside .workstation-workspace. So a projection scoped to the workspace would never reach it. The themed root in this app is BODY.neo-theme-neo-dark, which is why the palette belongs at the :root .neo-theme-neo-* scope the app's Viewport theme files already use.
The Architectural Reality
resources/scss/src/tooltip/Base.scss — the whole rule is eight tokens: --tooltip-bg, --tooltip-border, --tooltip-borderradius, --tooltip-boxshadow, --tooltip-color, --tooltip-fontsize, --tooltip-height, --tooltip-padding. No rule to re-declare; the surface is already consumer-skinnable.
resources/scss/theme-neo-{dark,light}/tooltip/Base.scss — the theme defaults, including the #3E63DD this ticket is about.
resources/scss/theme-neo-{dark,light}/apps/workstation/Viewport.scss — the app's palette and its existing projections, at :root .neo-theme-neo-*. Loaded only when the Workstation Viewport class instantiates, so the scope is broad but the file is not.
resources/scss/src/apps/workstation/Workspace.scss:98-105 — the reveal overlay's projection, which is the grammar this should match: a surface token, a signal-tinted border, a ground-based elevation.
The Fix
Add a --tooltip-* projection block to both theme-neo-dark/apps/workstation/Viewport.scss and theme-neo-light/apps/workstation/Viewport.scss, beside the existing --agent-dock-preview-* projections, reading only the app's own palette variables. Values follow the reveal overlay's grammar so the tooltip reads as a member of the same family: a signal-tinted elevated surface, a signal-tinted border against the app's line colour, app ink, and an elevation appropriate to each theme's ground.
Structure is untouched: no rule is re-declared, --tooltip-height and --tooltip-padding stay with the engine, and every other consumer renders byte-identically because the file only loads with the Workstation Viewport.
Contract Ledger
Target Surface
Source of Authority
Proposed Behavior
Fallback
Docs
Evidence
--tooltip-{bg,color,border,boxshadow} under the Workstation
src/tooltip/Base.scss (the eight-token rule); the app's own body:has() bridges for floating direct-body chrome
resolve from the Workstation palette in both themes, declared on the tooltip element itself (amended 2026-09-02 — see AC-3; the :root .neo-theme-neo-* shape ties with the theme sheet and loses on load order)
an app that does not load the Workstation Viewport keeps the theme defaults untouched
comment beside the block naming why the scope is the theme root and not the workspace
a rendered .neo-tooltip computes the app's values in both themes; the stock blue appears in neither
Decision Record impact:none — a consumer filling engine token slots is the documented path, not a deviation.
Acceptance Criteria
AC-1 In apps/workstation, neo-dark: a rendered .neo-tooltip computes a background derived from the Workstation palette, ink-coloured text, a signal-tinted border and a visible elevation — and notrgb(62,99,221).
AC-2 Same in neo-light, from that theme's own palette; the two are independently derived, not one value reused.
AC-3 A tooltip mounted outside the workspace subtree resolves the app's values. Amended 2026-09-02 on the reviewer's ask: the prescription below said "at the theme root"; the shipped scope is the ELEMENT (body:has(.workstation-viewport) .neo-tooltip). The theme-root shape was implemented first and measured as a complete no-op — the tooltip theme sheet declares at that same :root .neo-theme-* weight and loads later, so a theme-root projection ties and loses on order. Naming the element also lets the projection follow the singleton's own runtime theme stamp, which a body-inherited one cannot.
AC-4 No rule in src/tooltip/Base.scss is re-declared and no engine default changes; an app that does not load the Workstation Viewport renders byte-identically.
AC-5 Read by eye in the running app in both themes, since the acceptance here is visual.
Out of Scope
A --tooltip-fontfamily token. The engine's rule has no font-family slot, so matching the app's mono voice would need an engine addition (the precedent exists — --dock-rail-tab-font-family), and re-declaring font-family app-side is exactly the re-declaration this ticket avoids. Worth its own leaf if the sans voice reads wrong.
Every other consumer's tooltips, and the engine's own theme defaults.
--tooltip-height / --tooltip-padding: structure, not identity.
Avoided Traps
Scoping the projection to .workstation-workspace. The shared tooltip is not a descendant of it; the projection would silently never apply, and the symptom would read as "the tokens do not work" rather than "the scope is wrong".
Re-declaring .neo-tooltip in app SCSS. The engine comment on the rail tabs already names this trap: an app rule in an equal-specificity tie with the engine's, won on load order.
Reusing one colour for both themes. The app's light and dark palettes are independently authored; a shared literal would drift from whichever one moves first.
Related
#18071 / PR #18072 (which made tooltips visible here) · #17633 (rail paint tokens — the same promote-to-token pattern) · #17614 · #18036 (the DockLayouts styling guide slot, where this belongs once written)
Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T14:12Z; no equivalent found, and a tooltip workstation styling theme search returns nothing. A2A: no competing claim in the window; the operator raised this directly. Structure-map gate §1c: N/A — app theme SCSS, no ai/ touch. Structural pre-flight: N/A — no new .mjs file.
Context
#18071 / PR #18072 gave the dock header actions tooltips, so
apps/workstationrenders Neo tooltips for the first time. It renders them in the stock theme blue.Measured (dev server,
apps/workstation,dev@fabb61f001, dark theme): a.neo-tooltipelement rendered against the live tokens computesbackground rgb(62, 99, 221)—#3E63DD, declared as--tooltip-bginresources/scss/theme-neo-light/tooltip/Base.scssand reached through the neo-dark theme's own value — withcolor rgb(255,255,255),border none,box-shadow none,border-radius 4px. The app's chrome around it is--workstation-ground #0b0e13,--workstation-panel #141a23,--workstation-signal #5eead4(teal). Operator-reported from the running app.The Workstation is its own theme authority —
theme-neo-{dark,light}/apps/workstation/Viewport.scssopen with "App-owned palette: Workstation is its own theme authority" — so a stock-blue overlay is the one piece of chrome that does not belong to it.The Problem
Nothing is broken; a surface the app never consumed before is now visible and unclaimed. The Workstation already answers this exact question for every other reused engine surface: it projects its palette into the engine's token slots rather than re-declaring rules.
--agent-dock-preview-*,--agent-dock-proxy-borderand the dock rail/reveal families are all filled that way. Tooltips are simply the newest member of that set and have no projection yet.The scope matters and is the one non-obvious part.
Neo.tooltip.Base.createSingletonbuilds one shared instance per app withcomponentId: app.mainView.id, and the node is created lazily on first target-over — it does not live inside.workstation-workspace. So a projection scoped to the workspace would never reach it. The themed root in this app isBODY.neo-theme-neo-dark, which is why the palette belongs at the:root .neo-theme-neo-*scope the app's Viewport theme files already use.The Architectural Reality
resources/scss/src/tooltip/Base.scss— the whole rule is eight tokens:--tooltip-bg,--tooltip-border,--tooltip-borderradius,--tooltip-boxshadow,--tooltip-color,--tooltip-fontsize,--tooltip-height,--tooltip-padding. No rule to re-declare; the surface is already consumer-skinnable.resources/scss/theme-neo-{dark,light}/tooltip/Base.scss— the theme defaults, including the#3E63DDthis ticket is about.resources/scss/theme-neo-{dark,light}/apps/workstation/Viewport.scss— the app's palette and its existing projections, at:root .neo-theme-neo-*. Loaded only when the Workstation Viewport class instantiates, so the scope is broad but the file is not.resources/scss/src/apps/workstation/Workspace.scss:98-105— the reveal overlay's projection, which is the grammar this should match: a surface token, a signal-tinted border, a ground-based elevation.The Fix
Add a
--tooltip-*projection block to boththeme-neo-dark/apps/workstation/Viewport.scssandtheme-neo-light/apps/workstation/Viewport.scss, beside the existing--agent-dock-preview-*projections, reading only the app's own palette variables. Values follow the reveal overlay's grammar so the tooltip reads as a member of the same family: a signal-tinted elevated surface, a signal-tinted border against the app's line colour, app ink, and an elevation appropriate to each theme's ground.Structure is untouched: no rule is re-declared,
--tooltip-heightand--tooltip-paddingstay with the engine, and every other consumer renders byte-identically because the file only loads with the Workstation Viewport.Contract Ledger
--tooltip-{bg,color,border,boxshadow}under the Workstationsrc/tooltip/Base.scss(the eight-token rule); the app's ownbody:has()bridges for floating direct-body chrome:root .neo-theme-neo-*shape ties with the theme sheet and loses on load order).neo-tooltipcomputes the app's values in both themes; the stock blue appears in neitherDecision Record impact:
none— a consumer filling engine token slots is the documented path, not a deviation.Acceptance Criteria
apps/workstation, neo-dark: a rendered.neo-tooltipcomputes a background derived from the Workstation palette, ink-coloured text, a signal-tinted border and a visible elevation — and notrgb(62,99,221).body:has(.workstation-viewport) .neo-tooltip). The theme-root shape was implemented first and measured as a complete no-op — the tooltip theme sheet declares at that same:root .neo-theme-*weight and loads later, so a theme-root projection ties and loses on order. Naming the element also lets the projection follow the singleton's own runtime theme stamp, which a body-inherited one cannot.src/tooltip/Base.scssis re-declared and no engine default changes; an app that does not load the Workstation Viewport renders byte-identically.Out of Scope
--tooltip-fontfamilytoken. The engine's rule has no font-family slot, so matching the app's mono voice would need an engine addition (the precedent exists —--dock-rail-tab-font-family), and re-declaringfont-familyapp-side is exactly the re-declaration this ticket avoids. Worth its own leaf if the sans voice reads wrong.--tooltip-height/--tooltip-padding: structure, not identity.Avoided Traps
.workstation-workspace. The shared tooltip is not a descendant of it; the projection would silently never apply, and the symptom would read as "the tokens do not work" rather than "the scope is wrong"..neo-tooltipin app SCSS. The engine comment on the rail tabs already names this trap: an app rule in an equal-specificity tie with the engine's, won on load order.Related
#18071/ PR#18072(which made tooltips visible here) ·#17633(rail paint tokens — the same promote-to-token pattern) ·#17614·#18036(the DockLayouts styling guide slot, where this belongs once written)Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T14:12Z; no equivalent found, and a
tooltip workstation styling themesearch returns nothing. A2A: no competing claim in the window; the operator raised this directly. Structure-map gate §1c: N/A — app theme SCSS, noai/touch. Structural pre-flight: N/A — no new.mjsfile.Origin Session ID: 7596538d-5324-419a-b043-95c585d833ea
Retrieval Hint: "workstation tooltip token projection theme root shared singleton scope"
⚖️ Ada ·
@neo-opus-ada· Claude Opus 5 · Claude Code