diff --git a/docs/guides/demo-authoring.md b/docs/guides/demo-authoring.md index ad497514c..7dad42dfb 100644 --- a/docs/guides/demo-authoring.md +++ b/docs/guides/demo-authoring.md @@ -131,7 +131,7 @@ mandatory (the snippet shows the active prop values; see §12). The v1 norm "every chip enumerates the role/variant/size union" was too narrow: it hid the palette. The component `color` prop accepts **the full surface** -([`theming/reference.md`](../theming/reference.md) §4 / §25.5): +([`theming/reference.md`](../theming/reference.md) §4 / §25): - **intent** (`neutral · affirm · fulfill · risk · threat · loss`) — evaluative. - **color** — under `intent='neutral'`: a **hierarchy** role diff --git a/docs/rfcs/rfc-color-engine.md b/docs/rfcs/rfc-color-engine.md index 0e972a468..a00124c50 100644 --- a/docs/rfcs/rfc-color-engine.md +++ b/docs/rfcs/rfc-color-engine.md @@ -2,7 +2,7 @@ title: RFC — Next-generation color engine (OKLCH · P3 · APCA · 1-seed generator) type: rfc audience: human + agent -status: implemented — phases landed through 4-bis; slot tuning (phase 5) gated +status: implemented — phases landed through 4-bis; phase 5's `border` 6→7 landed 2026-06-05 (reference §28) source: migrated from src/uix/eidos/COLOR_ENGINE_RFC.md (2026-07-02, docs-book F7.4) --- @@ -518,6 +518,13 @@ regression on the dark ones (purple/red/blue keep white). ## 9. Cleanup of the current model (included in the sweep) +> **All four rows are DONE** (verify in `themes/base.ts`: `primary: 'purple'`, +> `loss: 'plum'`, `tertiary: 'indigo'`; and `border: '7'` in +> `DEFAULT_COLOR_ROLE_SLOT_STEPS`). They landed separately rather than via the +> base→seeds migration this section assumed — see §11 phase 3. Kept as the +> record of what was wrong and why it was fixed; do not read it as a live +> defect list. + | Defect | Fix | Anchor | |---|---|---| | `primary` ≡ `loss` = purple in the base | Migrate the base to seeds; `loss` → its own seed (plum). Auto-derives if omitted. | `themes/base.ts:18,31` | @@ -597,7 +604,12 @@ bump if the authored `scales` shape changes; old 12-hex scales read the same. live (build-time via an endpoint or precomputed). - **Verify**: an arbitrary brand generates an AA scale without authoring hex. -### Phase 5 — slot→step tuning (P3-3), optional, gated behind a probe +### Phase 5 — slot→step tuning (P3-3) — ✅ LANDED (2026-06-05) +> The conservative move shipped: `border` = **step 7** +> (`DEFAULT_COLOR_ROLE_SLOT_STEPS`), standing doctrine in +> [`theming/reference.md §28`](../theming/reference.md). The slot set also grew +> past the original nine — `bg2`/`separator`/`textStrong` re-expose steps +> 2/6/12 (rationale in `config-types.ts`, `COLOR_ROLE_SLOTS`). - Only if the visual probe supports it. Conservative (`border` 6→7). ### Phase 6 — Docs @@ -661,7 +673,7 @@ good part **without** their regressions (see the runtime-vs-build analysis): |---|---|---| | "1 base variable instead of 12" | **1-seed generator** (§6), isomorphic: author one color, 12 come out — at build **or** JS runtime | …fidelity (tuned curve), introspectable contrast, alpha, universal support | | "fewer bytes" | Brand apps author N seeds (not N×12 hex) + **purge** + compact direct-OKLCH | …the `data-color` override (the scales stay available) | -| "semantic reduction to ~6 states" | **Already exists**: the 9 `--color-{role}-{slot}` slots (§25). Untouched. | — | +| "semantic reduction to ~6 states" | **Already exists**: the `--color-{role}-{slot}` slots — `COLOR_ROLE_SLOTS` (§25). Untouched. | — | | OKLCH / vanguard | **Authoring and source space** + wide-gamut output | …breaking the static computation | Agreement on the goal (OKLCH, less authoring); the right mechanism is diff --git a/docs/theming/reference.md b/docs/theming/reference.md index 39178f537..7a9fefea2 100644 --- a/docs/theming/reference.md +++ b/docs/theming/reference.md @@ -479,8 +479,9 @@ neutral — gray default threat — active negative consequence **Mapping to physical scales** (in the base theme): the standing assignment lives in the code — **single source**: `THEME_BASE_COLOR_ROLES` (`lib/themes/base.ts`), with each choice's rationale in its comments (e.g. -`tertiary: 'indigo'` is a RESERVED hierarchy slot no component consumes -yet; `loss: 'plum'` to avoid colliding with `primary: 'purple'`). This +`tertiary: 'indigo'` is the saturated third hierarchy accent, in a hue band +no intent occupies; `loss: 'plum'` to avoid colliding with +`primary: 'purple'`). This table was copied here twice and diverged both times (primary, risk) — hence it is now a pointer. @@ -508,18 +509,21 @@ Any system with fewer loses perceptual resolution. Any system with more falls into redundancy (success vs fulfill, danger vs threat — they are not the same). -### Per-component subset - -Each component exposes its own subset of the 9. Examples: +### Per-component subset — REVOKED (2026-07-18) -| Component | Subset | Excludes | -|---|---|---| -| Toggle | primary, secondary, neutral, affirm, risk, threat | fulfill, loss (doesn't apply) | -| Button | all 9 | — | -| Badge | primary, secondary, neutral, affirm, fulfill, risk, threat, loss | tertiary (not canonical for it) | +> **There are no per-component subsets.** `color` accepts the FULL system on +> **every** component — role / intent / 33 donor scales / raw CSS value +> (`ComponentColorProp`) — per the design decision in **§25, "Reversión de los +> subconjuntos"**. This section used to publish a subset table (Toggle +> excluding fulfill/loss, Badge excluding tertiary) and argue that exposing +> them "would be semantically wrong". That argument is precisely what §25 +> revoked, and the guard *"keeps every component `*Color` prop open"* +> (`recipe-css-contract.test.ts`) now breaks the build on any narrowing. -Why subsets: a toggle is neither completion nor irreversible loss. Exposing -fulfill/loss in its API would be semantically wrong. +What survives is the **arbitration**, not the narrowing: identity (`color`) +stays decoupled from evaluation, so an evaluative `intent` still wins over +`color` (the rule above). A component does not decide *which* colors exist for +it; it decides nothing — the system is open and the intent arbitrates. --- @@ -1767,7 +1771,7 @@ initiative registry [`next-features.md §1`](../next-features.md). Standing doctrine — which slot pairs the framework promises to clear, and which are intentionally subtle. The ratified pair table now lives as DATA (`$color` → `CONTRAST_PAIRS`, `arts/color/contrast-contract.ts`), a single source shared by -the audit and the Stage-2 solver. Measured by `scripts/contrast-audit.ts` (WCAG +the audit and the CI guard (Stage 2 shipped no solver — see the closing note). Measured by `scripts/contrast-audit.ts` (WCAG 2 gate + APCA Lc, over the 33 scales × 2 modes, plus a morph-generated regression bank), reusing the on-solid math (§8 of `rfc-color-engine.md`, `lib/on-solid.ts`). diff --git a/src/uix/eidos/components/avatar/types.ts b/src/uix/eidos/components/avatar/types.ts index b577c0884..c47fbadc4 100644 --- a/src/uix/eidos/components/avatar/types.ts +++ b/src/uix/eidos/components/avatar/types.ts @@ -39,7 +39,8 @@ export type AvatarProps = Omit, 'children'> & { /** Visual variant. @default 'soft' */ variant?: AvatarVariant; /** - * Accent palette. Canonical `ColorRole` OR any CSS color string. + * Accent palette. The full system — any canonical role / intent / donor + * scale (`ComponentColor`) OR any raw CSS color string. * @default 'neutral' */ color?: AvatarColor; @@ -54,7 +55,7 @@ export type AvatarProps = Omit, 'children'> & { ring?: AvatarRing; /** * Ring color override. Falls back to the avatar's `color` when - * unset. Accepts canonical `ColorRole` or any CSS color string. + * unset. Same open surface as `color` (`ComponentColorProp`). */ ringColor?: AvatarColor; /** Ring stroke thickness. @default 'md' */ diff --git a/src/uix/eidos/components/banner/types.ts b/src/uix/eidos/components/banner/types.ts index bdf452e54..6174216ba 100644 --- a/src/uix/eidos/components/banner/types.ts +++ b/src/uix/eidos/components/banner/types.ts @@ -5,9 +5,17 @@ import type { ChipVariant } from '$uix/eidos/lib/types'; import type { IconButtonProps } from '$uix/eidos/components/icon-button'; /** - * Banner intent vocabulary. Mirrors the canonical UIX `ColorRole` set - * (Intent + `primary` / `secondary` hierarchy roles). The recipe pulls - * the matching `--color-{intent}-{slot}` tokens for each value. + * Banner intent vocabulary. Currently the whole `ColorRole` set (the + * hierarchy roles + the canonical intents); the recipe pulls the matching + * `--color-{intent}-{slot}` tokens for each value. + * + * ⚠️ This conflates two axes the doctrine separates: `intent` is the + * EVALUATIVE axis (the 6 `INTENTS`), while `primary`/`secondary`/`tertiary` + * are non-evaluative hierarchy roles that belong to `color` — the paint axis + * (`reference.md` §4, `active-architecture.md` DOM table: `data-color` = + * per-token recipe, `data-intent` = persistent state). Banner also has no + * `color` prop, so it is the one painted component outside the open colour + * system. Resolving this is a pending decision, not an oversight. */ export type BannerIntent = ColorRole; diff --git a/src/uix/eidos/components/button/README.md b/src/uix/eidos/components/button/README.md index 7ac4db273..538fef259 100644 --- a/src/uix/eidos/components/button/README.md +++ b/src/uix/eidos/components/button/README.md @@ -68,7 +68,7 @@ font-size/font-weight/icon-size` por talla (`-xs`…`-xl`), `font-family`, `transition-*`, `disabled-opacity`, y las paletas de 7 slots por color (`--button-{color}-{track,element,border,solid,solid-hover,text,contrast}`) que la cascada `--button-palette-*` resuelve según `data-color` (piloto del -patrón; THEMING §25.5 admite escalas donantes per-instance). +patrón; THEMING §25 admite escalas donantes per-instance). ## Comparativa diff --git a/src/uix/eidos/components/button/types.ts b/src/uix/eidos/components/button/types.ts index 703e2278e..1ca8cff0f 100644 --- a/src/uix/eidos/components/button/types.ts +++ b/src/uix/eidos/components/button/types.ts @@ -76,7 +76,7 @@ export type ButtonProps = Omit & { /** * Color accent. Hierarchy (`primary`/`secondary`/`tertiary`/`neutral`), * an evaluative intent, OR any donor palette scale (`teal`, `plum`, …) — - * the Radix `color="grass"` per-instance override (THEMING §25.5). Per + * the Radix `color="grass"` per-instance override (THEMING §25). Per * the doctrine, an evaluative `intent` wins; under `intent='neutral'` * the `color` is free (hierarchy or palette). * diff --git a/src/uix/eidos/components/callout/README.md b/src/uix/eidos/components/callout/README.md index bdcf96be9..ea8ebca65 100644 --- a/src/uix/eidos/components/callout/README.md +++ b/src/uix/eidos/components/callout/README.md @@ -20,9 +20,9 @@ rule (parity floor = v1). - **Two axes** (doctrine §4): `intent` (neutral | affirm | risk | threat — the admonition type, drives tint + icon + title defaults) and `color` (paint override under `intent="neutral"` ONLY — evaluative intent wins). - Paint rides the C6 private-palette slots → the 8 roles, the 33 donor - scales AND raw CSS colors resolve through the THM-2 shared layer with - zero extra CSS. + Paint rides the C6 private-palette slots → the canonical color roles, the + 33 donor scales AND raw CSS colors resolve through the THM-2 shared layer + with zero extra CSS. ## Comparativa diff --git a/src/uix/eidos/components/callout/callout.svelte b/src/uix/eidos/components/callout/callout.svelte index a41f7be44..a90c469fc 100644 --- a/src/uix/eidos/components/callout/callout.svelte +++ b/src/uix/eidos/components/callout/callout.svelte @@ -57,7 +57,7 @@ // Paint doctrine (§4): an evaluative intent WINS — the `color` override // only applies under `intent="neutral"` (the IMPORTANT case). The - // intent values themselves belong to the canonical 8 color roles. + // intent values themselves are drawn from the canonical color roles. const effectiveColor = $derived(intent !== 'neutral' ? intent : (color ?? 'neutral')); const colorAttrs = $derived(resolveComponentColor(effectiveColor)); const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle)); diff --git a/src/uix/eidos/components/card/types.ts b/src/uix/eidos/components/card/types.ts index 4ca65d9f8..3c3d145e7 100644 --- a/src/uix/eidos/components/card/types.ts +++ b/src/uix/eidos/components/card/types.ts @@ -34,7 +34,8 @@ export type CardSize = Extract; * * Two valid shapes: * - * - **Canonical** `ColorRole`. Wins over consumer overrides for + * - **Canonical** name — a role, an intent or a donor scale + * (`ComponentColor`). Wins over consumer overrides for * evaluative intents (affirm / fulfill / risk / threat / loss) and * hierarchy promotion (primary / secondary). The recipe pulls the * matching `--color-{role}-{slot}` palette. @@ -68,8 +69,9 @@ export type CardProps = Omit & { /** Sizing scale (padding / gap / radius). @default 'md' */ size?: ResponsiveProp; /** - * Accent palette. Accepts a canonical `ColorRole` OR any CSS color - * string (hex / rgb / oklch / named). @default 'neutral' + * Accent palette. The full system — any canonical role / intent / donor + * scale (`ComponentColor`) OR any raw CSS color string + * (hex / rgb / oklch / named). @default 'neutral' */ color?: CardColor; /** Corner radius (magnitude). @default 'md' */ diff --git a/src/uix/eidos/components/mark/README.md b/src/uix/eidos/components/mark/README.md index 993fc50aa..474a3d22c 100644 --- a/src/uix/eidos/components/mark/README.md +++ b/src/uix/eidos/components/mark/README.md @@ -37,7 +37,7 @@ Adaptaciones para eidos: | Capacidad | UIX Mark | Radix Themes Mark | Chakra Mark | Mantine Mark | | --- | --- | --- | --- | --- | | Inline `` element | Sí | Sí | uses Highlight | Sí | -| Color palette | Intent (8 roles) | accent + intent | colorScheme | intent | +| Color palette | Intent (canonical color roles) | accent + intent | colorScheme | intent | | Default color | fulfill | yellow accent | yellow | yellow | | Inherits typography | Sí | Sí | Sí | Sí | | `as` polymorphism | Sí | Sí | Sí | Sí | diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 6975f3ead..fabdfd608 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -6,7 +6,7 @@ import type { } from '../config-types'; import { defineRecipes } from './define'; -// ── Per-instance palette cascade (THEMING §25.5) ────────────────────────── +// ── Per-instance palette cascade (THEMING §25) ──────────────────────────── // // `palette-{slot}` is RESERVED recipe vocabulary: the GENERATOR appends the // full per-scale `color:{scale}` cascade to any token with that name diff --git a/src/uix/eidos/lib/render-css.ts b/src/uix/eidos/lib/render-css.ts index 8a7bd4c1d..801b7391d 100644 --- a/src/uix/eidos/lib/render-css.ts +++ b/src/uix/eidos/lib/render-css.ts @@ -1845,7 +1845,7 @@ interface NormalizedDeclaration { * Normalize a component's tokens: convert each entry to its list of * declarations, infer var()-driven dependencies, merge explicit deps. */ -// ── Universal per-instance palette cascade (THEMING §25.5) ──────────────── +// ── Universal per-instance palette cascade (THEMING §25) ────────────────── // // `palette-{slot}` is RESERVED recipe vocabulary: any recipe token named // `palette-track` / `-surface` / `-surface-hover` / `-element` / `-border` / @@ -2052,7 +2052,7 @@ function renderRecipeGradientFinish( return blocks } -// ── Shared per-instance palette layer (THEMING §25.5 · THM-2 2026-07-12) ── +// ── Shared per-instance palette layer (THEMING §25 · THM-2 2026-07-12) ──── // // The component-agnostic SOURCE of the per-instance palette. ONE cascade for // the whole catalog: `[data-color='{role|scale}'] { --palette-{slot}: … }`. diff --git a/src/uix/eidos/lib/themes/base.ts b/src/uix/eidos/lib/themes/base.ts index 407f9c5bd..3a7f7779c 100644 --- a/src/uix/eidos/lib/themes/base.ts +++ b/src/uix/eidos/lib/themes/base.ts @@ -26,9 +26,11 @@ export const THEME_BASE_COLOR_ROLES: ColorRoleMap = { // and the gray neutral. Indigo is purple's cool ~60° neighbor (Material 3's // tertiary-hue rule) and sits in a hue band no intent occupies, so it reads as // hierarchy, not a signal. (Was `gray` — collided with neutral.) - // RESERVED (2026-06-15 audit): defined + emitted (`--color-tertiary-*`) but no - // component consumes it yet — a deliberate third-hierarchy slot apps/themes can - // reach for. Not dead code; not to be removed. + // CONSUMABLE: `tertiary` is part of `HierarchyColorRole`, so it enters + // `ComponentColor` and every open `color` prop accepts it, and the shared + // palette layer emits its `[data-color='tertiary']` row along with the rest of + // `COLOR_ROLES`. (It was marked RESERVED by the 2026-06-15 audit; the note + // outlived the wiring that made it reachable.) tertiary: 'indigo', // `gray-9` (el sólido neutral) es un gris medio: ni blanco ni negro // contrastan por defecto. El slot `contrast` se fija al step 12 — casi-negro diff --git a/src/uix/eidos/lib/types.ts b/src/uix/eidos/lib/types.ts index 7b1ccc54c..00cdb5764 100644 --- a/src/uix/eidos/lib/types.ts +++ b/src/uix/eidos/lib/types.ts @@ -8,6 +8,7 @@ */ import type { Intent } from '$uix/intent'; +import type { ColorRole, HierarchyColorRole } from './config-types'; export type { Breakpoint, ResponsiveProp } from '$adom'; @@ -119,10 +120,18 @@ export const SHAPE_FAMILIES = ['rounded', 'continuous', 'cut', 'scoop'] as const export type ShapeFamily = (typeof SHAPE_FAMILIES)[number]; /** - * Shared Eidos color role vocabulary. `primary` and `secondary` are - * hierarchy roles; the remaining values come from the UIX intent canon. + * Shared Eidos color role vocabulary — re-exported from `config-types`, where + * both types derive from the `COLOR_ROLES` / `HIERARCHY_COLOR_ROLES` consts. + * + * They used to be re-spelled here by hand, and drifted: this `ColorRole` lost + * `tertiary` (8 members) while the canonical one kept 9 — two exported types + * with the SAME name, and the import path decided which you got. The runtime + * never diverged (`resolveComponentColor` reads `COLOR_ROLES`), so the drift + * was type-only: it rejected at compile time a value the engine resolves. + * Derive from the const; never re-spell a closed vocabulary by hand. */ -export type ColorRole = 'primary' | 'secondary' | Intent; +export type { ColorRole, HierarchyColorRole } from './config-types'; + export type AffirmativeColorRole = Extract; export type ProgressiveColorRole = Extract< ColorRole, @@ -133,17 +142,10 @@ export type EditableColorRole = Extract< 'primary' | 'secondary' | 'neutral' | 'affirm' | 'risk' | 'threat' >; -/** - * The hierarchy color roles — non-evaluative promotion levels. `tertiary` - * is the saturated third accent (was RESERVED; exposed for component - * `color` overrides per THEMING §4 / §25.5). - */ -export type HierarchyColorRole = 'primary' | 'secondary' | 'tertiary'; - /** * The full donor palette — every `--scale-{name}-*` scale the theme ships * (33 scales). These are NOT roles: they are the raw scales a - * component instance can borrow via `color="teal"` (THEMING §25.5, the + * component instance can borrow via `color="teal"` (THEMING §25, the * per-instance `color` override, e.g. `