Context
Chrome half of the pane-header parity family under epic #13158 (the action half lives in the #17928 sibling leaves): dense multi-pane compositions need tab chrome designed for embedding, and the current themes only offer one look each. Motivating consumer in-repo: apps/workstation, whose dock workspace renders the neo themes' free-standing tab chrome inside pane headers where it reads oversized and disconnected. Anchors verified at Engine dev@34499f737b.
The Problem
The classic themes (theme-light / theme-dark) style tab containers for inline placement — flush strips that sit inside surrounding chrome. The two neo themes (theme-neo-light / theme-neo-dark) style free-standing tab containers — card-like, elevated, generous. Both looks are correct for their original context and wrong for the other: inside a dock workspace every tabs node is embedded in a pane header band, and the free-standing design fights the composition (workstation shows it today). There is no per-instance selector; a consumer gets whatever its theme ships.
The Architectural Reality
- The variant mechanism already exists and costs nothing:
component.Base#afterSetUi (src/component/Base.mjs:894) renders ui: 'x' as a neo-<ntype>-x class — for tab.Container, neo-tab-container-inline. No new config machinery, no projection change beyond a value.
- Theme substrate: base sheets at
resources/scss/src/tab/{Container,Strip,header/}; per-theme overrides exist (verified: theme-neo-light/tab/). Four themes declared: theme-light, theme-dark, theme-neo-light, theme-neo-dark.
- Dock seam: the adapter projects tabs-node config at
src/dashboard/dock/projection/LayoutAdapter.mjs:1004-1013 (cls: ['neo-dashboard-dock-tabs']) — one added ui value flows to every dock consumer, apps/workstation included, with zero app changes.
The Fix
Define two documented ui variants for Neo.tab.Container:
standalone — the free-standing look: exactly the neo themes' current design, now addressable by name.
inline — the embedded look: flush compact strip, reduced radius/elevation, and a header background-gradient hook, frozen here as a public surface: the structural sheet consumes background-image: var(--tab-header-inline-background-image, none) on .neo-tab-container-inline > .neo-tab-header-toolbar; theme value layers (or a consumer root) may set the property. The none default keeps today's flat paint, and ui: null containers never match the owning selector, so a globally-set hook cannot leak outside the inline variant. Dense pane headers are where a subtle band gradient earns its keep.
Naming decision (recorded): inline / standalone — over "free", which reads ambiguously (free-standing vs unconstrained).
Defaults preserve every current consumer: ui: null keeps each theme's existing look, byte-for-byte. The neo themes gain real inline styling; the classic themes map inline to (approximately) their current look and standalone to a coherent card variant, so both values mean something in all four themes. The dock adapter projects ui: 'inline' on the tabs nodes it builds — the one behavior change, scoped to dock projections, which is precisely the context whose chrome is wrong today.
Contract Ledger (consumed cells)
| Cell |
Producer |
Behavior |
Fallback |
Docs |
Evidence |
| variant selector |
component.Base#afterSetUi (src/component/Base.mjs:894) — exists |
ui: 'inline' ⇒ neo-tab-container-inline on the container root; standalone likewise; null ⇒ no variant class |
Current look |
tab.Container config jsdoc documents both values |
Mechanism shipping engine-wide |
| theme styling |
NEW SCSS blocks under the tab sheets for all four themes |
Distinct, documented chrome per variant per theme; gradient hook = --tab-header-inline-background-image, consumed as background-image: var(--tab-header-inline-background-image, none) on .neo-tab-container-inline > .neo-tab-header-toolbar (the --tab-* token family) |
Unset hook = none = flat header, today's look; ui: null never matches the owning selector |
SCSS block comments name the two variants' intent + the hook |
Theme override structure exists (theme-neo-light/tab/) |
| dock projection |
projection.LayoutAdapter tabs-node config (LayoutAdapter.mjs:1004-1013) |
Projects ui: 'inline' on every projected tabs node |
n/a (dock chrome is the motivating context) |
Inline comment at the projection site |
Projection seam shipping; #17926/#17939 precedent for threading one option through it |
| non-dock consumers |
untouched |
ui: null default keeps every existing tab container byte-identical |
n/a |
Config jsdoc |
AC pins it |
Decision Record impact
none — theming plus one projected config value; no model, operation, or ADR surface. (ADR 0029 §2.6's embedded-product-surface consumers are downstream beneficiaries, not contract parties here.)
Acceptance Criteria
Out of Scope
- Redesigning either theme's
standalone look — it is today's neo design, renamed.
- Tab body styling, icon work, or the header actions (the sibling leaves).
- Per-app theme work in consumers.
Related
Parent: #13158 (the closure gate's experience-parity matrix is where dense-composition chrome gets judged). Siblings: #17928 and the header-action leaves filed alongside. Public consumer: apps/workstation.
Origin Session ID: 914dffcd-0057-46a5-a0c6-9ca807fd5c91
Retrieval Hint: "tab container ui inline standalone variant dock header chrome gradient"
— Vega (Fable 5, Claude Code) 🌿
Context
Chrome half of the pane-header parity family under epic #13158 (the action half lives in the
#17928sibling leaves): dense multi-pane compositions need tab chrome designed for embedding, and the current themes only offer one look each. Motivating consumer in-repo:apps/workstation, whose dock workspace renders the neo themes' free-standing tab chrome inside pane headers where it reads oversized and disconnected. Anchors verified at Enginedev@34499f737b.The Problem
The classic themes (
theme-light/theme-dark) style tab containers for inline placement — flush strips that sit inside surrounding chrome. The two neo themes (theme-neo-light/theme-neo-dark) style free-standing tab containers — card-like, elevated, generous. Both looks are correct for their original context and wrong for the other: inside a dock workspace every tabs node is embedded in a pane header band, and the free-standing design fights the composition (workstation shows it today). There is no per-instance selector; a consumer gets whatever its theme ships.The Architectural Reality
component.Base#afterSetUi(src/component/Base.mjs:894) rendersui: 'x'as aneo-<ntype>-xclass — fortab.Container,neo-tab-container-inline. No new config machinery, no projection change beyond a value.resources/scss/src/tab/{Container,Strip,header/}; per-theme overrides exist (verified:theme-neo-light/tab/). Four themes declared:theme-light,theme-dark,theme-neo-light,theme-neo-dark.src/dashboard/dock/projection/LayoutAdapter.mjs:1004-1013(cls: ['neo-dashboard-dock-tabs']) — one addeduivalue flows to every dock consumer,apps/workstationincluded, with zero app changes.The Fix
Define two documented
uivariants forNeo.tab.Container:standalone— the free-standing look: exactly the neo themes' current design, now addressable by name.inline— the embedded look: flush compact strip, reduced radius/elevation, and a header background-gradient hook, frozen here as a public surface: the structural sheet consumesbackground-image: var(--tab-header-inline-background-image, none)on.neo-tab-container-inline > .neo-tab-header-toolbar; theme value layers (or a consumer root) may set the property. Thenonedefault keeps today's flat paint, andui: nullcontainers never match the owning selector, so a globally-set hook cannot leak outside the inline variant. Dense pane headers are where a subtle band gradient earns its keep.Naming decision (recorded):
inline/standalone— over "free", which reads ambiguously (free-standing vs unconstrained).Defaults preserve every current consumer:
ui: nullkeeps each theme's existing look, byte-for-byte. The neo themes gain realinlinestyling; the classic themes mapinlineto (approximately) their current look andstandaloneto a coherent card variant, so both values mean something in all four themes. The dock adapter projectsui: 'inline'on the tabs nodes it builds — the one behavior change, scoped to dock projections, which is precisely the context whose chrome is wrong today.Contract Ledger (consumed cells)
component.Base#afterSetUi(src/component/Base.mjs:894) — existsui: 'inline'⇒neo-tab-container-inlineon the container root;standalonelikewise;null⇒ no variant classtab.Containerconfig jsdoc documents both values--tab-header-inline-background-image, consumed asbackground-image: var(--tab-header-inline-background-image, none)on.neo-tab-container-inline > .neo-tab-header-toolbar(the--tab-*token family)none= flat header, today's look;ui: nullnever matches the owning selectortheme-neo-light/tab/)projection.LayoutAdaptertabs-node config (LayoutAdapter.mjs:1004-1013)ui: 'inline'on every projected tabs node#17926/#17939precedent for threading one option through itui: nulldefault keeps every existing tab container byte-identicalDecision Record impact
none— theming plus one projected config value; no model, operation, or ADR surface. (ADR 0029 §2.6's embedded-product-surface consumers are downstream beneficiaries, not contract parties here.)Acceptance Criteria
ui: 'inline'andui: 'standalone'render distinct, intentional chrome intheme-neo-lightandtheme-neo-dark; both values are also styled (not no-ops) in the classic themes. Visual evidence beat per the epic's closure-gate discipline (recorded comparison or screenshot pair per theme).ui: null) tab containers are byte-identical in DOM and cls to today in all four themes (regression spec).ui: 'inline'; a projected dock tabs node carriesneo-tab-container-inline(headless spec), andapps/workstationinherits the inline chrome with zero app-side changes.getComputedStyleread of--tab-header-inline-background-imageproves unset ⇒background-image: none(flat, today's paint) and set ⇒ the gradient renders on the inline header toolbar only — never on aui: nullcontainer (both witnessed).Out of Scope
standalonelook — it is today's neo design, renamed.Related
Parent: #13158 (the closure gate's experience-parity matrix is where dense-composition chrome gets judged). Siblings: #17928 and the header-action leaves filed alongside. Public consumer:
apps/workstation.Origin Session ID: 914dffcd-0057-46a5-a0c6-9ca807fd5c91
Retrieval Hint: "tab container ui inline standalone variant dock header chrome gradient"
— Vega (Fable 5, Claude Code) 🌿