You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/THEMING.md

1500 lines
67 KiB

feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
# Eidos Theming — Architecture Reference
> **Audiencia**: cualquier dev que abra el repo y necesite entender cómo
> se hace el theming en UIX. Cubre el modelo mental, los contratos,
> las herramientas y las trampas. Si después de leerlo todavía no sabes
> dónde poner un token nuevo, falló este doc — abre un issue.
**TL;DR**:
- **9 roles canónicos** de color (`primary`, `secondary`, `tertiary`,
`neutral`, `affirm`, `fulfill`, `risk`, `threat`, `loss`).
- **6 sizes canónicos** + `full` (`xxs..xxl`).
- **3 niveles de tokens** públicos: foundation (estable), per-component
recipe (overrideable), private (`--_*`, no contrato externo).
- **Token Scope Contract (TSC)** decide DÓNDE se emite cada token
(`:root` / `[data-{c}]` / `[data-{c}][data-color='X']` / etc.) y
valida transitividad al generar.
- **226 KB raw / 25 KB gzip** de CSS foundation por defecto. Usa
`npm run eidos:purge` para apps en producción → −46 a −55%.
- **Modelo de color**: paleta de 33 escalas (diseñable) → roles de
jerarquía (alias explícito) → intents (auto-derivados por convención
del libro, identidad = step 9). Ver §25.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- **Compatible con** persistencia versionada, themes CSS-only,
runtime overrides, dark/light, density (compact/comfortable/spacious),
scaling (zoom 90–110, eje aparte), reduced motion, multi-axis breakpoints.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## Tabla de contenidos
1. [Mental model](#1-mental-model)
1bis. [Theming vive en Eidos, no en Morfo (por diseño)](#1bis-theming-vive-en-eidos-no-en-morfo-por-diseño)
3 months ago
2. [Las capas del CSS de Eidos](#2-las-capas-del-css-de-eidos)
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
3. [Las 7 capas de tokens](#3-las-7-capas-de-tokens)
4. [Los 9 roles canónicos de color](#4-los-9-roles-canónicos-de-color)
5. [El canon de sizes](#5-el-canon-de-sizes)
6. [Convenciones de naming](#6-convenciones-de-naming)
7. [Token Scope Contract (TSC)](#7-token-scope-contract-tsc)
8. [Cómo añadir un componente nuevo](#8-cómo-añadir-un-componente-nuevo)
9. [Cómo definir un theme](#9-cómo-definir-un-theme)
10. [Cómo overridear tokens en runtime](#10-cómo-overridear-tokens-en-runtime)
11. [Bundle strategy + `eidos:purge`](#11-bundle-strategy--eidospurge)
12. [Herramientas de validación](#12-herramientas-de-validación)
13. [Integración con Sema (`event:*` scope)](#13-integración-con-sema-event-scope)
14. [Motion](#14-motion)
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
15. [Comparación con librerías de referencia](#15-comparación-con-librerías-de-referencia)
16. [Anti-patterns que NO debes cometer](#16-anti-patterns-que-no-debes-cometer)
17. [FAQ — decisiones polémicas](#17-faq--decisiones-polémicas)
18. [Cobertura universal de TSC](#18-cobertura-universal-de-tsc)
feat(eidos): variants canon — EIDOS_VARIANTS + per-component lint Variants son canon del eidos, NO del theme. Decisión arquitectónica firmemente sostenida: el vocabulario de variants (solid/outline/ghost/ soft/surface/line/pills) está fijo a nivel del framework — paralelo a las 8 sema families del libro. Theme = retintar lo perceptualmente fijo; cambia QUÉ color es `affirm`, no QUÉ significa `outline`. Cambios: - lib/types.ts: nueva constante `EIDOS_VARIANTS` con los 5 archetypes canónicos (control / selection / chip / marker / tabs). Los 5 union types se derivan via `[number]` indexed access — valor y tipo no pueden desincronizarse. Nueva `EIDOS_VARIANT_VALUES` Set flat con todos los valores canónicos + utilidades cross-component (`plain`, `subtle`). - recipe-css-contract.test.ts: nuevo test "variant CSS selectors per component match the declared type union". Por cada componente: extrae el union type de `components/{c}/types.ts` (soporta literal unions + archetype aliases; cae a advisory mode en Extract<> y conditional types); compara con `[data-{c}][data-variant='X']` selectores en `{c}.css`; reporta typos y unauthorized extensions bidireccionalmente. - THEMING.md §19: nueva sección "Variants son canon del eidos, NO del theme" con argumentación (portabilidad, type safety, archetypes perceptuales paralelos a sema families), tabla de las 3 capas de la cebolla, referencia a `EIDOS_VARIANTS`, comparación con Radix Themes 3.x / Mantine 7 / Chakra v3 / Ark / shadcn. TOC actualizado. - eidos/README.md: tabla de referencia ampliada con §19. - CLAUDE.md: hand-off "2026-05-27 #6 (variants canon)". - CONTINUE.md: nota de la decisión arquitectónica. Variants component-specific permitidos (Banner inline/overlay/ persistent, Spinner bars/dots/ring, Button 'plain'): viven en cada `components/{c}/types.ts` y el lint los valida contra la CSS del componente. Tests: 101/101 pass en `src/uix/eidos`. `npm run check`: los mismos 6 errores pre-existentes (lib/_demo, soma/components/internal, web/ routes/active) — no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
19. [Variants son canon del eidos, NO del theme](#19-variants-son-canon-del-eidos-no-del-theme)
20. [Correcciones del engine de theming (2026-06-01)](#20-correcciones-del-engine-de-theming-2026-06-01)
21. [Modelo de color de dos niveles (RFC — RESUELTO en §25)](#21-modelo-de-color-de-dos-niveles-rfc--resuelto-en-25)
22. [Mejoras pendientes del theming](#22-mejoras-pendientes-del-theming)
23. [Eje de `scaling` (zoom global)](#23-eje-de-scaling-zoom-global--2026-06-02)
24. [Correcciones P2 del engine (2026-06-02)](#24-correcciones-p2-del-engine-2026-06-02)
25. [Modelo de color — paleta + roles/intents derivados](#25-modelo-de-color--paleta--rolesintents-derivados-2026-06-02)
feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed) RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime. Packages the demo-only builder into a first-class, tested API. - build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine (deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha) into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map. seed -> { variables, roles }. No DOM. 6 tests. - ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor scales + background from the active theme, writes a managed `uix-eidos-scheme` style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode change (follows light/dark). Returns BuildSchemeResult for introspection. opts: variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) + per-role overrides + selector. 4 tests (return value, intents, DOM block ordering + clear, mode re-derivation). - index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types. - temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated emitRole/rgbaStr; identical output verified in-browser). - generated/base.css: regenerated for the loss->plum role fix (binding layer --primitive-loss-* now points at --scale-plum-*; keeps the contract test green). - docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row. Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral chrome downstream; the 31-scale palette stays put. Math in $color, composition in eidos/lib (pure), DOM application in ActiveEidos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
26. [Theme builder en runtime — `eidos.applyColorScheme`](#26-theme-builder-en-runtime--eidosapplycolorscheme-2026-06-04)
27. [Salida wide-gamut OKLCH (default-on)](#27-salida-wide-gamut-oklch-default-on-2026-06-04)
feat(eidos): forced-colors focus a11y (P3-2) + role border ramp 6->7 (P3-3) Closes the two color-quality items from THEMING_AUDIT P3. P3-2 · forced-colors (Windows High Contrast): under @media (forced-colors: active) the browser auto-maps borders/text/backgrounds to system colors BUT drops box-shadow — so the box-shadow focus ring (--focus-ring) vanishes and keyboard focus disappears. The foundation now always emits a system-colored outline fallback: @media (forced-colors: active) { :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; } } Components that already focus via outline (e.g. Button) keep theirs by specificity; this is the fallback for the box-shadow ones. renderForcedColorsBlock in render-css.ts. P3-3 · role border ramp: the per-role `border` slot moved step 6 -> 7. In Radix's functional scale 6 is a subtle separator and 7 is the UI element border; step 6 read washed-out on real element borders (outline/surface/controls). element/hover/active (3/4/5) stay — Radix-canonical for component bg. DEFAULT_COLOR_ROLE_SLOT_STEPS. - generated/base.css regenerated (forced-colors block + --color-{role}-border -> step 7). - Verified in browser: --color-primary-border now resolves to primitive-7 (oklch 0.80 0.092 vs the softer step-6 0.86 0.072); checkbox borders render defined, not broken. - test: renderStaticCss emits the forced-colors outline block. - docs: THEMING §28 + audit P3-1/2/3 marked resolved + README ref row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
28. [Accesibilidad forced-colors + ramp de bordes](#28-accesibilidad-forced-colors--ramp-de-bordes-2026-06-05)
29. [Profundidad (depth) — canal unificado + eventful](#29-profundidad-depth--canal-unificado--eventful-2026-06-05)
30. [Forma (shape) — continuidad + familias + anidado](#30-forma-shape--continuidad--familias--anidado--eventful-2026-06-05)
31. [Estructura (espacio · densidad · escala)](#31-estructura-espacio--densidad--escala--el-espacio-como-ritmo-2026-06-05)
32. [Focus ring — modelo de dos anillos parametrizado](#32-focus-ring--modelo-de-dos-anillos-parametrizado-2026-06-11)
refactor(eidos): share number-field + css-field visual via spin-field NumberField and CssField are the same visual (a bordered field + input + increment/decrement triggers + scrubber, split/stacked layouts, sizes/ variants/colors, themeable glyphs); only their value model differs. They were two cloned recipes + CSS that drifted — a refinement to one (square flush buttons, divider, contrast) didn't reach the other. Unify into ONE shared source (the toggle-group structural-identity pattern): - New eidos/components/spin-field: recipe key `spin-field` (`--spin-field-*`) + spin-field.css with all the stepper-field rules, selecting `[data-spin- field*]`. Loaded via the foundation @import in index.css. - number-field + css-field morfos declare structural identity (`data-spin- field*` presence attrs on each part). The Provider emits them via syncAttrs; the sub-parts emit them in their soma `props` getter (number-field's soma hardcodes sub-part attrs rather than syncing the morfo). - Removed the `number-field` / `css-field` recipe keys; their CSS files are now stubs. A theme tints one component by scoping `[data-number-field] { --spin-field-… }`. - css-field thereby adopts number-field's refined steppers (square, flush, divider) — the drift fix the user asked for, now structural (no clone). Verified bit-for-bit in browser: number-field identical to baseline (split flush, stacked symmetric xs..xl, RTL, glyph token/children override); css-field now square/flush/divider. eidos-lint invalid 0; recipe contract passes (no orphans, loads-once); check + morfo:check clean for these. Docs: THEMING sections 33 (glyph tokens now `--spin-field-*`) + 34 (the shared layer); number-field / css-field READMEs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
33. [Glifos de stepper themeables (`spin-field`)](#33-glifos-de-stepper-themeables-spin-field--2026-06-11)
34. [`spin-field` — visual compartido del stepper-field](#34-spin-field--visual-compartido-del-stepper-field-number-field--css-field--2026-06-11)
35. [Canon de escalas — auditoría de theming (2026-06-15)](#35-canon-de-escalas--auditoría-de-theming-2026-06-15)
36. [Gap canónico trigger→panel — offset token-driven (2026-06-22)](#36-gap-canónico-triggerpanel--offset-token-driven-2026-06-22)
37. [Touch-target — 44px en táctil, gated por puntero (2026-06-28)](#37-touch-target--44px-en-táctil-gated-por-puntero-2026-06-28)
38. [Capa de estado (state-layer) — feedback neutro unificado (2026-06-28)](#38-capa-de-estado-state-layer--feedback-neutro-unificado-2026-06-28)
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 1. Mental model
Eidos es **la capa visual** de UIX. NO posee comportamiento ni estado.
Lee del DOM lo que las capas anteriores escribieron y aplica estilos.
```
Morfo declara la genética (qué attrs / events / partes existen)
↓
Soma transcribe behavior (data-state, data-color, aria-*, focus, …)
↓
Sema emite señales (data-event-* durante el hold perceptual)
↓
Eidos aplica visual (tokens, themes, recipes, archetypes, motion)
```
**Lo que Eidos posee**:
- El namespace `--*` de custom properties.
3 months ago
- Las capas de CSS del entrypoint (§2: foundation generado, archetypes,
events, recipes) + los bloques de theme que inyecta `ActiveEidos`.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- El runtime `ActiveEidos` que inyecta foundation + theme CSS.
- Tooling: generación, validación, purge, lint.
**Lo que Eidos NO posee**:
- Estado lógico de componentes (eso es soma).
- Definición de qué events existen (eso es morfo).
- Disparar señales perceptivas (eso es sema).
**La regla del 2-de-3**: una extensión al sistema (atributo, token,
convención) sólo se justifica si **al menos dos de las tres capas**
(soma, sema, eidos) la consumen. Las extensiones que entraron por voto
de eidos: `archetype`, `events[].semantic.{family,intent}`,
`events[].prewrite[]`, `data-starting-style` / `data-ending-style`.
---
## 1.bis Theming vive en Eidos, no en Morfo (por diseño)
> **Esta es la pregunta arquitectónica más frecuente — y la respuesta más
> importante para no romper el sistema.**
La intuición razonable de un dev nuevo es: *"si morfo es la fuente de
verdad cross-layer, los tokens visuales deberían vivir en morfo también"*.
**NO.** El diseño explícito de UIX dice lo contrario. Esta sección existe
para cerrar el caso con citas, antes de que la confusión arrastre a un
PR que viole la arquitectura.
### Las dos citas canónicas del repo
**`src/uix/active_architecture.md` §9 (Lo que NO es esta arquitectura)**:
> **No es un design system clásico.** Tokens, themes y recipes pertenecen
> a Eidos, no al núcleo.
**`src/uix/README.md` §2 (Eidos)**:
> Capa visual: **tokens, themes, recipes CSS por componente**, archetype
> rules, event reactions y wrappers Svelte sobre los providers headless de
> soma…
>
> Eidos lee del DOM lo que las otras capas escriben — **nunca importa
> internals de soma ni de sema**.
Estas dos frases, por sí solas, cierran cualquier debate sobre dónde
vive el theming. Si una propuesta futura las contradice, la propuesta
debe rechazarse o el doc canónico debe actualizarse antes — no después.
### La regla 2-de-3 lo deriva mecánicamente
**`active_architecture.md` §7 #12** y **README.md §5** dicen lo mismo:
> Una extensión a morfo solo se justifica si **al menos dos de las tres
> capas** (soma, sema, eidos) la consumen.
Aplicado al theming:
| ¿Quién consume los tokens visuales? | |
|---|---|
| Soma (behavior runtime) | ❌ no |
| Sema (perceptual signals) | ❌ no |
| Eidos (visual layer) | ✅ sí |
| **Cuenta** | **1-de-3** |
**1-de-3 ≠ 2-de-3 → tokens NO van en morfo, por regla**. La integración
visual cae automáticamente en eidos por la disciplina del 2-de-3, sin
que nadie tenga que decidirlo per-caso.
### ¿Cuál es entonces la relación entre morfo y theming?
Morfo es source-of-truth del **contrato cross-layer**:
- Parts (qué partes existen)
- Events (qué eventos puede disparar)
- Attrs y sus valores enumerados (qué attrs aparecen en DOM con qué values)
- Archetypes (clasificación transversal)
- States declarativos
**El theming se integra con morfo en UN SENTIDO PRECISO**: las recipes de
eidos targetean DOM attrs que morfo declara. Sin morfo, los attrs no
existirían en el DOM y los selectores eidos estarían muertos.
```
MORFO declara data-color.values = ['primary', 'affirm', 'threat', ...]
↓
SOMA emite <button data-color="affirm"> (al DOM)
↓
EIDOS recipe targetea scope: 'color:affirm' → [data-toggle][data-color='affirm']
↓
BROWSER cascade resuelve la regla CSS
```
El canal entre morfo y eidos es **el DOM**, no objetos TypeScript. Esto
es crítico y está blindado por la regla #6 de las reglas duras:
**`active_architecture.md` §7 #6**:
> **Eidos consume DOM y data-*, no internals de Soma ni Sema.** Si lo
> necesita, debe estar declarado en morfo o emitido en una señal de sema.
**README.md §5** lo repite literalmente.
### Lo que NO debe hacer eidos jamás
```ts
// ❌ VIOLACIÓN ARQUITECTÓNICA — Eidos importando morfo en runtime
import { toggleMorfo } from '$uix/morfo/components/toggle';
const validValues = toggleMorfo.parts
.find((p) => p.kebab === 'provider')!
.data.find((d) => d.attr === 'data-color')!.values;
// usar validValues para validar TSC scope:'color:X'
```
Aunque la intención sea buena (validar que `scope: 'color:affirm'` matchee
un value morfo-declarado), **este import viola la regla #6** y rompe el
boundary morfo↔eidos. Si quieres esa validación, la defensa correcta es
eidos-lint al nivel del DOM/CSS, no acoplamiento TS.
### La defensa correcta: eidos-lint al nivel del DOM/CSS
La validación de que las recipes eidos targetean valores que morfo declara
SE HACE, pero al nivel DOM/CSS:
```bash
node scripts/eidos-lint.ts toggle
```
Clasifica cada selector `[data-*]` como:
- **morfo-backed** — declarado en morfo, soma lo emite con value válido
- **eidos-only** — attr añadido por wrapper (data-variant, data-size)
- **invalid** — referencia un attr morfo-backed con value fuera del enum → bug
Esto cierra el loop arquitectónicamente sin requerir imports cross-layer.
### Recapitulación de la división canónica
| Concepto | Source-of-truth | Justificación |
|---|---|---|
| Parts (qué partes existen) | **Morfo** | Cross-layer: soma emite, eidos selecciona, sema referencia |
| Events + semantic | **Morfo** | Cross-layer: soma trigger, sema dispatch, eidos reaction |
| Archetypes | **Morfo** | Cross-layer: soma emite, sema cascade, eidos selectores |
| Attr values (`data-color.values`) | **Morfo** | Cross-layer: soma valida, eidos targetea, sema referencia |
| States declarativos | **Morfo** | Cross-layer: soma emite data-state, eidos selecciona |
| **9 roles canónicos sistémicos** | **Eidos** (`lib/themes/base.ts`) | Solo eidos los materializa |
| **6 sizes canónicos** | **Eidos** (`lib/config-types.ts`) | Solo eidos los coordina |
| **Variants visuales** (solid/outline/ghost) | **Eidos wrapper** | Solo eidos las renderiza |
| **Tokens** (`--toggle-solid-on-bg`) | **Eidos** (`lib/recipes/base.ts`) | Solo eidos los consume |
| **Themes** (light/dark/custom) | **Eidos** (`lib/themes/`) | Solo eidos los compone |
| **Persistencia del theme** | **Eidos** (`toDocument()`) | Solo eidos lo serializa |
| **TSC scope axes** (color, state, variant, size, event) | **Eidos** (refieren a attrs morfo-emitted) | Hardcoded en TSC porque son axes del DOM contract |
### La frase canónica
> **Morfo declara el contrato. Eidos declara el theming. El DOM los conecta.**
Esto NO es un compromiso. ES el diseño. La pureza de morfo (TS
declarativo, sin runtime, sin imports de capas visuales) DEPENDE de que
el theming viva fuera.
### Qué propuestas futuras DEBEN rechazarse citando esta sección
1. **"Vamos a poner los tokens visuales del Toggle en su morfo así
morfo es source of truth de todo"** — viola §9 y la regla 2-de-3.
2. **"Vamos a hacer que TSC valide `color:affirm` importando
`toggleMorfo.data['data-color'].values`"** — viola regla #6 (eidos
no importa internals de morfo en TS).
3. **"Vamos a meter `variant: 'solid' | 'outline'` en el morfo del
Toggle"** — viola 2-de-3 (variants solo las consume eidos).
4. **"Vamos a definir `size` en el morfo con sus 6 valores
canónicos"** — viola 2-de-3 (los 6 sizes son sistema visual, solo
eidos los materializa con tokens coordinados).
Si la propuesta tiene mérito, lo correcto es **actualizar el doc
canónico (`active_architecture.md` §9) ANTES** de aplicar el cambio.
No al revés.
### Lo que SÍ debería entrar en morfo respecto al theming
- Un componente que expone `color` como prop → debe declarar
`data-color.values: ['primary', 'affirm', ...]` en su morfo. Esos
values son cross-layer (eidos targetea, soma emite, sema podría
referenciar).
- Un componente que expone `state` (open/closed) → declara
`data-state.values: ['open', 'closed']`. Igual.
- Un componente que añade attrs visualmente puros (`data-variant`,
`data-size`) que NADIE más necesita → **NO van en morfo**, los añade
el wrapper eidos directamente.
### Consistencia con los docs canónicos
Esta sección **no introduce doctrina nueva**. Recoge y consolida lo que
ya estaba disperso en:
- [`src/uix/active_architecture.md`](../active_architecture.md) §3 (Morfo
= único punto de articulación cross-layer), §7 #6 (Eidos no importa
internals), §7 #12 (regla 2-de-3), §9 (tokens pertenecen a Eidos).
- [`src/uix/README.md`](../README.md) §2 (Eidos = capa visual con tokens),
§4 (no es design system clásico), §5 (Eidos consume DOM y data-\*).
- Esta misma `THEMING.md` §1 (Mental model) y §7 (TSC).
Si alguno de esos docs canónicos contradice esta sección, **el doc
canónico gana**. Esta sección consolida; no decide.
---
3 months ago
## 2. Las capas del CSS de Eidos
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
3 months ago
`src/uix/eidos/index.css` es el entrypoint (la fuente de verdad del orden es
el propio archivo). Importa, en orden:
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
```
3 months ago
1. generated/base.css ← foundation + recipe tokens + @font-face (generado)
2. archetypes.css ← reglas transversales por data-archetype
3. events.css ← reacciones a data-event-* (sema)
4. components/{c}/{c}.css ← recipes agregados: layout primitives + spin-field
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
```
3 months ago
Fuera del entrypoint pero parte de la capa visual:
- **Recipes code-split**: la mayoría de componentes NO están en `index.css`
— cada `.svelte` importa su propio CSS y Vite emite un chunk por
componente. La AUSENCIA de un `@import` es deliberada; re-añadirlo
duplicaría la carga.
- **Partials compartidos** (`lib/menu-indicator.css`): los importa el
componente que los usa, no el entrypoint.
- **Themes**: bloques CSS inyectados en runtime por `ActiveEidos`
(`uix-eidos-theme`), no un `@import` estático. `themes/fonts.css` quedó
superseded — los `@font-face` viven en `EidosConfig` y salen en
`generated/base.css`.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Por qué este orden importa
- `generated/base.css` declara tokens (no estiliza). Si los recipes se
cargan antes, no tienen los tokens disponibles.
- `archetypes.css` setea baseline interactiva (cursor, hover, focus
ring). Recipes específicos sobrescriben.
- `events.css` reacciona a `data-event-*` con `animation: @keyframes`
(no `transition`) porque las señales son transitorias y la animación
debe completar independiente del lifetime de la señal.
- Recipes específicos vienen al final → mayor cascade priority en
conflictos de igual specificity.
### Qué hace cada layer concretamente
| Layer | Tipo | Cuántas reglas | Propósito |
|---|---|---|---|
| `themes/fonts.css` | `@font-face` | 4-12 | Cargar fuentes |
| `generated/base.css` | `:root` + algunos `[data-{c}]` blocks | 4400+ declaraciones | Foundation tokens (scale, primitive, color, size, density, typography, recipe tokens) |
| `archetypes.css` | `[data-archetype='X']` selectors | 11 archetypes | Estilo baseline transversal (trigger, overlay, content, indicator, thumb, track, close, action, item, option, focus) |
| `events.css` | `[data-event-*]` selectors + @keyframes | 9 keyframes + 15 rules | Reactions perceptivas (announce pulse, dismiss fade, commit settle, etc.) |
| `lib/menu-indicator.css` | Partial | 1 selector | Indicator alignment compartido entre menus |
| `components/{c}/{c}.css` | `[data-{c}-*]` selectors | varía | Recipe específico del componente |
### Reglas para tocar cada layer
- **`fonts.css`**: añade `@font-face`. No declara tokens, no estiliza.
- **`generated/base.css`**: **NUNCA editar a mano**. Es output del
generador. Para cambiarlo, edita `lib/recipes/base.ts` o
`lib/themes/base.ts` y corre `npm run generate:eidos-css`.
- **`archetypes.css`**: añade entradas sólo si el archetype está
declarado en algún morfo. Reglas de specificity baja (un atributo).
- **`events.css`**: añade reactions sólo para señales que sema emite.
Usa `animation: @keyframes`, NO `transition`. Lee `data-event-intent`
(signal-bound), NUNCA `data-intent` (state-bound).
- **`components/{c}/{c}.css`**: el dueño es la persona que mantiene
el componente. Sigue la convención de naming (sección 6).
---
## 3. Las 7 capas de tokens
Eidos compone el color final de un elemento atravesando 7 niveles de
indirección. Cada nivel sirve un propósito distinto:
```
┌─ Capa 1: --scale-{name}-{step} :root (estable)
│ Escalas físicas Radix (12 steps + alpha): --scale-teal-9 = #12a594
│
├─ Capa 2: --primitive-{role}-{step} :root (estable)
│ Mapeo role → scale: --primitive-affirm-9 = var(--scale-teal-9)
│
├─ Capa 3: --color-{role}-{slot} :root (estable)
│ Slot semántico: --color-affirm-solid = var(--primitive-affirm-9)
│
├─ Capa 4: --{component}-{role}-{slot} :root (estable)
│ Per-component alias: --button-affirm-solid = var(--color-affirm-solid)
│ (NOTA: drop del segmento "color-" en 2026-05-27)
│
├─ Capa 5: --{component}-palette-{slot} [data-{c}] (DINÁMICA)
│ Palette dinámica por instancia: cambia con data-color
│
├─ Capa 6: --{component}-{variant}-{slot} [data-{c}] (host) (DINÁMICA)
│ Combinación de variante × palette
│
└─ Capa 7: --_{component}-{slot} [data-{c}] (privado)
Token privado consumido por el recipe CSS directamente
```
**Reglas de scope**:
- Capas 1-4 son constantes → `:root`.
- Capa 5 cambia por instancia → `[data-{c}]` y `[data-{c}][data-color='X']`.
- Capa 6 depende de la 5 → DEBE estar en `[data-{c}]` (TSC lo enfuerza).
- Capa 7 es privada → siempre en `[data-{c}]`.
### Por qué tantas capas
**No es accidental**. Cada salto sirve un punto de extensión:
| Capa | Override permite | Ejemplo de uso |
|---|---|---|
| 1 | Cambiar la escala física Radix | Brand quiere su propio teal |
| 2 | Cambiar qué escala mapea un role | "Affirm" usa green en vez de teal |
| 3 | Cambiar slot mapping per role | "Solid" del affirm usa step 10 en vez de 9 |
| 4 | Cambiar token component-specific | Toggle quiere su affirm distinto del global |
| 5 | El runtime per-instancia | `<Toggle color="affirm" />` cambia el palette |
| 6 | Combinar variant × color | Solid variant del toggle con affirm color |
| 7 | Recipe-internal | El recipe decide qué token interno usa para qué |
En la práctica, **la mayoría de las apps SÓLO tocan las capas 1-3**
(brand customization). Las capas 4-7 son del catálogo de componentes.
### Cuándo crear un token nuevo en cada capa
- **Capa 1** (scale): una app rara vez; un **tema de marca** SÍ trae o
amplía su propia paleta (§25.7). Las **33 escalas** por defecto cubren
el caso general.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- **Capa 2** (primitive): rara vez. Sólo si añades un role canónico
nuevo (lo cual cambiaría el book canon — no lo hagas).
3 months ago
- **Capa 3** (color): si añades un nuevo `{slot}` (raro). El inventario
canónico de slots y su step por defecto viven en el código — **única
fuente**: `COLOR_ROLE_SLOTS` (`lib/config-types.ts`, con el porqué de
cada slot en su JSDoc) + `DEFAULT_COLOR_ROLE_SLOT_STEPS`
(`lib/render-css.ts`). No se copia la lista aquí: ya divergió dos veces
(border-hover retirado; bg2/separator/text-strong añadidos).
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- **Capa 4** (component-color): añadiendo color support a un componente
nuevo. Se genera automáticamente por `lib/recipes/base.ts`.
- **Capa 5** (palette): cuando el componente acepta `data-color` prop
y necesita un palette dinámico. TSC `scope: 'host'` + overrides
`scope: 'color:X'`.
- **Capa 6** (variant): cuando una variant (`solid`, `outline`, etc.)
combina palette + algo específico. TSC `scope: 'host'`.
- **Capa 7** (private): el recipe lo consume. Convención: prefijo `_`.
---
## 4. Los 9 roles canónicos de color
Los roles vienen del libro *Diseñando lo que ocurre*. SON CANON. NO
inventes nuevos.
```
HIERARCHY (no evaluative) INTENT (evaluative)
───────────────────────────── ───────────────────────────
primary — brand main affirm — turning ON something positive
secondary — brand support fulfill — completion / success
tertiary — brand tertiary risk — moderate negative consequence
neutral — gray default threat — active negative consequence
loss — irreversible negative outcome
```
**Reglas estrictas**:
- Los 9 nombres son los únicos válidos. NO uses `success`, `warning`,
`danger`, `info` — esos pertenecen a otros modelos (Bootstrap, etc.).
- **`primary`/`secondary`/`tertiary`** son **jerárquicos**: usar cuando
la diferencia es "más vs menos prominente". Sin carga evaluativa.
- **`neutral`** es el default. Sin carga semántica.
- **`affirm`/`fulfill`/`risk`/`threat`/`loss`** son **evaluativos**:
comunican qué pasa con la acción.
- Si `intent === 'neutral'`, `color` (hierarchy override) puede aplicar.
Si `intent` es evaluativo, el `intent` GANA y `color` se ignora.
3 months ago
**Mapping a escalas físicas** (en theme base): la asignación vigente vive
en el código — **única fuente**: `THEME_BASE_COLOR_ROLES`
(`lib/themes/base.ts`), con el porqué de cada elección en sus comentarios
(p. ej. `tertiary: 'indigo'` es un slot RESERVED de jerarquía que ningún
componente consume aún; `loss: 'plum'` para no colisionar con
`primary: 'purple'`). Esta tabla se copió aquí dos veces y divergió las dos
(primary, risk) — por eso ahora es un puntero.
> **Convención ≠ autoría.** `CANONICAL_INTENT_SCALES`
> (`lib/config-types.ts`) es la **convención** del libro para auto-derivar
> intents de una paleta (identidad = step 9; p. ej. `risk→amber`). El
> **theme base** es autoría y puede desviarse (p. ej. `risk: 'orange'`).
> La jerarquía (`primary`/`secondary`/`tertiary`) la elige siempre el
> tema. Modelo completo en **§25**.
La **paleta** son **33 escalas** de 12 steps + 12 alpha = 24 tokens c/u
(**792 tokens `--scale-*`** — el grueso del bloat del foundation). Sobre
ella, los 9 roles aliasan vía `--primitive-{role}-{step}` (9 × 24 =
**216 primitives**).
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Por qué 9 roles y no 4 (como shadcn) o 14 (como Mantine)
Los 9 son el resultado del análisis perceptivo del libro:
- 3 hierarchy roles cubren la dimensión "prominencia visual".
- 1 neutral cubre el default sin carga.
- 5 intent roles cubren las cinco valencias evaluativas distintas.
Cualquier sistema con menos pierde resolución perceptiva. Cualquier
sistema con más cae en redundancia (success vs fulfill, danger vs
threat — no son lo mismo).
### Subset por componente
Cada componente expone su propio subset de los 9. Ejemplos:
| Componente | Subset | Excluye |
|---|---|---|
| Toggle | primary, secondary, neutral, affirm, risk, threat | fulfill, loss (no aplica) |
| Button | los 9 | — |
| Badge | primary, secondary, neutral, affirm, fulfill, risk, threat, loss | tertiary (no canónico) |
Por qué subsets: un toggle no es completion ni irreversible loss.
Exponer fulfill/loss en su API sería semánticamente incorrecto.
---
## 5. El canon de sizes
```
xxs · xs · sm · md · lg · xl · xxl | full
───────────────────────────────────── ───────
6 sizes canónicos (físicos) 1 size de layout
```
`md` es el default. `full` no es físico — es semántica de layout
(`100%` / `100vw` / `100dvh` según contexto). No genera tokens fijos.
Cada size canónico genera tokens coordinados:
```
--size-md-control-height: 36px
feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions Fable audit follow-through (fable_audit.md + fable-eidos-audit.md): - RECIPE_CONTRACT.md (E2 canon): the transversal systems every recipe must consume, enforced by component-audit R-4.1-4.6 (all at error; escape valves /* literal */ + /* functional */; WIP tracks excluded). Stale audit rules fixed against the current architecture (E-2.2 wrapper imports, D-1.2 v2 9-tab union, D-3.1 single snippet, TabsVariant mirror) - verdicts went 0/117/15 -> 75/50/5. - Motion migration 15/15: recipes off local @keyframes onto the channel - preset stamps (dropdown/context/select/combobox/tooltip/link-preview/ clipboard), new expand/collapse + value-flash signatures, shared-axis reverse pair, delayed-open open-alias in the preset trigger (PRESET_STATE_ALIASES), materials pattern for irreducible triggers (card/timeline/tabs/nav-menu/metrics). Duration/scale hooks keep every recipe's tuned values. - buildScheme (fase C): full a1..a12 alpha ramp per role (was a2/a3 - stale alphas after applyColorScheme), intentSeeds so temper starts from the ACTIVE theme's intent mapping (risk stays orange), alpha background self-derived from the scheme's own neutral step 1 (was hardcoded #fff/#111); mode now forces the donor variant. - Inventory decisions (fase D): semanticTracking axis removed (all-zero), border-hover slot dropped (0 consumers), separator slot adopted across line dividers (step 6, Radix divider tone), size-bundle consumption pilot on toggle (canonical coordinates consumed, deliberate deviations kept visible). - Docs: building-a-component.md (the one door, 9-phase route + known traps), PLAN-docs-reconciliation.md (fase 6 kickoff for a fresh session), THEMING wiring updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
--size-md-font-size: 16px /* = var(--font-size-md), 1:1. Bundle EN ADOPCIÓN (decisión Fase D 2026-07-02; pilot: toggle) */
--size-md-font-line-height: 1.45
--size-md-icon-size: 18px /* = --icon-size-md */
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
--size-md-padding-inline: 12px
--size-md-padding-block: 8px
--size-md-gap: 8px
--size-md-radius: 6px
```
> **`md` es uno solo** (1:1 — override 2026-06-17, ver abajo): el bundle de control
> `--size-md-font-size` = `var(--font-size-md)` = 16px, idéntico a la escala
> tipográfica. La doctrina previa de "dos `md`" (control 14px compacto vs body 16px)
> quedó **revocada** — el texto de control sigue la escala tipográfica 1:1.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Regla clave: `md` NO cambia por viewport
Lo responsive decide **qué size activo se usa**, NO redefine los
tokens. Si tu Toggle en mobile usa `sm` y en desktop `md`, ambos
tokens están disponibles y el wrapper elige cuál.
```svelte
<!-- Correcto -->
<Toggle size={{ base: 'sm', md: 'md' }} />
<!-- Incorrecto -->
@media (max-width: 768px) {
:root { --toggle-height-md: 32px; } /* NO redefinas el token del size por viewport */
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
```
### Subset por componente
Como con color, cada componente expone su subset de sizes que su
recipe soporta. Categorías:
| Categoría | Subset | Ejemplos |
|---|---|---|
| Form controls + text inputs | `xs..xl` | input, select, switch, slider, checkbox |
| Nav controls | `xs..lg` | breadcrumb, pagination, tag-group, toolbar |
| Composed panels | `sm..lg` | calendar, date-picker, file-upload, stepper, tooltip |
La categorización vive en
[`web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md §12.8`](../../../web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md).
### Derivación contenedor→parte: cap en `md` (norma 2026-06-19)
Cuando una parte deriva su `size` de su **contenedor** (p. ej. `Dialog.Close`
hereda el size del dialog), la parte **sigue el size del contenedor solo en los
pasos por debajo de `md`**; en `md` y por encima (`lg` / `xl` / `full`) **cap a
la densidad normal `md`**. Un contenedor más ancho — o `full` — **NO engorda sus
controles**: `full` es layout (llena el viewport), no un tamaño de control mayor.
| size del contenedor | size de la parte |
|---|---|
| `xs` / `sm` | `xs` / `sm` (sigue el paso) |
| `md` / `lg` / `xl` / `full` | `md` (normal) |
Coincide con las referencias: Radix separa `size` (densidad) de `width` (full);
Mantine `fullScreen` **ignora** `size`; Material 3 full-screen es un **tipo de
layout** (top app bar) con controles estándar. Ninguno agranda los controles por
ser el dialog `full`.
Primer consumidor: `Dialog.Close` — `components/dialog/context.ts` publica el size
del dialog y el Close lo deriva. Una prop `size` explícita en la parte siempre gana.
### Mapeo size→fuente: 1:1 universal (override 2026-06-17)
> **Supersede los arquetipos `control`/`compact`/`dense` de la Fase 7.** El usuario
> decidió que un framework de grado empresarial necesita **una sola fuente de verdad**:
> el texto de control sigue la escala tipográfica **1:1** — `font-size-{size}` =
> `var(--font-size-{size})` — en TODOS los componentes. Así, cambiar `--font-size-md`
> a 15px re-adapta el tema entero sin tocar un solo recipe. El `md`-control es **16px**
> (ya NO 14: la doctrina "control compacto md=14" queda revocada).
**Escala** (xs/sm/md/lg/xl/xxl): **12 · 14 · 16 · 18→20 · 24→28 · 32→48**
(`lib/primitives/typography.ts`; lg/xl/xxl fluidas `clamp()`).
Recipes llevados a 1:1 (2026-06-17): button, badge, breadcrumb, calendar, pagination,
radio-group, toolbar, file-upload, tag-group, stepper, toggle, tooltip — más toda la
familia de campos (field, spin/date/time/color-field, search/password-field, select,
editable, tags-input), ya 1:1 desde el sprint de campos.
**Excepciones legítimas** (NO son texto-de-control → no aplican 1:1):
- **avatar/marker** — la fuente es la inicial dentro del círculo, escalada al **diámetro**
(24→96px): glifo proporcional, no control.
- **accordion** — el trigger es un encabezado de sección: usa la escala de **prosa**
`--text-N-size` (xs→text-2 … full→text-6), progresión propia coherente.
- **words / palabras / chronos** — tracks WIP excluidos.
feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions Fable audit follow-through (fable_audit.md + fable-eidos-audit.md): - RECIPE_CONTRACT.md (E2 canon): the transversal systems every recipe must consume, enforced by component-audit R-4.1-4.6 (all at error; escape valves /* literal */ + /* functional */; WIP tracks excluded). Stale audit rules fixed against the current architecture (E-2.2 wrapper imports, D-1.2 v2 9-tab union, D-3.1 single snippet, TabsVariant mirror) - verdicts went 0/117/15 -> 75/50/5. - Motion migration 15/15: recipes off local @keyframes onto the channel - preset stamps (dropdown/context/select/combobox/tooltip/link-preview/ clipboard), new expand/collapse + value-flash signatures, shared-axis reverse pair, delayed-open open-alias in the preset trigger (PRESET_STATE_ALIASES), materials pattern for irreducible triggers (card/timeline/tabs/nav-menu/metrics). Duration/scale hooks keep every recipe's tuned values. - buildScheme (fase C): full a1..a12 alpha ramp per role (was a2/a3 - stale alphas after applyColorScheme), intentSeeds so temper starts from the ACTIVE theme's intent mapping (risk stays orange), alpha background self-derived from the scheme's own neutral step 1 (was hardcoded #fff/#111); mode now forces the donor variant. - Inventory decisions (fase D): semanticTracking axis removed (all-zero), border-hover slot dropped (0 consumers), separator slot adopted across line dividers (step 6, Radix divider tone), size-bundle consumption pilot on toggle (canonical coordinates consumed, deliberate deviations kept visible). - Docs: building-a-component.md (the one door, 9-phase route + known traps), PLAN-docs-reconciliation.md (fase 6 kickoff for a fresh session), THEMING wiring updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
El bundle `--size-*` (arriba) está **en adopción** (decisión Fase D, 2026-07-02 —
antes llevaba un año huérfano). Patrón establecido por el pilot (`toggle`,
`lib/recipes/base.ts`): la recipe consume `--size-{k}-*` donde su valor ES la
coordenada canónica (height, font-size — cadenas de alias idénticas, cero cambio
visual) y conserva su propio valor donde **desvía deliberadamente** (px/gap más
prietos que el padding del bundle) — la desviación queda visible en vez de
enterrada en una re-declaración paralela. El barrido al resto del catálogo es el
workstream abierto; ya estaba **realineado al 1:1** (2026-06-29:
`--size-md-font-size` = `var(--font-size-md)` = 16px).
**Regla dura (guard de coherencia)** — `recipe-css-contract.test.ts`:
> Ningún token `font-size-*` / `icon-size-*` de recipe puede ser un **literal px/rem**
> — DEBE referenciar `--font-size-*` / `--icon-size-*` (si no, el texto deja de seguir
> la escala tipográfica, `--scaling` y `applyTypeScale()`).
Cierra el hueco que el guard anterior (solo-CSS) dejaba — los valores de recipe-token
se vuelven custom properties, no la propiedad `font-size:`. (`words`/`palabras`/`chronos`
excluidos — track activo.) El refactor a *consumir* el bundle del arquetipo (en vez de
re-declarar el mapeo) queda como follow-up; el guard es lo que **impide la deriva**.
### Campos: input 1:1 + label un paso por debajo (override 2026-06-17)
La familia de **campos** aplica el **1:1 universal** (arriba) al **input**, y añade una
regla propia para el **label**:
- **Input/control → 1:1** (como todo control): **12 · 14 · 16 · 18→20 · 24→28**.
- **Label → un paso tipográfico por debajo del input**: **10 · 12 · 14 · 16 · 18**.
El label es el **único** elemento del sistema que baja un escalón a propósito —
jerarquía label↔input, no la deriva colapsada que se rechazó.
| Recipe | input | label |
|---|---|---|
| `field` (genérico) | `control-font-size-*` 1:1 | `label-font-size-*` un paso abajo → cubre todo lo que envuelve `<Field>` |
| `spin-field` (number/css), `date/time/color-field` | `font-size-*` 1:1 | date/time/color: label propio vía CSS calc; spin: label del Field |
| `search-field`, `password-field`, `select`, `editable`, `tags-input` | `font-size-*` 1:1 | label del Field |
**Label de campos segmentados** (solo date/time/color tienen `[data-X-field-label]`):
`font-size: calc(1em - (var(--font-size-md) - var(--font-size-sm)))` — el input (`1em`
heredado) menos un paso de escala (md−sm = 2px, tokenizado). El `Field` genérico usa
tokens `label-font-size-*` por-size.
**No tocados** (ya coherentes): `pin-input` (`cell-font-size-*` ya escala), `combobox`
(sin fuente propia). **Excluidos**: `words`, `chronos`. Los no-campos (button/calendar/
badge…) conservan su arquetipo.
**Pendiente**: en `lg` el label segmentado (calc → 18) y el del `Field` (token → 16)
divergen 2px porque la `lg` del input es **fluida** (18→20). En xs/sm/md (fijos)
coinciden. Resolver dando tokens por-size a los segmentados, o quitando la `lg` fluida
del input de campo.
### Escala tipográfica ↔ escala de iconos
Son dos escalas paralelas — `--font-size-{name}` (`lib/primitives/typography.ts`)
y `--icon-size-{name}` (`lib/primitives/static.ts` → `STATIC_ICON.size`) — ambas
escaladas por densidad (`× --scaling`). **No son independientes: el icono
acompaña al texto.**
La regla óptica se validó **por ojo, no por fórmula** (banco de pruebas
[`/uix/icon-scale-study`](../../../web/routes/uix/icon-scale-study/+page.svelte)):
el icono pesa *un punto por encima del texto* — `≈ font + 2` en los tamaños de
cuerpo, creciendo hacia `≈ line-height` en los grandes (un icono **atado a
`font-size` abajo, a la `line-height` arriba**). No es una constante:
| size | `--font-size` (px) | line-height (px) | `--icon-size` (px) | icon − font |
|---|---|---|---|---|
| xxs | 10 | 15 | 12 | +2 |
| xs | 12 | 18 | 14 | +2 |
| sm | 14 | 20 | 16 | +2 |
| md | 16 | 23 | 18 | +2 |
| lg | 20 | 27 | 20 | 0 |
| xl | 24→28 | 34 | 32 | +4 |
| xxl | 32→48 | 50 | 52 | +4 |
(Los headings `lg`–`xxl` son fluidos `clamp()`; la columna `font-size` muestra el
max desktop. El salto de iconos `lg 20 → xl 32` es fiel al salto del texto
`20 → 28` — no hay tamaño intermedio porque la tipografía tampoco lo tiene.)
Por eso un componente **nunca inventa tamaños de icono en px**: declara
`var(--icon-size-{name})` y hereda esta correlación. El `size` canónico ya
empareja ambos ejes (`--size-md-font-size` + `--size-md-icon-size`). Matices por
arquetipo:
- **Iconos de control siguen el font 1:1** (override 2026-06-17): el `icon-size-{size}`
de cada recipe de control referencia `var(--icon-size-{size})`, paralelo al font 1:1.
La escala de iconos preserva `icon ≈ font+2` en cuerpo (md: font 16 → icono 18).
Aplicado a **button** + **search-field**. **Excepciones**: `password-field` —
su `icon-size-*` NO es un glifo sino la **caja del botón visibility-trigger** (el glifo
es el 65%), control-coupled a propósito; `radio-cards` — el icono sigue el font del
**título** de la card.
- **Densidad ⊥ tipografía** (compact/comfortable/spacious): la densidad escala SOLO
layout — `space` (`× --density-space-scale`) + `control-height` (`× --density-control-scale`).
**`--font-size-*` y `--icon-size-*` NO llevan densidad** — solo el zoom global
`--scaling` (Radix-parity) los toca. Consecuencia: a 1:1 el icono queda **acoplado al
texto en toda densidad** (font 16 / icono 18 constantes; solo la caja del control se
aprieta: 32.4 / 36 / 40.3). Por eso un icono dimensionado desde `--control-height-*`
(density-coupled) se desacopla del texto — es un anti-patrón salvo que el elemento sea
un control (p. ej. el trigger del password-field).
- **Cards / títulos** (radio-cards, empty-states): el icono **acompaña el font del
título**, nunca un tamaño inflado a mano — p. ej. radio-cards = `16/16/18/18/20`
(sigue su título). Para destacar más se sube el **font del título** (el icono lo
sigue), no se infla el icono.
Cambiar la escala = editar `STATIC_ICON` + `components/icon/create-icon.ts` +
regenerar (`npm run generate:eidos-css`). Nada más la consume en crudo.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 6. Convenciones de naming
### Tokens públicos (consumibles)
```
--{prefix}-{slot}
```
Donde `{prefix}` es uno de:
| Prefix | Significado | Ejemplo |
|---|---|---|
| `--scale-{name}-{step}` | Escala física Radix | `--scale-teal-9` |
| `--primitive-{role}-{step}` | Role → step | `--primitive-affirm-9` |
| `--color-{role}-{slot}` | Color role × slot | `--color-affirm-solid` |
| `--font-{kind}-{key}` | Tipografía | `--font-family-primary` |
| `--size-{key}-{slot}` | Size primitive | `--size-md-control-height` |
| `--space-{n}` | Spacing scale | `--space-3` |
| `--radius-{key}` | Radius scale | `--radius-md` |
| `--border-width-{key}` | Border-width scale (lineal `none·thin·medium·thick·heavy` = 0/1/2/3/4) — §35 | `--border-width-thick` |
| `--ring-inset-width` | Default ancho del inset-ring (anillo interior `box-shadow`) — §35 | `--ring-inset-width` |
| `--shadow-{n}` | Shadow scale (gota) | `--shadow-3` |
| `--shadow-inset-{key}` | Inner-shadow / recessed (`subtle·deep`, mode-aware) — §35 | `--shadow-inset-subtle` |
| `--blur-{key}` | Blur scale (backdrop/frost, `none·sm·md·lg·xl·xxl`) — §35 | `--blur-lg` |
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
| `--depth-{plane}-translucency` | Frost opacity por plano = **función de la elevación** (más alto = más opaco) — §29 | `--depth-modal-translucency` |
| `--gradient-{name}` | Gradiente nombrado themeable — token o **derivado de rol** vía `buildGradient`/`applyGradients` (6º builder) — §29 | `--gradient-aurora` |
| `--gradient-angle-{dir}` | Dirección de gradiente (8 brújulas) — §35 | `--gradient-angle-to-r` |
| `--breakpoint-{key}` | Breakpoint responsive (fuente = ActiveDom) — §35 | `--breakpoint-md` |
fix(eidos): THEME-SYS-1 — consolidate overlay z-index into a named scale (+ guard + docs) The ~12 overlay recipes hardcoded an ad-hoc parallel z-index scale (raw integers 60–99/1200) that duplicated nothing reusable and had drifted out of order (tooltip 76 < dropdown 80 — a tooltip painted BEHIND a dropdown). They are now a named scale. - `STATIC_Z_INDEX_OVERLAY` (static.ts) → emitted as `--z-index-overlay-{inline, backdrop,content,floating,tooltip,detached,toast}`. A SEPARATE scale from the global `--z-index-*` ladder (which orders the depth planes) — overlays portal to <body> as siblings of modals, so they share one flat low band where each rung sits just above the modal scrim. Mapping them to the 300–900 ladder would hide a dropdown/select/popover opened INSIDE a dialog (dropdown 300 < modal 700); the combobox recipe already warned about this. `tooltip` now sits above `floating` (fixes the inversion); `toast` stays above the soma FloatPanel band. - Every overlay recipe token (`content-z`/`overlay-z`/`inline-z`/`toaster-z`/ `preview-z`) now references `var(--z-index-overlay-*)` — zero raw integers. dialog/drawer gain an explicit `content-z` rung (drops the `calc(... + 1)`). - Guard (contracts.test.ts, "overlay z-index against raw integers"): a recipe `*-z` token must reference the scale, never a bare integer. Proven to catch drift (a raw `'76'` makes it fail). Local `z-index: 0..5` (avatar/tabs/sticky) is intra-component relative stacking — out of scope, stays. - Docs: THEMING.md §35 rewritten to describe the consolidated scale + the flat-band rationale + the guard; token table gains `--z-index-overlay-*`; testing-and-tooling.md documents the catalogue guards (VG-8/SYS-1/A31/A30/ THEME-SYS-1). Stacking order verified from the resolved CSS (deterministic z compare: content 70 < floating 80 < tooltip 90 < toast 1200; dropdown-in-dialog preserved). A live browser check was blocked by a port conflict with another session's server. check: 0 new type errors; the 5 guards green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
| `--z-index-{key}` | Z-index layer (depth planes) | `--z-index-modal` |
| `--z-index-overlay-{key}` | Flat overlay micro-band (portaled overlays + modals) — §35 | `--z-index-overlay-floating` |
| `--opacity-{key}` | Opacidad — escala dual numérica (`0..100`) + semántica (`disabled·muted·…`) — §35 | `--opacity-disabled` |
| `--tracking-{key}` | Letter-spacing scale (incl. `caps` para MAYÚSCULAS) — §35 | `--tracking-caps` |
| `--leading-{key}` | Line-height scale | `--leading-ui` |
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
| `--duration-{key}` | Motion duration | `--duration-fast` |
| `--ease-{key}` | Motion ease | `--ease-out` |
| `--style-{name}-*` | Typography named style | `--style-h1-font-size` |
| `--{c}-{slot}` | Component recipe token | `--toggle-height-md` |
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
| `--{c}-{role}-{slot}` | Component color | `--toggle-affirm-solid` |
| `--{c}-palette-{slot}` | Component palette runtime | `--toggle-palette-solid` |
### Tokens privados (componente-internal)
```
--_{c}-{slot}
```
El prefijo `_` significa: NO consumes esto desde fuera del recipe del
componente. Es interno. Ejemplo:
```css
[data-toggle] {
--_toggle-bg: var(--toggle-solid-bg); /* privado */
--_toggle-on-bg: var(--toggle-palette-solid); /* privado */
}
```
### Reglas estrictas
1. **Todos los public tokens del Eidos llevan el prefijo `--`** sin
sub-prefijo de capa. Razón: clarity en debug. Ver `--toggle-bg` y
sabes que es Eidos. Ver `--bg` y no sabes de dónde viene.
2. **NUNCA usar `--eidos-`** como prefijo. La capa ya está implícita
en el path `$uix/eidos/components/{c}`.
3. **NUNCA usar `--soma-` ni `--air-` ni `--terra-`**. Esas capas son
muertas o no poseen tokens.
4. **Los component tokens siguen el patrón** `--{component-kebab}-...`.
El componente kebab es el nombre del directorio.
5. **No abreviar nombres de componente**. `dropdown-menu` no se vuelve
`ddmenu`. La authorship clarity vale 6 chars.
6. **NO incluir el segmento "color-"** intermedio en tokens de color.
`--toggle-affirm-solid` (correcto), `--toggle-color-affirm-solid`
(deprecado 2026-05-27).
3 months ago
7. **Slots siguen vocabulario fijo**: para las capas 3-5 el inventario
canónico es `COLOR_ROLE_SLOTS` (`lib/config-types.ts` — ver §3, capa 3;
no se copia aquí). Para las capas 6-7 (recipe-level):
`bg, fg, border, on-bg, on-fg, on-border, hover-bg, on-hover-bg`.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Tokens generados vs autoría
Tokens en `generated/base.css` son **output**. Para añadir uno nuevo,
editas:
- `lib/themes/base.ts` para primitives, scales, theme variants.
- `lib/recipes/base.ts` para tokens de componente.
Y corres `npm run generate:eidos-css`.
---
## 7. Token Scope Contract (TSC)
> **Movido a [`TSC.md`](./TSC.md)** (canon visual, E2). El Token Scope Contract
> —scopes disponibles, las tres formas de declarar un token, el álgebra de
> `scopeCovers`, la detección de colisión cross-axis, multi-part scope y
> cross-recipe composition (v2.2), y el pipeline de 5 defensas— vive ahí como su
> propio capítulo. Resumen: el TSC decide DÓNDE se emite cada token (raíz, por
> componente, por color, por evento) y valida al generar que toda dependencia
> esté disponible en el scope del consumidor.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 8. Cómo añadir un componente nuevo
> **Movido a [`THEMING_GUIDE.md`](./THEMING_GUIDE.md)** (guía E4). Los pasos para
> añadir un componente nuevo —decidir qué tokens necesita, recipe, scope y
> validación— viven ahí junto a la guía de definir un theme.
feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions Fable audit follow-through (fable_audit.md + fable-eidos-audit.md): - RECIPE_CONTRACT.md (E2 canon): the transversal systems every recipe must consume, enforced by component-audit R-4.1-4.6 (all at error; escape valves /* literal */ + /* functional */; WIP tracks excluded). Stale audit rules fixed against the current architecture (E-2.2 wrapper imports, D-1.2 v2 9-tab union, D-3.1 single snippet, TabsVariant mirror) - verdicts went 0/117/15 -> 75/50/5. - Motion migration 15/15: recipes off local @keyframes onto the channel - preset stamps (dropdown/context/select/combobox/tooltip/link-preview/ clipboard), new expand/collapse + value-flash signatures, shared-axis reverse pair, delayed-open open-alias in the preset trigger (PRESET_STATE_ALIASES), materials pattern for irreducible triggers (card/timeline/tabs/nav-menu/metrics). Duration/scale hooks keep every recipe's tuned values. - buildScheme (fase C): full a1..a12 alpha ramp per role (was a2/a3 - stale alphas after applyColorScheme), intentSeeds so temper starts from the ACTIVE theme's intent mapping (risk stays orange), alpha background self-derived from the scheme's own neutral step 1 (was hardcoded #fff/#111); mode now forces the donor variant. - Inventory decisions (fase D): semanticTracking axis removed (all-zero), border-hover slot dropped (0 consumers), separator slot adopted across line dividers (step 6, Radix divider tone), size-bundle consumption pilot on toggle (canonical coordinates consumed, deliberate deviations kept visible). - Docs: building-a-component.md (the one door, 9-phase route + known traps), PLAN-docs-reconciliation.md (fase 6 kickoff for a fresh session), THEMING wiring updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
>
> **Qué sistemas transversales DEBE consumir la recipe** (state-layer, focus,
> elevación tokenizada, tipografía 1:1, opacidad, motion, ejes lógicos) es su
> propio canon: [`RECIPE_CONTRACT.md`](./RECIPE_CONTRACT.md), vigilado por las
> reglas R-4.x de `component-audit`.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 9. Cómo definir un theme
> **Movido a [`THEMING_GUIDE.md`](./THEMING_GUIDE.md)** (guía E4).
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 10. Cómo overridear tokens en runtime
`ActiveEidos.setCssVariables()` permite override runtime contract-aware:
```ts
activeEidos.setCssVariables({
'--color-primary-solid': 'rebeccapurple',
'size-md-control-height': '40px', // sin -- también vale
'shadow-3': '0 10px 28px rgb(20 20 20 / 0.16)'
});
```
Eidos:
1. **Valida** cada nombre contra `getCssContract()`. Tokens fuera del
contrato lanzan error en modo `strict` (default).
2. **Renderiza** transacionalmente: primero render + valida, luego
reemplaza el `<style>` block runtime.
3. **Aplica** los overrides bajo `:root` (o el selector que pases).
Para variables fuera del contrato (locales de la app):
```ts
activeEidos.setCssVariables(
{ '--my-app-custom': 'value' },
{ strict: false }
);
```
### Builders de sistema completo
Por encima de `setCssVariables` hay dos builders que derivan un sistema entero desde una
semilla y lo escriben como bloque gestionado (siguen el tema activo light/dark):
- **`eidos.applyColorScheme(seed, opts)`** — deriva las 33 escalas + 9 roles desde un color
de marca (`buildScheme`). `clearColorScheme()` revierte.
- **`eidos.applyTypeScale(seed, opts)`** — deriva los 8 `--font-size-*` desde un ratio
modular + base (`buildTypeScale`), opcionalmente fluido (`ratioMax`). `clearTypeScale()`
revierte.
Ambos son puros en `eidos/lib` (`build-scheme` / `build-type-scale`) + un método de
aplicación en `ActiveEidos`. Demos en vivo: `/temas/color` y `/temas/tipografia`.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 11. Bundle strategy + `eidos:purge`
`generated/base.css` contiene tokens de TODOS los componentes del
catálogo (~95 components). En producción una app típica usa 5-20.
### El tool
```bash
npm run eidos:purge -- \
--src 'src/**/*.svelte' \
--src 'src/**/*.ts' \
--src 'src/**/*.css' \
--output dist/eidos.purged.css \
--verbose
```
### Cómo decide qué mantener
1. **Foundation siempre kept**: scale, primitive, color, size, opacity,
z-index, shadow, border, radius, space, density, motion, icon,
typography, layout. ~1100 tokens (~95 KB raw / ~11 KB gzip).
2. **Source-scan tokens**: cada `var(--XXX)` y `--XXX:` declaración
encontrada en source → `XXX` pinned.
3. **Component-import detection**: cada `from '...components/{c}'` →
recipe completo de `{c}` pinned.
4. **Data-attr detection**: cada `data-{c}=` (filtrado contra registry
canonical de recipes) → recipe completo de `{c}` pinned.
5. **Cierre transitivo**: si X pinned y X→`var(--Y)`, Y pinned.
Iteración hasta fixed point.
### Resultados medidos
| Perfil | Components | Raw before | Raw after | Reducción | Gzip after |
|---|---|---|---|---|---|
| Minimal (toggle+button+badge) | 3 | 217.7 KB | 97.4 KB | **−55%** | 11.1 KB |
| SaaS típico (10 components) | 10 | 217.7 KB | 116.3 KB | **−46%** | 13.6 KB |
| 5 páginas demo UIX | 7 | 217.7 KB | 107.3 KB | −51% | 12.4 KB |
| Exhaustivo (todos) | 62 | 217.7 KB | ~217 KB | −0.4% | ~25 KB |
**Piso arquitectónico**: ~95 KB raw / ~11 KB gzip (foundation que
toda app necesita).
### Cuándo usarlo
- **En producción**: SIEMPRE. Integra en tu build pipeline.
- **En dev**: opcional. El raw 226 KB es aceptable para iteración local.
- **En SSR**: pre-purge una vez por build, no per-request.
### Limitaciones conocidas
- **Dynamic component selection**: si tu app importa componentes
dinámicamente (`await import(...)`), el scanner los puede perder.
Mitigación: pasa los nombres via `--keep my-component`.
- **`var()` en strings dinámicos**: si construyes `var(--${name})` en
runtime, el scanner no lo ve. Mitigación: declara los nombres
estáticamente en algún archivo escaneable.
---
## 12. Herramientas de validación
| Tool | Comando | Qué valida |
|---|---|---|
| **morfo:check** | `npm run morfo:check` | DOM contracts vs morfo declarations (Playwright walk de 107 demos) |
| **eidos-lint** | `node scripts/eidos-lint.ts {c}` | Recipe CSS selectors vs morfo enum values |
| **eidos-lint-all** | `node scripts/eidos-lint-all.ts` | Igual, todos los componentes |
| **TSC validation** | `npm run generate:eidos-css` (implícito) | Scope algebra + cross-axis collision detection |
| **recipe-css-contract** | `npm test -- recipe-css-contract` | Recipe tokens consumidos + TSC v2 scenarios (17 tests) |
| **component-api-contract** | `npm test -- component-api-contract` | Public API surface por componente |
| **component-visual-attrs** | `npm test -- component-visual-attrs` | Visual data-attrs que el wrapper emite |
| **generated-css** | `npm test -- generated-css` | Estructura del CSS generado |
### Pipeline de validación recomendado pre-commit
```bash
npm run generate:eidos-css # Si tocaste recipes/themes
npm test -- src/uix/eidos # 99/99 tests
npm run check # TS check
npm run morfo:check # DOM contracts (requiere dev server)
node scripts/eidos-lint-all.ts # CSS drift safety net
```
---
## 13. Integración con Sema (`event:*` scope)
> ⚠️ **Superseded.** El modelo de motion vigente es el de **dos momentos**
> documentado en [`eidos-motion.md`](./eidos-motion.md) (F1–F7): el momento
> `--event` (la firma perceptiva) se declara en `motion.signatures` y se
> genera como CSS contra `data-event-*` directamente — sin el scope TSC
> `event:*` ni el `data-motion-ref` que esta sección discutía. El motor
> (`EngineMotion`) es un servicio en `arts/motion` (`uix.motion`). El cuerpo
> original (contexto histórico de la decisión) vive en
> [`THEMING_CHANGELOG.md §13`](./THEMING_CHANGELOG.md).
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 14. Motion
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
El sistema de motion de Eidos —el **modelo de dos momentos** (`--event`
perceptivo durante el *hold* de una señal + `--state` para transiciones
persistentes), el motor `EngineMotion` (reubicado a `arts/motion`, expuesto como
`uix.motion` y consumido por soma y eidos) y cómo se autora un preset— es su
propio sistema y vive en [`eidos-motion.md`](./eidos-motion.md). Roadmap F1–F7
implementado (2026-06-04). Esta sección cubre sólo lo que toca al **theming**.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
> **Nota de estado.** Versiones previas de este doc describían motion como
> "deferred" y daban el `data-motion-ref` / el "TSC `event:*` scope" (§13) como
> su futuro. Eso quedó **obsoleto**: la firma perceptiva migró al registro de
> `signatures` (no a `events.css`) y el motor es hoy un servicio en `arts/motion`.
> `eidos-motion.md` es la referencia canónica y actual.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Superficies arrastrables: el lift de "pickup"
Cuando el usuario **agarra y arrastra** una superficie, ésta debe **subir hacia
él** (profundidad: "lo he cogido"). Es un cue transversal — NO específico de un
componente — así que el factor de escala es un **token global de motion**, no un
literal por recipe:
| Token | Valor | Uso |
|---|---|---|
| `--motion-scale-lift` | `1.02` | escala de pickup al arrastrar (el único `>1` de la familia `--motion-scale-*`) |
**Patrón (cualquier draggable lo aplica igual)** — gateado por el data-attr de
arrastre que su morfo declara (`data-dragging`, `data-grabbed`, …), emparejado
con una elevación de sombra, y suprimido bajo reduced-motion:
```css
/* float-panel, un thumb de slider arrastrándose, un item sortable, un drawer… */
[data-x][data-dragging] {
scale: var(--motion-scale-lift); /* sube hacia el usuario */
box-shadow: var(--…-shadow-active); /* + eleva la sombra */
}
@media (prefers-reduced-motion: reduce) {
[data-x][data-dragging] { scale: 1; transition: none; }
}
```
Reglas:
- **`scale` (no `transform`)** para componer con la posición, que viaja por
`translate` (propiedades distintas) → el lift no pelea con el arrastre 1:1.
- El cambio de `scale` se **transiciona** (lift al agarrar / settle al soltar);
durante el move queda **estático** (compuesto, sin coste por frame).
- `will-change: translate, scale` mientras dura el gesto.
- Un theme retematiza el lift en `STATIC_MOTION.scale.lift` (`primitives/static.ts`)
— todos los draggables lo heredan. No redefinir el 1.02 por componente.
Hoy lo consume `float-panel` (`[data-float-panel-content][data-dragging]`); un
slider/sortable que añada arrastre debe leer el MISMO token, no inventar el suyo.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 15. Comparación con librerías de referencia
> **Movido a [`THEMING_NOTES.md`](./THEMING_NOTES.md)** (E3). La comparación de
> eidos con Radix, Ark, Mantine y otras vive ahí, junto al FAQ de decisiones.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## 16. Anti-patterns que NO debes cometer
### A. Declarar tokens derivados en `:root`
```ts
// ❌ INCORRECTO — el bug del Toggle pre-TSC
'palette-solid': { value: '...', scope: 'host' },
'solid-on-bg': 'var(--my-component-palette-solid)' // scope 'root' implícito
```
TSC lanza error al regenerar. Fix: `scope: 'host'` en el consumer.
### B. Usar nombres con segmento "color-" redundante
```ts
// ❌ DEPRECATED (2026-05-27)
'color-affirm-solid': 'var(--color-affirm-solid)'
// ✅ CORRECTO
'affirm-solid': 'var(--color-affirm-solid)'
```
### C. Inventar roles fuera del canon
```ts
// ❌ NO — success/danger/warning/info son de otros modelos
'success': 'green',
'danger': 'red'
// ✅ Usa los 9 canónicos
'fulfill': 'green', // success → fulfill
'threat': 'red' // danger → threat
```
### D. Definir media-queries que cambien tokens canónicos
```css
/* ❌ NO — md cambia significado por viewport */
@media (max-width: 768px) {
:root { --size-md-control-height: 32px; }
}
/* ✅ Componente elige qué size aplica por viewport */
<Toggle size={{ base: 'sm', md: 'md' }} />
```
### E. Importar `$libs/dom` directamente en eidos
```ts
// ❌ NO
import { foo } from '$libs/dom';
// ✅ Eidos consume vía ActiveEidos.dom
const eidos = ActiveEidos.require();
eidos.dom.apply(...);
```
### F. Tocar `generated/base.css` a mano
Es output. Cualquier cambio se sobrescribe al regenerar. Si necesitas
cambiar algo, edita `lib/themes/base.ts` o `lib/recipes/base.ts`.
### G. Crear escalas físicas sueltas dentro de una app
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
Las **33 escalas** por defecto cubren las paletas razonables. Un **tema
de marca** SÍ trae su propia paleta como escalas (§25.7) — eso es
legítimo. Lo que NO debes hacer es añadir una escala one-off dentro de
una app cuando remapear un role a una escala existente ya resuelve el caso.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### H. Re-exportar entre layers
```ts
// ❌ NO — eidos no re-exporta soma
export * from '$soma/components/toggle';
// ✅ Cada layer expone su propio API
```
---
## 17. FAQ — decisiones polémicas
> **Movido a [`THEMING_NOTES.md`](./THEMING_NOTES.md)** (E3).
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
## Referencias
- [`src/uix/eidos/README.md`](./README.md) — el doc de arquitectura
vivo (esta info está duplicada parcialmente; este doc es la canónica).
- [`src/uix/eidos/THEMING_AUDIT_2026-06-01.md`](./THEMING_AUDIT_2026-06-01.md) —
la auditoría del theming (el journal de cómo llegamos aquí).
- [`src/uix/eidos/eidos-motion.md`](./eidos-motion.md) — el sistema de
motion (modelo de dos momentos, F1–F7; ver §14).
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- [`src/uix/eidos/lib/config-types.ts`](./lib/config-types.ts) — la
fuente de verdad del TSC type.
- [`src/uix/eidos/lib/render-css.ts`](./lib/render-css.ts) — el
generador (parsing, scope algebra, cross-axis detection).
- [`src/uix/eidos/lib/recipes/base.ts`](./lib/recipes/base.ts) — el
catálogo de tokens per-component.
- [`src/uix/eidos/lib/themes/base.ts`](./lib/themes/base.ts) — el
theme base (primitives + semantics + themes).
- [`scripts/eidos-purge.ts`](../../../scripts/eidos-purge.ts) — el
purge tool.
- [`src/uix/eidos/recipe-css-contract.test.ts`](./recipe-css-contract.test.ts) —
el test guard (17 tests).
- [`src/uix/active_architecture.md`](../active_architecture.md) —
el contexto UIX completo.
- [`src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md`](../../docs/GUIA_IMPLEMENTACION_SEMAUIX.md) —
la doctrina sema/perceptual (fuente del canon de 9 roles).
---
## 18. Cobertura universal de TSC
> **Movido a [`TSC.md`](./TSC.md)**. Las extensiones v2.2 (multi-part scope y
> cross-recipe composition) que llevan el TSC a cobertura universal viven con el
> resto del contrato en `TSC.md`.
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
---
feat(eidos): variants canon — EIDOS_VARIANTS + per-component lint Variants son canon del eidos, NO del theme. Decisión arquitectónica firmemente sostenida: el vocabulario de variants (solid/outline/ghost/ soft/surface/line/pills) está fijo a nivel del framework — paralelo a las 8 sema families del libro. Theme = retintar lo perceptualmente fijo; cambia QUÉ color es `affirm`, no QUÉ significa `outline`. Cambios: - lib/types.ts: nueva constante `EIDOS_VARIANTS` con los 5 archetypes canónicos (control / selection / chip / marker / tabs). Los 5 union types se derivan via `[number]` indexed access — valor y tipo no pueden desincronizarse. Nueva `EIDOS_VARIANT_VALUES` Set flat con todos los valores canónicos + utilidades cross-component (`plain`, `subtle`). - recipe-css-contract.test.ts: nuevo test "variant CSS selectors per component match the declared type union". Por cada componente: extrae el union type de `components/{c}/types.ts` (soporta literal unions + archetype aliases; cae a advisory mode en Extract<> y conditional types); compara con `[data-{c}][data-variant='X']` selectores en `{c}.css`; reporta typos y unauthorized extensions bidireccionalmente. - THEMING.md §19: nueva sección "Variants son canon del eidos, NO del theme" con argumentación (portabilidad, type safety, archetypes perceptuales paralelos a sema families), tabla de las 3 capas de la cebolla, referencia a `EIDOS_VARIANTS`, comparación con Radix Themes 3.x / Mantine 7 / Chakra v3 / Ark / shadcn. TOC actualizado. - eidos/README.md: tabla de referencia ampliada con §19. - CLAUDE.md: hand-off "2026-05-27 #6 (variants canon)". - CONTINUE.md: nota de la decisión arquitectónica. Variants component-specific permitidos (Banner inline/overlay/ persistent, Spinner bars/dots/ring, Button 'plain'): viven en cada `components/{c}/types.ts` y el lint los valida contra la CSS del componente. Tests: 101/101 pass en `src/uix/eidos`. `npm run check`: los mismos 6 errores pre-existentes (lib/_demo, soma/components/internal, web/ routes/active) — no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
## 19. Variants son canon del eidos, NO del theme
**Posición arquitectónica firmemente sostenida**: el vocabulario de
variants (`solid`, `outline`, `ghost`, `soft`, `surface`, `line`,
`pills`, etc.) está **fijo en el sistema**. Un theme NO puede:
- Añadir un variant nuevo (no hay un `branded`, `bubble`, `corporate`
que cada theme invente).
- Redefinir el cascade visual de un variant existente (`outline` significa
"bordered restraint" en todos los themes — solo cambia el COLOR del
border, no su geometría).
### Las 3 capas de la cebolla
| Capa | Qué es | Quién la cambia |
|---|---|---|
| **Sema families / intents** | vocabulario perceptual canon del libro (8 families × 6 intents) | NADIE — fijo |
| **Eidos variants** | archetypes visuales perceptuales (5 archetypes shared + variants component-specific) | NADIE — fijo |
| **Eidos color roles** | los 9 roles canon (`primary`/`affirm`/`risk`/…) | NADIE — fijo |
| **Eidos color palette** | qué hex es cada rol | **THEME** |
| **Tokens internos de recipe** | `--toggle-solid-bg` etc. | **App** (override puntual en `EidosConfig.recipes`) |
**Theming = retintar lo perceptualmente fijo**. El theme cambia QUÉ
color es `affirm`, no QUÉ significa `outline`.
### Por qué fijos
1. **Portabilidad de componentes.** `<Toggle variant="outline">` debe
renderizar coherentemente en cualquier theme. Theme-defined variants
romperían eso silenciosamente — un componente que asume `outline`
no funcionaría en un theme que no lo declara.
2. **Type safety = parte del contrato.** Los consumers necesitan
`SelectionVariant = 'solid' | 'outline' | 'ghost'` para autocomplete
y TS errors. Un `Record<string, …>` extensible perdería esa garantía.
Radix Themes 3.x, Chakra v3, Mantine — todas las referencias serias
mantienen variants fijos por componente.
3. **Variants son archetypes perceptuales, paralelos a sema families.**
`solid` = "filled emphasis", `outline` = "bordered restraint",
`ghost` = "ambient transparency", `soft` = "tinted background". Eso
es vocabulario perceptual del framework — no decisión de theme.
4. **Hay 5 archetypes, no infinitos.** El catálogo se cierra; el TS
los rechaza si no son canónicos. Si emerge un nuevo archetype
genuinamente perceptual, se añade a `EIDOS_VARIANTS` — pero al
nivel del framework, no al del theme.
### Single source of truth — `EIDOS_VARIANTS`
`src/uix/eidos/lib/types.ts` declara la constante:
```ts
export const EIDOS_VARIANTS = {
control: ['surface', 'outline', 'ghost'],
selection: ['solid', 'outline', 'ghost'],
chip: ['soft', 'solid', 'outline', 'ghost'],
marker: ['solid', 'soft', 'outline'],
tabs: ['line', 'surface', 'pills']
} as const satisfies Readonly<Record<string, readonly string[]>>;
export type ControlVariant = (typeof EIDOS_VARIANTS.control)[number];
export type SelectionVariant = (typeof EIDOS_VARIANTS.selection)[number];
// …
```
Los 5 unions se DERIVAN de la const — el valor y el tipo no pueden
desincronizarse. Cada componente narrow al archetype apropiado:
```ts
// components/toggle/types.ts
export type ToggleVariant = SelectionVariant;
// components/accordion/types.ts
export type AccordionVariant = ControlVariant;
```
Para narrowing más fino dentro de un archetype:
```ts
export type AlertDialogCancelVariant = Extract<ButtonVariant, ControlVariant>;
```
### Variants component-specific
Algunos componentes tienen vocabularios genuinamente únicos:
- `Banner`: `inline | overlay | persistent` (posicionamiento, no
treatment perceptual).
- `Spinner`: `bars | dots | ring` (geometría del indicador).
- `Button`: añade `'plain'` para inline/link-like — no merece archetype
propio porque solo aparece en Button + Code.
Estos viven en cada `components/{c}/types.ts`. El lint
`recipe-css-contract.test.ts > variant CSS selectors per component
match the declared type union` valida bidireccionalmente:
- CSS usa `[data-{c}][data-variant='X']` → X debe estar en el union.
- Type union declara `'X'` → CSS debería tener entries (advisory).
### Lo que un theme SÍ puede
- Cambiar paletas (`ThemeColorSet.scales`, `ThemeColorSet.roles`).
- Cambiar shadows (`ShadowScale`).
- Cambiar typography styles (`TypographyPrimitiveSet.styles`).
### Lo que un theme NO puede
- Añadir variants. (`ThemeDefinition` no expone `recipes`.)
- Redefinir cascades visuales. (Los recipes son `EidosConfig.recipes`,
parte del bootstrap del app, no del theme.)
- Cambiar roles de color. (Los 9 roles son canon.)
- Cambiar sizes canon. (`SIZE_PRIMITIVE_KEYS` es fijo.)
### Lo que el app SÍ puede (al boot, no per-theme)
- Override de tokens en `EidosConfig.recipes` — cambia el RESULTADO
del cascade visual, no añade un variant nuevo.
- Crear componentes wrappers propios que compongan los primitives de
eidos con className/style custom.
- Cambiar tokens en runtime via `ActiveEidos.setCssVariables()` /
`clearCssVariables()` (per-app variables CSS).
feat(eidos): variants canon — EIDOS_VARIANTS + per-component lint Variants son canon del eidos, NO del theme. Decisión arquitectónica firmemente sostenida: el vocabulario de variants (solid/outline/ghost/ soft/surface/line/pills) está fijo a nivel del framework — paralelo a las 8 sema families del libro. Theme = retintar lo perceptualmente fijo; cambia QUÉ color es `affirm`, no QUÉ significa `outline`. Cambios: - lib/types.ts: nueva constante `EIDOS_VARIANTS` con los 5 archetypes canónicos (control / selection / chip / marker / tabs). Los 5 union types se derivan via `[number]` indexed access — valor y tipo no pueden desincronizarse. Nueva `EIDOS_VARIANT_VALUES` Set flat con todos los valores canónicos + utilidades cross-component (`plain`, `subtle`). - recipe-css-contract.test.ts: nuevo test "variant CSS selectors per component match the declared type union". Por cada componente: extrae el union type de `components/{c}/types.ts` (soporta literal unions + archetype aliases; cae a advisory mode en Extract<> y conditional types); compara con `[data-{c}][data-variant='X']` selectores en `{c}.css`; reporta typos y unauthorized extensions bidireccionalmente. - THEMING.md §19: nueva sección "Variants son canon del eidos, NO del theme" con argumentación (portabilidad, type safety, archetypes perceptuales paralelos a sema families), tabla de las 3 capas de la cebolla, referencia a `EIDOS_VARIANTS`, comparación con Radix Themes 3.x / Mantine 7 / Chakra v3 / Ark / shadcn. TOC actualizado. - eidos/README.md: tabla de referencia ampliada con §19. - CLAUDE.md: hand-off "2026-05-27 #6 (variants canon)". - CONTINUE.md: nota de la decisión arquitectónica. Variants component-specific permitidos (Banner inline/overlay/ persistent, Spinner bars/dots/ring, Button 'plain'): viven en cada `components/{c}/types.ts` y el lint los valida contra la CSS del componente. Tests: 101/101 pass en `src/uix/eidos`. `npm run check`: los mismos 6 errores pre-existentes (lib/_demo, soma/components/internal, web/ routes/active) — no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 months ago
### Cuándo añadir un archetype nuevo
Solo si EMERGE un patrón perceptual repetido en ≥3 componentes que no
encaja en los 5 archetypes existentes. Procedimiento:
1. Documentar el archetype con 1 párrafo describiendo el affordance
perceptual (paralelo a "solid = filled emphasis").
2. Añadirlo a `EIDOS_VARIANTS` en `lib/types.ts`.
3. Export el type derivado.
4. Migrar los componentes consumidores a referenciarlo.
5. Actualizar esta sección.
### Comparación con referentes
| Lib | Variants extensibles por theme | Variants extensibles por app |
|---|---|---|
| **Radix Themes 3.x** | ❌ | ❌ (fijos por componente) |
| **Mantine 7+** | ❌ | ❌ (defaultProps + styles override) |
| **Chakra UI v3 (Panda)** | ❌ | ⚠️ via recipes config (compound variants) |
| **Ark UI** | n/a (100% headless, sin opinión) | n/a |
| **shadcn/ui** | n/a (copy-paste, no framework) | ✓ (copia + edita) |
| **activeUIX** | ❌ | ⚠️ via `EidosConfig.recipes` override (cambia tokens, no añade variants) |
activeUIX se alinea con Radix Themes y Mantine: framework con contrato
fijo, theme con flexibilidad acotada al color/spacing. La extensibilidad
extrema (Tailwind, CSS-in-JS plain) es deliberadamente NO el goal —
porque la promesa del framework es portabilidad perceptual entre apps
y themes.
---
> **§20–§38 — crónica movida a [`THEMING_CHANGELOG.md`](./THEMING_CHANGELOG.md).**
> Estas secciones eran registros fechados de sprint (correcciones, incidentes,
> commits) mezclados con doctrina. La crónica completa vive ahora en el
> changelog **con la misma numeración §N**; abajo queda, por sección, la
> decisión vigente en una frase + el puntero a la fuente viva (RFC / config /
> generador). Las citas históricas `§N` del corpus y del código siguen
> resolviendo aquí.
## 20. Correcciones del engine de theming (2026-06-01)
Crónica en [`THEMING_CHANGELOG.md §20`](./THEMING_CHANGELOG.md). Vigente: la
densidad emite escalares reales por nivel (`data-density` mueve
`--density-space-scale` / `--density-control-scale`) y el slot `contrast`
resuelve legible sobre sólidos (APCA on-solid con flip; ver §25 y
`lib/render-css.ts`).
Theming: Radix-parity palette + intent auto-derivation + color-model docs Two-level color model settled (THEMING.md section 25), replacing the anchor RFC: the palette is the source (scales, directly usable, designable); hierarchy roles alias scales explicitly; intents auto-derive from the palette by the book's canonical convention. - Palette library expanded 12 -> 31 scales at Radix Colors parity (exact values): radix-scales.ts (19 added: mauve/sage/olive/sand/tomato/ruby/crimson/plum/ violet/iris/indigo/jade/grass/brown/sky/mint/lime/gold/bronze) spread into base.ts. Each directly usable as --scale-{name}-{step}. - Intent auto-derivation: CANONICAL_INTENT_SCALES (neutral->gray, affirm->teal, fulfill->green, risk->amber, threat->red, loss->plum) + completeColorRoleMap. Intents omitted from a theme role map fill from the convention (identity = step 9); slots derive normally; override optional. ColorRoleMap: hierarchy required, intents optional. - Validation: hierarchy roles required; omitted intents validate the canonical scale exists in the palette. - index: export ScalingKey / SCALING_KEYS. - docs: THEMING.md section 25 (full color model + decisions), section 21 marked resolved, COLOR_MODEL_RFC resolved (anchor rejected). - test: base library asserts 31 scales x 12 steps. Verified: npm run check (0 new errors), vitest eidos (0 new regressions), browser (intents auto-derive: affirm=teal #0E9384, risk=amber #DC6803, loss=plum #7A3AAD at step 9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
## 21. Modelo de color de dos niveles (RFC — RESUELTO en §25)
RFC resuelto — crónica en [`THEMING_CHANGELOG.md §21`](./THEMING_CHANGELOG.md).
El modelo vigente es el de §25 + [`COLOR_MODEL_RFC.md`](./COLOR_MODEL_RFC.md).
## 22. Mejoras pendientes del theming
Backlog histórico, resuelto — crónica en
[`THEMING_CHANGELOG.md §22`](./THEMING_CHANGELOG.md). La auditoría priorizada
que lo absorbió es [`THEMING_AUDIT_2026-06-01.md`](./THEMING_AUDIT_2026-06-01.md)
(scorecard completo).
## 23. Eje de `scaling` (zoom global) — 2026-06-02
Crónica en [`THEMING_CHANGELOG.md §23`](./THEMING_CHANGELOG.md). Vigente:
`data-scaling` (`90`–`110`) es el **zoom global** — multiplica `space`,
`control-height`, `font-size`, `icon-size` (SÍ tipografía); NO escala radius /
border / sombra. Ortogonal a la densidad (que NO toca tipografía) y se
multiplica con ella. Diseño completo: [`SCALING_RFC.md`](./SCALING_RFC.md).
Theming: on-solid contrast by luminance (P2-2) + translucent role surfaces (P2-4) Two audit P2 quality defects, fixed at engine level. P2-2 - on-solid text illegible on light solids: The `contrast` slot defaulted to `--color-content-on-solid` (white) for every role. On light solids (amber/yellow, risk=orange ~2.3:1) white is sub-AA. The engine now picks by WCAG contrast (gamma-linearized) of the role step-9: when white fails (<3:1) it uses `--color-content-on-solid-contrast` (a dark, new OPTIONAL `content.onSolidContrast` semantic, #1c1917 in base). Only risk flips to dark in base (5.89:1); purple/red/teal/green keep white (convention, >=3:1). An explicit `slots.contrast` override is still honored verbatim. P2-4 - opaque tinted soft surfaces: The soft variant tint (Button + Badge `{role}-soft-bg`) was opaque (step-1 track + opaque color-mix hover) so it did not composite over non-uniform backgrounds. New derived tokens `--color-{role}-surface` (= a2) + `--color-{role}-surface-hover` (= a3) are translucent by construction (compositing-inverse alpha). Button/Badge soft consume them. Toast/Tabs untouched - they are cards, opacity is correct. - config-types: optional onSolidContrast + CONTENT_COLOR_OPTIONAL_KEYS. - config: content keySet allows the optional key; validator value-checks it. - render-css: wcagContrastRatio/wcagRelativeLuminance; luminance pick; emit on-solid-contrast + surface/surface-hover. - contract: on-solid-contrast + surface tokens per role. - themes/base: onSolidContrast #1c1917 (light + dark). - recipes/base: Button + Badge soft-bg -> surface, soft-bg-hover -> surface-hover. - docs: THEMING.md section 24 + section 22 item 2; audit P2-2/P2-4 resolved. Verified: npm run check (0 new errors), vitest eidos (0 new regressions), browser runtime (risk 5.89:1 dark text, surfaces translucent rgba). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
## 24. Correcciones P2 del engine (2026-06-02)
Crónica en [`THEMING_CHANGELOG.md §24`](./THEMING_CHANGELOG.md). Vigente: los
tintes `surface`/`soft` por rol son **translúcidos por construcción**
(`--color-{role}-surface` = `a2`, `-surface-hover` = `a3`) — componen sobre
fondos no uniformes.
Theming: on-solid contrast by luminance (P2-2) + translucent role surfaces (P2-4) Two audit P2 quality defects, fixed at engine level. P2-2 - on-solid text illegible on light solids: The `contrast` slot defaulted to `--color-content-on-solid` (white) for every role. On light solids (amber/yellow, risk=orange ~2.3:1) white is sub-AA. The engine now picks by WCAG contrast (gamma-linearized) of the role step-9: when white fails (<3:1) it uses `--color-content-on-solid-contrast` (a dark, new OPTIONAL `content.onSolidContrast` semantic, #1c1917 in base). Only risk flips to dark in base (5.89:1); purple/red/teal/green keep white (convention, >=3:1). An explicit `slots.contrast` override is still honored verbatim. P2-4 - opaque tinted soft surfaces: The soft variant tint (Button + Badge `{role}-soft-bg`) was opaque (step-1 track + opaque color-mix hover) so it did not composite over non-uniform backgrounds. New derived tokens `--color-{role}-surface` (= a2) + `--color-{role}-surface-hover` (= a3) are translucent by construction (compositing-inverse alpha). Button/Badge soft consume them. Toast/Tabs untouched - they are cards, opacity is correct. - config-types: optional onSolidContrast + CONTENT_COLOR_OPTIONAL_KEYS. - config: content keySet allows the optional key; validator value-checks it. - render-css: wcagContrastRatio/wcagRelativeLuminance; luminance pick; emit on-solid-contrast + surface/surface-hover. - contract: on-solid-contrast + surface tokens per role. - themes/base: onSolidContrast #1c1917 (light + dark). - recipes/base: Button + Badge soft-bg -> surface, soft-bg-hover -> surface-hover. - docs: THEMING.md section 24 + section 22 item 2; audit P2-2/P2-4 resolved. Verified: npm run check (0 new errors), vitest eidos (0 new regressions), browser runtime (risk 5.89:1 dark text, surfaces translucent rgba). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
Theming: Radix-parity palette + intent auto-derivation + color-model docs Two-level color model settled (THEMING.md section 25), replacing the anchor RFC: the palette is the source (scales, directly usable, designable); hierarchy roles alias scales explicitly; intents auto-derive from the palette by the book's canonical convention. - Palette library expanded 12 -> 31 scales at Radix Colors parity (exact values): radix-scales.ts (19 added: mauve/sage/olive/sand/tomato/ruby/crimson/plum/ violet/iris/indigo/jade/grass/brown/sky/mint/lime/gold/bronze) spread into base.ts. Each directly usable as --scale-{name}-{step}. - Intent auto-derivation: CANONICAL_INTENT_SCALES (neutral->gray, affirm->teal, fulfill->green, risk->amber, threat->red, loss->plum) + completeColorRoleMap. Intents omitted from a theme role map fill from the convention (identity = step 9); slots derive normally; override optional. ColorRoleMap: hierarchy required, intents optional. - Validation: hierarchy roles required; omitted intents validate the canonical scale exists in the palette. - index: export ScalingKey / SCALING_KEYS. - docs: THEMING.md section 25 (full color model + decisions), section 21 marked resolved, COLOR_MODEL_RFC resolved (anchor rejected). - test: base library asserts 31 scales x 12 steps. Verified: npm run check (0 new errors), vitest eidos (0 new regressions), browser (intents auto-derive: affirm=teal #0E9384, risk=amber #DC6803, loss=plum #7A3AAD at step 9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
## 25. Modelo de color — paleta + roles/intents derivados (2026-06-02)
Crónica en [`THEMING_CHANGELOG.md §25`](./THEMING_CHANGELOG.md). Vigente (el
modelo de color canónico, tres capas):
Theming: Radix-parity palette + intent auto-derivation + color-model docs Two-level color model settled (THEMING.md section 25), replacing the anchor RFC: the palette is the source (scales, directly usable, designable); hierarchy roles alias scales explicitly; intents auto-derive from the palette by the book's canonical convention. - Palette library expanded 12 -> 31 scales at Radix Colors parity (exact values): radix-scales.ts (19 added: mauve/sage/olive/sand/tomato/ruby/crimson/plum/ violet/iris/indigo/jade/grass/brown/sky/mint/lime/gold/bronze) spread into base.ts. Each directly usable as --scale-{name}-{step}. - Intent auto-derivation: CANONICAL_INTENT_SCALES (neutral->gray, affirm->teal, fulfill->green, risk->amber, threat->red, loss->plum) + completeColorRoleMap. Intents omitted from a theme role map fill from the convention (identity = step 9); slots derive normally; override optional. ColorRoleMap: hierarchy required, intents optional. - Validation: hierarchy roles required; omitted intents validate the canonical scale exists in the palette. - index: export ScalingKey / SCALING_KEYS. - docs: THEMING.md section 25 (full color model + decisions), section 21 marked resolved, COLOR_MODEL_RFC resolved (anchor rejected). - test: base library asserts 31 scales x 12 steps. Verified: npm run check (0 new errors), vitest eidos (0 new regressions), browser (intents auto-derive: affirm=teal #0E9384, risk=amber #DC6803, loss=plum #7A3AAD at step 9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
- **Paleta** — escalas funcionales de 12 pasos + 12 alpha, diseñables por el
tema (`lib/themes/color-scales.ts` + `base.ts`); identidad de una escala =
step 9 (el sólido). Directamente usable: `var(--scale-{name}-{step})`.
- **Roles de jerarquía** (`primary`/`secondary`/`tertiary`) — alias explícito
a una escala: `THEME_BASE_COLOR_ROLES` (`lib/themes/base.ts`), ver §4.
- **Intents** — auto-derivados de la paleta por convención del libro
(`CANONICAL_INTENT_SCALES`, `lib/config-types.ts`); el tema puede desviarse
(convención ≠ autoría, §4).
feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed) RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime. Packages the demo-only builder into a first-class, tested API. - build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine (deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha) into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map. seed -> { variables, roles }. No DOM. 6 tests. - ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor scales + background from the active theme, writes a managed `uix-eidos-scheme` style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode change (follows light/dark). Returns BuildSchemeResult for introspection. opts: variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) + per-role overrides + selector. 4 tests (return value, intents, DOM block ordering + clear, mode re-derivation). - index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types. - temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated emitRole/rgbaStr; identical output verified in-browser). - generated/base.css: regenerated for the loss->plum role fix (binding layer --primitive-loss-* now points at --scale-plum-*; keeps the contract test green). - docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row. Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral chrome downstream; the 31-scale palette stays put. Math in $color, composition in eidos/lib (pure), DOM application in ActiveEidos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
Los slots por rol son `COLOR_ROLE_SLOTS` (§3, capa 3 — única fuente). Detalle
y racional: [`COLOR_MODEL_RFC.md`](./COLOR_MODEL_RFC.md) +
[`COLOR_ENGINE_RFC.md`](./COLOR_ENGINE_RFC.md).
feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed) RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime. Packages the demo-only builder into a first-class, tested API. - build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine (deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha) into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map. seed -> { variables, roles }. No DOM. 6 tests. - ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor scales + background from the active theme, writes a managed `uix-eidos-scheme` style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode change (follows light/dark). Returns BuildSchemeResult for introspection. opts: variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) + per-role overrides + selector. 4 tests (return value, intents, DOM block ordering + clear, mode re-derivation). - index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types. - temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated emitRole/rgbaStr; identical output verified in-browser). - generated/base.css: regenerated for the loss->plum role fix (binding layer --primitive-loss-* now points at --scale-plum-*; keeps the contract test green). - docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row. Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral chrome downstream; the 31-scale palette stays put. Math in $color, composition in eidos/lib (pure), DOM application in ActiveEidos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
## 26. Theme builder en runtime — `eidos.applyColorScheme` (2026-06-04)
feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed) RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime. Packages the demo-only builder into a first-class, tested API. - build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine (deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha) into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map. seed -> { variables, roles }. No DOM. 6 tests. - ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor scales + background from the active theme, writes a managed `uix-eidos-scheme` style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode change (follows light/dark). Returns BuildSchemeResult for introspection. opts: variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) + per-role overrides + selector. 4 tests (return value, intents, DOM block ordering + clear, mode re-derivation). - index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types. - temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated emitRole/rgbaStr; identical output verified in-browser). - generated/base.css: regenerated for the loss->plum role fix (binding layer --primitive-loss-* now points at --scale-plum-*; keeps the contract test green). - docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row. Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral chrome downstream; the 31-scale palette stays put. Math in $color, composition in eidos/lib (pure), DOM application in ActiveEidos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
Crónica en [`THEMING_CHANGELOG.md §26`](./THEMING_CHANGELOG.md). Vigente:
`buildScheme(seed, opts)` (puro, `lib/build-scheme.ts`) +
`ActiveEidos.applyColorScheme(seed, opts)` / `clearColorScheme()` — deriva un
scheme completo (roles + alphas `a1..a12` + on-solid APCA) del tema activo y
re-deriva al cambiar de modo. Capas: `uix.color` = matemática ·
`build-scheme` = composición pura · `ActiveEidos` = aplicación DOM.
API: [`COLOR_ENGINE_RFC.md`](./COLOR_ENGINE_RFC.md) §6.2/§7.
feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed) RFC Phase 4: derive a whole-system color scheme from ONE brand seed at runtime. Packages the demo-only builder into a first-class, tested API. - build-scheme.ts (pure): buildScheme(seed, opts) composes the uix.color engine (deriveScheme -> generateScale -> APCA on-solid -> compositing-inverse alpha) into the `--primitive-{role}-*` (+ `--color-{role}-contrast`) override map. seed -> { variables, roles }. No DOM. 6 tests. - ActiveEidos.applyColorScheme(seed, opts) / clearColorScheme(): resolves donor scales + background from the active theme, writes a managed `uix-eidos-scheme` style block AFTER the theme block (wins the cascade), and RE-DERIVES on mode change (follows light/dark). Returns BuildSchemeResult for introspection. opts: variant (tonal|vibrant|monochrome) + temper (intent coherence, keeps hue) + per-role overrides + selector. 4 tests (return value, intents, DOM block ordering + clear, mode re-derivation). - index.ts: export buildScheme + ApplyColorSchemeOptions + BuildScheme* types. - temas/color demo: themeOverride now dogfoods buildScheme (drops the duplicated emitRole/rgbaStr; identical output verified in-browser). - generated/base.css: regenerated for the loss->plum role fix (binding layer --primitive-loss-* now points at --scale-plum-*; keeps the contract test green). - docs: THEMING.md SS26 + COLOR_ENGINE_RFC SS6.2 (status: landed) + README ref row. Overriding the binding layer reprojects every --color-{role}-{slot} + the neutral chrome downstream; the 31-scale palette stays put. Math in $color, composition in eidos/lib (pure), DOM application in ActiveEidos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
## 27. Salida wide-gamut OKLCH (default-on) (2026-06-04)
Crónica en [`THEMING_CHANGELOG.md §27`](./THEMING_CHANGELOG.md). Vigente: cada
paso de paleta emite hex (fallback) + hermano `oklch()` que gana donde se
soporta — default-on, sin flag (`appendColorScaleDeclarations`,
`lib/render-css.ts`). El wide-gamut REAL vive en el generador (`buildScheme`
retiene el OKLCH sin clamp → `result.wideGamut`).
feat(eidos): forced-colors focus a11y (P3-2) + role border ramp 6->7 (P3-3) Closes the two color-quality items from THEMING_AUDIT P3. P3-2 · forced-colors (Windows High Contrast): under @media (forced-colors: active) the browser auto-maps borders/text/backgrounds to system colors BUT drops box-shadow — so the box-shadow focus ring (--focus-ring) vanishes and keyboard focus disappears. The foundation now always emits a system-colored outline fallback: @media (forced-colors: active) { :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; } } Components that already focus via outline (e.g. Button) keep theirs by specificity; this is the fallback for the box-shadow ones. renderForcedColorsBlock in render-css.ts. P3-3 · role border ramp: the per-role `border` slot moved step 6 -> 7. In Radix's functional scale 6 is a subtle separator and 7 is the UI element border; step 6 read washed-out on real element borders (outline/surface/controls). element/hover/active (3/4/5) stay — Radix-canonical for component bg. DEFAULT_COLOR_ROLE_SLOT_STEPS. - generated/base.css regenerated (forced-colors block + --color-{role}-border -> step 7). - Verified in browser: --color-primary-border now resolves to primitive-7 (oklch 0.80 0.092 vs the softer step-6 0.86 0.072); checkbox borders render defined, not broken. - test: renderStaticCss emits the forced-colors outline block. - docs: THEMING §28 + audit P3-1/2/3 marked resolved + README ref row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
## 28. Accesibilidad forced-colors + ramp de bordes (2026-06-05)
Crónica en [`THEMING_CHANGELOG.md §28`](./THEMING_CHANGELOG.md). Vigente: el
foundation emite siempre `@media (forced-colors: active)` (focus por `outline`
— el box-shadow muere en HCM) y `@media (prefers-contrast: more)` (bordes y
texto reforzados vía `:root:root`); el slot `border` = **step 7** de la escala
(`DEFAULT_COLOR_ROLE_SLOT_STEPS`).
feat(eidos): forced-colors focus a11y (P3-2) + role border ramp 6->7 (P3-3) Closes the two color-quality items from THEMING_AUDIT P3. P3-2 · forced-colors (Windows High Contrast): under @media (forced-colors: active) the browser auto-maps borders/text/backgrounds to system colors BUT drops box-shadow — so the box-shadow focus ring (--focus-ring) vanishes and keyboard focus disappears. The foundation now always emits a system-colored outline fallback: @media (forced-colors: active) { :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; } } Components that already focus via outline (e.g. Button) keep theirs by specificity; this is the fallback for the box-shadow ones. renderForcedColorsBlock in render-css.ts. P3-3 · role border ramp: the per-role `border` slot moved step 6 -> 7. In Radix's functional scale 6 is a subtle separator and 7 is the UI element border; step 6 read washed-out on real element borders (outline/surface/controls). element/hover/active (3/4/5) stay — Radix-canonical for component bg. DEFAULT_COLOR_ROLE_SLOT_STEPS. - generated/base.css regenerated (forced-colors block + --color-{role}-border -> step 7). - Verified in browser: --color-primary-border now resolves to primitive-7 (oklch 0.80 0.092 vs the softer step-6 0.86 0.072); checkbox borders render defined, not broken. - test: renderStaticCss emits the forced-colors outline block. - docs: THEMING §28 + audit P3-1/2/3 marked resolved + README ref row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
## 29. Profundidad (depth) — canal unificado + eventful (2026-06-05)
Crónica en [`THEMING_CHANGELOG.md §29`](./THEMING_CHANGELOG.md). Vigente: la
profundidad es UN canal — `data-depth='{plane}'` (`flush · raised · overlay ·
modal · recessed`) cohere superficie + sombra + halo + z en reposo, y la firma
de evento (`present-rise` / `press-squeeze`) la mueve en el momento-evento.
Planos config-driven (`EidosConfig.depth.planes`); los overlays componen
`var(--depth-{plane}-shadow), var(--depth-{plane}-halo)`. Guía canónica:
[`DEPTH_ENGINE_RFC.md`](./DEPTH_ENGINE_RFC.md). Demo: `/temas/profundidad`.
## 30. Forma (shape) — continuidad + familias + anidado + eventful (2026-06-05)
Crónica en [`THEMING_CHANGELOG.md §30`](./THEMING_CHANGELOG.md). Vigente: la
forma es un canal — `--shape-smoothing` + `data-shape='{family}'`
(`rounded · continuous · cut · scoop`) sobre `corner-shape`, con degradación
al arco de `border-radius`; armonía anidada vía `[data-shape-nest]`
(concéntrico); el squircle es el **default del tier surface**
(`renderShapeBlocks` `:where` + `--shape-surface-default`; `shape` = opt-OUT).
Builder runtime `applyShape(seed)`. Guía canónica:
[`SHAPE_ENGINE_RFC.md`](./SHAPE_ENGINE_RFC.md). Demo: `/temas/forma`.
## 31. Estructura (espacio · densidad · escala) — el espacio como ritmo (2026-06-05)
Crónica en [`THEMING_CHANGELOG.md §31`](./THEMING_CHANGELOG.md). Vigente: los
tres ejes estructurales son solo-estado — densidad (`data-density`), scaling
(`data-scaling`, §23) y espacio: `--space-{key}` =
`calc(value · var(--density-space-scale) · var(--scaling))`, regenerable desde
una semilla modular/fluida (`buildSpaceScale` + `applySpacing`). Guía
canónica: [`STRUCTURE_ENGINE_RFC.md`](./STRUCTURE_ENGINE_RFC.md). Demo:
`/temas/estructura`.
feat(eidos): forced-colors focus a11y (P3-2) + role border ramp 6->7 (P3-3) Closes the two color-quality items from THEMING_AUDIT P3. P3-2 · forced-colors (Windows High Contrast): under @media (forced-colors: active) the browser auto-maps borders/text/backgrounds to system colors BUT drops box-shadow — so the box-shadow focus ring (--focus-ring) vanishes and keyboard focus disappears. The foundation now always emits a system-colored outline fallback: @media (forced-colors: active) { :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; } } Components that already focus via outline (e.g. Button) keep theirs by specificity; this is the fallback for the box-shadow ones. renderForcedColorsBlock in render-css.ts. P3-3 · role border ramp: the per-role `border` slot moved step 6 -> 7. In Radix's functional scale 6 is a subtle separator and 7 is the UI element border; step 6 read washed-out on real element borders (outline/surface/controls). element/hover/active (3/4/5) stay — Radix-canonical for component bg. DEFAULT_COLOR_ROLE_SLOT_STEPS. - generated/base.css regenerated (forced-colors block + --color-{role}-border -> step 7). - Verified in browser: --color-primary-border now resolves to primitive-7 (oklch 0.80 0.092 vs the softer step-6 0.86 0.072); checkbox borders render defined, not broken. - test: renderStaticCss emits the forced-colors outline block. - docs: THEMING §28 + audit P3-1/2/3 marked resolved + README ref row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
## 32. Focus ring — modelo de dos anillos parametrizado (2026-06-11)
Crónica en [`THEMING_CHANGELOG.md §32`](./THEMING_CHANGELOG.md). Vigente: UN
modelo de foco — el anillo canónico (`--focus-ring` + los `*-focus-shadow` de
recipes) es dos anillos parametrizados (`--focus-ring-inner-width`, default
`0` = solo marco exterior; `STATIC_FOCUS_RING`, `primitives/static.ts`); el
anillo del foundation excluye los elementos internos de campo. Nota a11y §28:
en forced-colors el foco visible es `outline` (la migración per-componente de
box-shadow → outline es trabajo abierto).
refactor(eidos): share number-field + css-field visual via spin-field NumberField and CssField are the same visual (a bordered field + input + increment/decrement triggers + scrubber, split/stacked layouts, sizes/ variants/colors, themeable glyphs); only their value model differs. They were two cloned recipes + CSS that drifted — a refinement to one (square flush buttons, divider, contrast) didn't reach the other. Unify into ONE shared source (the toggle-group structural-identity pattern): - New eidos/components/spin-field: recipe key `spin-field` (`--spin-field-*`) + spin-field.css with all the stepper-field rules, selecting `[data-spin- field*]`. Loaded via the foundation @import in index.css. - number-field + css-field morfos declare structural identity (`data-spin- field*` presence attrs on each part). The Provider emits them via syncAttrs; the sub-parts emit them in their soma `props` getter (number-field's soma hardcodes sub-part attrs rather than syncing the morfo). - Removed the `number-field` / `css-field` recipe keys; their CSS files are now stubs. A theme tints one component by scoping `[data-number-field] { --spin-field-… }`. - css-field thereby adopts number-field's refined steppers (square, flush, divider) — the drift fix the user asked for, now structural (no clone). Verified bit-for-bit in browser: number-field identical to baseline (split flush, stacked symmetric xs..xl, RTL, glyph token/children override); css-field now square/flush/divider. eidos-lint invalid 0; recipe contract passes (no orphans, loads-once); check + morfo:check clean for these. Docs: THEMING sections 33 (glyph tokens now `--spin-field-*`) + 34 (the shared layer); number-field / css-field READMEs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
## 33. Glifos de stepper themeables (`spin-field`) — 2026-06-11
Crónica en [`THEMING_CHANGELOG.md §33`](./THEMING_CHANGELOG.md). Vigente: los
glifos del stepper (`spin-field`) son tokens de recipe themeables, no SVG
hardcodeado.
refactor(eidos): share number-field + css-field visual via spin-field NumberField and CssField are the same visual (a bordered field + input + increment/decrement triggers + scrubber, split/stacked layouts, sizes/ variants/colors, themeable glyphs); only their value model differs. They were two cloned recipes + CSS that drifted — a refinement to one (square flush buttons, divider, contrast) didn't reach the other. Unify into ONE shared source (the toggle-group structural-identity pattern): - New eidos/components/spin-field: recipe key `spin-field` (`--spin-field-*`) + spin-field.css with all the stepper-field rules, selecting `[data-spin- field*]`. Loaded via the foundation @import in index.css. - number-field + css-field morfos declare structural identity (`data-spin- field*` presence attrs on each part). The Provider emits them via syncAttrs; the sub-parts emit them in their soma `props` getter (number-field's soma hardcodes sub-part attrs rather than syncing the morfo). - Removed the `number-field` / `css-field` recipe keys; their CSS files are now stubs. A theme tints one component by scoping `[data-number-field] { --spin-field-… }`. - css-field thereby adopts number-field's refined steppers (square, flush, divider) — the drift fix the user asked for, now structural (no clone). Verified bit-for-bit in browser: number-field identical to baseline (split flush, stacked symmetric xs..xl, RTL, glyph token/children override); css-field now square/flush/divider. eidos-lint invalid 0; recipe contract passes (no orphans, loads-once); check + morfo:check clean for these. Docs: THEMING sections 33 (glyph tokens now `--spin-field-*`) + 34 (the shared layer); number-field / css-field READMEs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 months ago
## 34. `spin-field` — visual compartido del stepper-field (`number-field` / `css-field`) — 2026-06-11
Crónica en [`THEMING_CHANGELOG.md §34`](./THEMING_CHANGELOG.md). Vigente:
`number-field` y `css-field` comparten UNA capa visual vía identidad
estructural `data-spin-field*` (`components/spin-field/spin-field.css`,
agregada en `index.css`) — sin clon.
## 35. Canon de escalas — auditoría de theming (2026-06-15)
Crónica en [`THEMING_CHANGELOG.md §35`](./THEMING_CHANGELOG.md). Vigente: cada
eje de escala (blur · inner-shadow · inset-ring · gradientes · breakpoints ·
container · opacidad — dual numérica+semántica · border-width · tracking) es
retunable por tema y **los recipes consumen el token, nunca un literal**
(guards R-2.x/R-4.x). El inventario vive en `EidosConfig`
(`lib/primitives/*` + `config-types.ts`) y la tabla de naming en §6;
breakpoints con fuente en `ActiveDom`. Los 3 arquetipos de size
(`control · compact · dense`) están en §5.
## 36. Gap canónico trigger→panel — offset token-driven (2026-06-22)
Crónica en [`THEMING_CHANGELOG.md §36`](./THEMING_CHANGELOG.md). Vigente: el
gap trigger→panel es un OFFSET del motor de posicionamiento vía
`@property --floating-gap` — menús `0`, paneles `--space-1-5`.
## 37. Touch-target — 44px en táctil, gated por puntero (2026-06-28)
Crónica en [`THEMING_CHANGELOG.md §37`](./THEMING_CHANGELOG.md). Vigente: los
touch-targets de 44px se aplican SOLO bajo `@media (pointer: coarse)`;
markers vía label-row, sin reestructurar DOM.
## 38. Capa de estado (state-layer) — feedback neutro unificado (2026-06-28)
Crónica en [`THEMING_CHANGELOG.md §38`](./THEMING_CHANGELOG.md). Vigente: el
feedback interactivo neutro (hover/press/selected) es la capa de estado MD3 —
tokens `--state-hover` / `--state-press` / `--state-selected` compuestos como
`background-image: linear-gradient(...)` en `archetypes.css`; los hovers
bespoke por componente están deprecados (R-4.3).
---
**Última revisión**: 2026-07-02. Si algo en este doc no coincide con
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage Token Scope Contract universal — no más excepciones arquitectónicas. Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar, toggle-group) ahora están dentro del contrato via dos extensiones nuevas. TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts): - `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para componentes con data-color cascadeado per-part. Consumer: select. - `composition: { foreignRecipe: { targetSelector, tokens } }` sibling key — overrides cross-recipe scoped a la cascade del host. Consumer: toggle-group modifica `--toggle-palette-*` en sus items. Migraciones: - select: 3 private `_accent-{track,border,text}` con parts: ['trigger', 'content'] + 7 cascades color:X each. Removed orphan `_accent-solid` (CSS no consumía). - avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques CSS × 2 partes. - toggle-group: composition block con 8 palette tokens × 4 colors. Reemplaza 4 bloques CSS per-color. Bug toggle-group post-composition (encontrado y arreglado): Tras la composition migration, el cascade del toggle-group seguía roto porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía undefined. Fix: inlined derivation expressions directamente en `[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost), referenciando palette tokens en su propio scope local. Validador + emisor + contract: - `validateRecipeComposition` valida el shape `{ targetSelector, tokens }` y rechaza composition entries con scope='root'. - `stripCompositionKey` + `emitComposition` separan el pipeline. - `appendRecipeContractTokens` skip-list para `composition` (no aparece como fake `--{c}-composition` knob). - `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts filtran composition en todos los iteradores. Documentación: - THEMING.md §18 reescrito como "Cobertura universal de TSC". §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" + ejemplos completos. TOC actualizado. - eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18. - CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal). - CONTINUE.md reescrito al estado actual de la sesión. Working tree también incluye sprint Words en paralelo (multiple authors): slash menu, find/replace regex, code language picker, table audit, toolbar family menu, code highlight engine. Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`: 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active) no relacionados. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
el código, el código gana — pero abre un issue para que actualicemos
el doc.

Powered by TurnKey Linux.