# Theming coherence — consolidated (E-bis roll-up) updated-at: 2026-06-26 batches-complete: 1 canon refs: THEMING §4 (9 roles), §5 (sizes + 1:1 font), §6 (naming/slots), §16 (anti-patterns A–H), §19 (EIDOS_VARIANTS), §35 (scales — z/opacity/border) ## (a) Hardcoded-where-canonical values (the consolidation) | Value (literal) | Canonical token it should be | Components hardcoding it | Severity | | --- | --- | --- | --- | | `content-z` / `overlay-z` integers (`80`/`75`/`70`/`60`) | `--z-index-{overlay-ladder}` (THEMING §35-C) | select(80), dialog(70), popover(75/60), combobox(80), dropdown-menu(80) — **5/5** | **SYSTEMIC** | | `item-disabled-opacity: '0.55'` | `var(--opacity-disabled)` | dropdown-menu | MEDIUM | | `item-description-font-size: '0.85em'` | `var(--font-size-*)` (escapes the px/rem guard) | select | MEDIUM | | `group-heading-letter-spacing: '0.04em'` | `var(--tracking-caps)` | select | LOW | | `item-description-mt: '0.125rem'` | `var(--space-0-5)` | select | LOW | | `item-height: '2rem'` | `--list-item-height-*` / `--control-height-*` (list-surface layer) | dropdown-menu | LOW | > `dialog.overlay-opacity: '62%'` is left as recipe-legitimate (scrim opacity is a recipe knob, not on the > `--opacity-*` step scale) — noted, not flagged. ## (b) Near-duplicate tokens (same resolved value, different name) - `content-z: '80'` appears identically in **select**, **combobox**, **dropdown-menu** — three names, one value, no shared source. Folds into SYS-2 (MAGIC-Z). - No other near-duplicate token pairs surfaced in Batch 1 (the recipes alias `--color-*`/`--space-*`/`--radius-*` consistently). ## (c) Non-canonical role / variant / size / slot names found - **Roles:** NONE. All 5 use only the 9 canonical roles (`primary/secondary/tertiary/neutral` + `affirm/fulfill/risk/threat/loss`). No `success`/`warning`/`danger`/`info`. ✓ (THEMING §16-C clean.) - **Variants:** NONE invented. `SelectVariant = ControlVariant`, dialog uses position/size data-attrs (visual, eidos-owned, not "variants"). ✓ - **Sizes:** NONE invented (`xs..xl` subsets). One raw-rem size (`dropdown-menu.item-height: 2rem`) that should bridge the list-surface scale — drift, not an invented name. - **Slots:** NONE invented; recipes use `track/border/text/bg/hover/...` from the fixed vocabulary. ✓ ## (d) Per-component canon-conformance | Component | roles | sizes | variants | naming | no-overlap | | --- | --- | --- | --- | --- | --- | | select | ✓ | ✓ | ✓ | ✓ | ✗ (em-font literal escapes guard; magic-z; subset type=all-9 vs 8 accents) | | dialog | ✓ | ✓ | ✓ | ✓ | ✗ (dead `[data-dialog-trigger]` envelope overlaps composed Button; magic-z) | | popover | ✓ | ✓ | ✓ | ✓ | ⚠ (magic-z only) | | combobox | ✓ | ✓ | ✓ | ✓ | ⚠ (magic-z only) | | dropdown-menu | ✓ | ⚠ (item-height 2rem) | ✓ | ✓ | ⚠ (magic-z + magic opacity) | Legend: ✓ clean · ⚠ minor drift · ✗ real overlap/violation. ## (e) Systemic theming finding (≥3 components → one structural fix) ### THEME-SYS-1 · Overlay z-index is an ad-hoc parallel scale; a canonical `--z-index-*` ladder already exists - recurs in (B1+B2): select 80 · dialog overlay 70 · popover content 75 / overlay 60 · combobox 80 · dropdown-menu 80 · **tooltip 76** · **drawer overlay 72 / inline 64** · **link-preview preview-z 99** · navigation-menu content-z (token, value hardcoded) · toast toaster-z 1200. **~10 overlay recipes.** - **lead-verified ground truth:** the canonical scale `STATIC_Z_INDEX` (`lib/primitives/static.ts:291`) EXISTS: `base 0 · raised 1 · sticky 100 · dropdown 300 · popover 400 · tooltip 500 · modal 700 · toast 900`, exposed as `--z-index-{key}` and consumed by the depth planes (`overlay → var(--z-index-popover)`, `modal → var(--z-index- modal)`) AND by **menu-dial** (`var(--z-index-sticky, 1100)`). So the overlay recipes hardcode an ad-hoc 60–99/ 1200 scale that (a) duplicates a scale that already exists and (b) doesn't even match its ordering — e.g. ad-hoc tooltip 76 < dropdown 80, but canonical tooltip 500 > dropdown 300. - precise fix mapping (one sweep): dropdown-menu/context-menu → `--z-index-dropdown`; select/combobox/popover/ link-preview content → `--z-index-popover`; tooltip → `--z-index-tooltip`; dialog/drawer overlay → `--z-index- modal` (content = +1 or a `--z-index-modal-content`); toast → `--z-index-toast`. menu-dial already does it right. - fix-status: open ### THEME-SYS-2 · Recipe opacity decimals where a numeric `--opacity-*` scale exists - recurs in: dropdown-menu `item-disabled-opacity 0.55`, context-menu `item-disabled-opacity 0.55`, onion-menu `muted-opacity 0.85`/`hover-ring-opacity 0.65`, **calendar `day-outside-opacity 0.62`** (B3). The `--opacity-*` scale is a 0.05 step (static.ts:255-275: `--opacity-60=0.6`, `--opacity-65=0.65`) — `0.62` maps to NO step, and the same off-scale `0.62`/`62%` recurs as overlay-opacity in dialog (base.ts:729) + drawer (:895): an unnamed brand value. Dialog uses `var(--opacity-disabled)` for disabled — the positive model. - one fix: map disabled opacities to `var(--opacity-disabled)`, snap decimals to the nearest `--opacity-*` step, and give the recurring `0.62` scrim value ONE named token (`--opacity-scrim`?) instead of three literals. - fix-status: open ### B3 theming note — calendar `8ch` select-min-width is LEGIT (not drift) - The verify correctly ruled `'select-min-width': '8ch'` (calendar recipe) a non-violation: `ch` is the right unit for a text-box min-width (tracks glyph width of the month/year label), there is no canonical `ch`-scale to reference, and it's already a named component token. Token-naming nit at most (LOW). Recorded so the next sweep doesn't re-flag it. ## Batch-8 closing — eidos primitives (44) are theming-clean The final batch swept all eidos-only primitives (layout · typography · visual · composite+service · infra) for E-bis. Result: **the eidos theming layer is in excellent shape.** - **(c) NON-CANONICAL ROLES — CLEAN across all 124 components (authoritative).** Lead grep + the B8 category sweep + verify agree: NO recipe / type union / `data-color` enum / token name anywhere uses `success`/`warning`/`danger`/ `info`/`error`/`brand`. The only occurrences are COMMENTS that correctly *document the mapping* (badge/types.ts:22: "success → affirm, warning → risk, error → threat"). The B8 agents' "TextColor/HeadingColor must use ColorRole" flags were all REFUTED: typography color uses the **content-color** slots (`content-primary/secondary/muted/disabled/ on-solid`), which are valid content roles, NOT the 9 hierarchy/intent roles. THEMING §16-C is fully satisfied. - **VARIANTS / SIZES / NAMING — CLEAN.** No invented `data-variant`, no invented size, no `--eidos-/--soma-/--air-` prefix, no redundant `color-` segment found in any primitive. - **RAW HEX — only the physically-fixed ones.** qr-code's `#fff`/`#000` (scanability) is the sole raw-hex, correctly ruled legitimate (refuted). - **The residual literals are minor (3 MEDIUM, 12 LOW), no systemic class:** - `code`/`kbd`/`mark` inline-pill padding in `em` (0.1–0.4em) — there is NO canonical em-padding scale, so this is borderline-acceptable LOW (a candidate for a future `--inline-pad-*` token if it ever recurs more). - `scroll-frames` layout + opacity literals (2 MEDIUM) — should reference `--space-*` / `--opacity-*`. - `radio-cards` one-off easing literal (MEDIUM) — should reference `--ease-*`. - `section` `calc(--space-16 * 1.5)` (LOW) — composes the canonical token for an off-scale xl=96px (no `--space-24`). - `container` `80rem` defensive fallback, `group` `1px` border-collapse — physically-fixed/defensive (LOW, accept). - **No new systemic theming finding from B8.** The two systemic theming issues remain THEME-SYS-1 (overlay z-ladder, ~10 overlay recipes — the only structural theming fix worth a sweep) and THEME-SYS-2 (magic opacity decimals). ## Notes for the full sweep - The biggest theming wins are E-bis structural (tokenize z-ladder), not per-recipe. Batch 8 (eidos-only primitives) will add the most distinct hardcoded-value data — re-run this roll-up after each batch and promote any value hardcoded in ≥3 components to a systemic entry here. - The px/rem font-size coherence guard has an `em`/`%` hole (select-008) — tracked in VALIDATOR_GAPS.