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
|
|
|
|
# Auditoría Fable — Eidos y el sistema de theming
|
|
|
|
|
|
|
|
|
|
|
|
> **Fecha**: 2026-07-01 · **Rama**: `alpha-0.1-sec-dom` · **Auditor**: Claude Fable 5
|
|
|
|
|
|
> **Alcance**: la capa eidos completa — foundation generada, colores, superficies,
|
|
|
|
|
|
> tipografía, sizes/espaciado/densidad/scaling, elevación (shadow/depth), forma, motion,
|
|
|
|
|
|
> focus/estado/touch, el theme builder runtime (`apply*`) y su documentación (THEMING.md,
|
|
|
|
|
|
> TSC.md, THEMING_GUIDE, components/README, eidos-motion.md).
|
|
|
|
|
|
> **Método**: lectura directa de THEMING.md (secciones canónicas + §20–§38),
|
|
|
|
|
|
> `lib/render-css.ts` (parcial), `lib/recipes/base.ts` (4.459 líneas, por barridos),
|
|
|
|
|
|
> `lib/themes/base.ts`, `lib/primitives/{static,typography}.ts`, `lib/build-scheme.ts`
|
|
|
|
|
|
> (completo), `archetypes.css` (completo), `events.css` (completo),
|
|
|
|
|
|
> `active-eidos.svelte.ts` (auditado en `fable_audit.md`), más **barridos cuantitativos
|
|
|
|
|
|
> por grep sobre los 135 CSS de componentes y el foundation generado**. Cada cifra de
|
|
|
|
|
|
> este informe sale de un grep reproducible, no de la documentación.
|
|
|
|
|
|
> Complementa (no repite) los hallazgos E1–E6 de [`fable_audit.md`](fable_audit.md).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Resumen ejecutivo — la hipótesis se confirma, con matiz
|
|
|
|
|
|
|
|
|
|
|
|
La sospecha del encargo era: *"hay incoherencias, no un esquema coherente; se ha ido
|
|
|
|
|
|
desviando con cada modificación y conceptualmente no hay una guía con la que se
|
|
|
|
|
|
implementen los componentes"*. El diagnóstico tras la evidencia:
|
|
|
|
|
|
|
|
|
|
|
|
**El núcleo está bien y está protegido.** El modelo de color (33 escalas → 9 roles →
|
|
|
|
|
|
slots → palette por componente), el TSC, la tokenización de ejes crudos (blur, border,
|
|
|
|
|
|
z-index de overlays, tracking) y los guards mecánicos que existen (`recipe-css-contract`,
|
|
|
|
|
|
guard tipográfico anti-literal, guard de z crudo, lint de variants) funcionan: en 135
|
|
|
|
|
|
ficheros CSS de componentes hay **0 usos de `--focus-ring` legacy, 1 solo fichero con
|
|
|
|
|
|
hex crudos, 0 blur literales, 5 duraciones ms sueltas, 1 uso directo de `--scale-*`**.
|
|
|
|
|
|
Donde hay guard, no hay deriva.
|
|
|
|
|
|
|
|
|
|
|
|
**La deriva es real, pero no está en los tokens: está en los SISTEMAS.** Eidos es hoy una
|
|
|
|
|
|
federación de **~14 subsistemas canónicos añadidos por sprints fechados** (densidad,
|
|
|
|
|
|
scaling, depth, shape, structure, focus de dos anillos, state-layer, touch-target,
|
|
|
|
|
|
list-surface, gap flotante, motion de dos momentos, tipografía 1:1, opacidad unificada,
|
|
|
|
|
|
gradientes) donde **cada uno declara ser "el canon" y ninguno alcanzó adopción completa**:
|
|
|
|
|
|
|
|
|
|
|
|
| Sistema canónico (fecha) | Promesa | Adopción real medida |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| State-layer `--state-*` (§38, 06-28) | sustituir ~168 hovers bespoke | **21/135** CSS lo usan; **22 tokens `*-bg-hover`** siguen declarados en recipes; el propio `archetypes.css` mantiene el idiom viejo en `close:hover` (opacity-dim 0.85) |
|
|
|
|
|
|
| Depth channel `--depth-*` (§29, 06-05) | "canal unificado, no tres sistemas sueltos" | 7 CSS + 6 refs en recipes componen el canal; el resto sigue en la escala `--shadow-*` directa; adopción por `data-depth` = "opción futura" → **dos sistemas conviven** (corrección 2026-07-02: los `box-shadow` de componentes SÍ están tokenizados — R-4.1 = 0 literales; el hallazgo es la partición escala-vs-canal, no literales) |
|
|
|
|
|
|
| Bundle `--size-{k}-*` (§5) | "map global coordinado" | **0 consumidores** (confirmado hoy por grep; el doc lo admite: "huérfano") |
|
|
|
|
|
|
| Opacidad unificada (§35, 06-15) | "~10 valores de disabled → 1" | 28 reglas usan `--opacity-disabled` (0.4)… y `archetypes.css` hardcodea **0.5 tres veces**, contradiciendo su propio token (corrección 2026-07-02: fuera de `@keyframes` solo quedan **2 componentes** con literal — el grueso de los 20 matches iniciales eran fades de keyframes, exentos) |
|
|
|
|
|
|
| list-surface (06-21) | ritmo único de menú/lista | **4/135** adoptantes; select/combobox "recipe-level pending" |
|
|
|
|
|
|
| Motion 2 momentos (F1–F7) | keyframes/firmas en config, themeables | **49 `@keyframes` locales en 25 CSS** fuera del canal; 14 componentes usan presets (`data-animation-style` — corrección 2026-07-02; dropdown/context-menu están en AMBAS listas: presets Y keyframe local). El repositorio (verificado): ~40 keyframes + 12 firmas + 13 presets de estado con `bySide`/`reduce` + 4 loops — cubre lo que los 15 del backlog re-implementan a mano (`slide-fade`, `collapse`, `shared-axis-x`) |
|
|
|
|
|
|
| Alpha scales a1..a12 (README) | tintas translúcidas para overlays/rings/hovers | **0 consumos directos** de `--primitive-*-aN`/`--scale-*-aN` en recipes+componentes; solo a2/a3 viven indirectamente vía los slots `surface`/`surface-hover` → **~500 tokens emitidos sin consumidor** |
|
|
|
|
|
|
| Slots de color emitidos | 14 slots × rol | `text-strong`, `separator`, `border-hover` = **0 usos**; `hover` = 8, `active` = 1 (sobre 36 emisiones cada uno) |
|
|
|
|
|
|
|
|
|
|
|
|
**Y la causa raíz es exactamente la que el encargo intuía**: no existe un **contrato por
|
|
|
|
|
|
componente** que diga "una recipe DEBE consumir estos sistemas" — el `component-audit`
|
|
|
|
|
|
mecánico cubre 10 reglas (R-1.x a R-3.x) anteriores a junio y **ninguna de las nuevas
|
|
|
|
|
|
capas canónicas** (state-layer, focus, touch, depth, list-surface). Cada sistema nuevo se
|
|
|
|
|
|
desplegó como sprint manual (pilot + N componentes) que se detuvo dejando una sección
|
|
|
|
|
|
"Pendiente", y THEMING.md creció como **changelog fechado disfrazado de referencia**
|
|
|
|
|
|
(§20–§38 son sprints; §5 contiene dos doctrinas revocadas inline; §13–§14 están
|
|
|
|
|
|
"superseded" pero siguen dentro; §1 dice "5 layers de CSS" y §2 dice "6 capas").
|
|
|
|
|
|
|
|
|
|
|
|
**Veredicto**: coherencia del foundation **8.5/10** · coherencia de sistemas transversales
|
|
|
|
|
|
**5/10** · theme builder **7/10** (bugs concretos abajo) · documentación como guía de
|
|
|
|
|
|
implementación **4/10**. No hace falta rediseñar: hace falta **cerrar adopciones,
|
|
|
|
|
|
canonizar el contrato de recipe como regla mecánica, y partir THEMING.md**.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. El patrón de deriva (diagnóstico estructural)
|
|
|
|
|
|
|
|
|
|
|
|
Los cinco mecanismos que producen la incoherencia, todos observables en el repo:
|
|
|
|
|
|
|
|
|
|
|
|
1. **Canon por acreción, no por sustitución.** Cada sprint añade un sistema "canónico"
|
|
|
|
|
|
sin retirar el anterior: elevación tiene hoy `--shadow-{1..6}` + aliases semánticos +
|
|
|
|
|
|
`--depth-{plane}-*` conviviendo. Feedback neutro tiene state-layer + surface-swap +
|
|
|
|
|
|
opacity-dim + 22 tokens bespoke. Translucidez tiene alpha-scales + `color-mix(...%,
|
|
|
|
|
|
transparent)` + `--opacity-*`. **Varios idiomas por concepto es la norma, no la
|
|
|
|
|
|
excepción.**
|
|
|
|
|
|
2. **Rollouts que se detienen en el "Pendiente".** §38 ("Pendiente: poda de bg-hover +
|
|
|
|
|
|
tabs + color-picker"), §29 ("adopción plena vía data-depth queda como opción futura"),
|
|
|
|
|
|
§5 ("el refactor a consumir el bundle queda como follow-up"), list-surface ("select/
|
|
|
|
|
|
combobox pending"). El parking-lot es sistemático y nada lo re-visita.
|
|
|
|
|
|
3. **El doc de referencia es un changelog.** THEMING.md §20–§38 son informes de sprint
|
|
|
|
|
|
con fechas, incidentes (¡el de PowerShell!), commits y doctrinas revocadas conservadas
|
|
|
|
|
|
inline. Un implementador nuevo no puede extraer de ahí "qué debe hacer mi recipe hoy"
|
|
|
|
|
|
sin arqueología.
|
|
|
|
|
|
4. **Guard-coverage asimétrico.** Donde hay guard mecánico (tipografía literal, z crudo,
|
|
|
|
|
|
variants, recipe-contract) la deriva es ~0. Donde el canon es prosa (state-layer,
|
|
|
|
|
|
depth, opacidad en componentes, padding axes) la deriva es del 30–85%. La lección está
|
|
|
|
|
|
en el propio repo y no se aplicó a los sistemas de junio.
|
|
|
|
|
|
5. **Sin plantilla de recipe.** No hay un esqueleto canónico de recipe (qué ejes declara,
|
|
|
|
|
|
con qué nombres, qué sistemas consume). Resultado medible: los ejes de padding se
|
|
|
|
|
|
nombran de dos formas al 50% (`padding-x/y` ×18 vs `padding-inline/block` ×18), las
|
|
|
|
|
|
alturas de tres (`height-*` ×50, `control-height-*` ×15, `trigger-height-*` ×11), y el
|
|
|
|
|
|
vocabulario de slots dimensionales no existe (el §6 solo fija slots de color).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Hallazgos por subsistema
|
|
|
|
|
|
|
|
|
|
|
|
Severidades: **P0** bug visible/corrección · **P1** incoherencia estructural ·
|
|
|
|
|
|
**P2** mejora clara · **P3** higiene.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 Color — núcleo sólido, periferia con huérfanos y docs desalineados
|
|
|
|
|
|
|
|
|
|
|
|
Lo bueno: la cadena escala→primitive→slot→palette se respeta en la práctica (609 refs
|
|
|
|
|
|
`--color-{role}-{slot}` en recipes, 1 solo bypass a `--scale-*`, 0 a `--primitive-*` en
|
|
|
|
|
|
CSS de componentes). El slot vocabulary REAL usado es coherente (border/solid/text/track/
|
|
|
|
|
|
element/solid-hover/contrast dominan).
|
|
|
|
|
|
|
|
|
|
|
|
- **C1 · P1 — Tres listas de slots, ninguna coincide.** §3 declara "13 slots" (incluye
|
|
|
|
|
|
`bg2`); el generador emite **14** (sin `bg2`, con `surface`/`surface-hover`); §6 regla 7
|
|
|
|
|
|
fija un "vocabulario fijo" de **9** que no incluye `surface`, `surface-hover`,
|
|
|
|
|
|
`separator`, `border-hover` ni `text-strong`. La fuente ejecutable
|
|
|
|
|
|
(`DEFAULT_COLOR_ROLE_SLOT_STEPS`, render-css.ts:93) no está citada por ninguna de las
|
|
|
|
|
|
tres. *Acción*: una sola tabla generada desde el código.
|
|
|
|
|
|
- **C2 · P1 — Slots emitidos sin consumidor.** `text-strong`, `separator`, `border-hover`:
|
|
|
|
|
|
0 usos; `hover`: 8; `active`: 1 (cada uno se emite 36 veces en generated). Son bytes del
|
|
|
|
|
|
"foundation floor" que el purge nunca podrá quitar (foundation siempre kept). Decidir:
|
|
|
|
|
|
consumir (p. ej. `separator` es exactamente lo que muchos recipes resuelven con
|
|
|
|
|
|
`border-subtle`) o dejar de emitir.
|
|
|
|
|
|
- **C3 · P1 — La tabla de mapeo de §4 miente.** Doc: `primary→indigo`, `risk→amber`.
|
|
|
|
|
|
Código (`THEME_BASE_COLOR_ROLES`): `primary→purple`, `tertiary→indigo`, `risk→orange`.
|
|
|
|
|
|
Tres celdas estables desde hace semanas — el doc canónico de roles describe un tema que
|
|
|
|
|
|
ya no existe.
|
|
|
|
|
|
- **C4 · P1 — Tres idiomas de translucidez.** (a) alpha-scales `aN` (emitidas, huérfanas
|
|
|
|
|
|
salvo a2/a3 indirectas); (b) `color-mix(in srgb, X N%, transparent)` — el idiom real
|
|
|
|
|
|
dominante (focus ring, state-layer, backdrops); (c) `--opacity-*`. No hay doctrina de
|
|
|
|
|
|
cuándo usar cada uno. La inversión (≈500 tokens alpha) no la usa nadie mientras el
|
|
|
|
|
|
código produce las mismas tintas con color-mix ad-hoc.
|
|
|
|
|
|
- **C5 · P3 — `natural-time-picker` conserva 6 hex crudos** (cielos del reloj natural +
|
|
|
|
|
|
`--_ntp-ink: #ffffff`). Los cielos son candidatos legítimos a "físicamente fijos", pero
|
|
|
|
|
|
la excepción exige comentario justificativo (doctrina "no intentional sin anotar") y no
|
|
|
|
|
|
lo llevan todos.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 Theme builder (`buildScheme` / `applyColorScheme` / `applyTheme`)
|
|
|
|
|
|
|
|
|
|
|
|
- **TB1 · P0 — El scheme deja las alphas viejas.** `buildScheme` solo emite
|
|
|
|
|
|
`--primitive-{role}-a2/a3` (build-scheme.ts:155-158). El foundation emite a1..a12 por
|
|
|
|
|
|
rol. Tras `applyColorScheme(seed)`, **a1 y a4..a12 siguen apuntando al color del tema
|
|
|
|
|
|
anterior** — cualquier consumidor futuro de esas alphas (o un tema externo que las use,
|
|
|
|
|
|
están en el contrato público) mezclará el morado viejo con el brand nuevo. Emitir las
|
|
|
|
|
|
12 (la matemática `alphaOverBackground` ya existe) o retirar a4..a12 del contrato (ver
|
|
|
|
|
|
C4).
|
|
|
|
|
|
- **TB2 · P0 — Donores de intent ignoran el tema activo.** Con `temper > 0`, los intents
|
|
|
|
|
|
se derivan desde `CANONICAL_INTENT_SCALES` (build-scheme.ts:184): `risk` siempre parte
|
|
|
|
|
|
de **amber**, aunque el tema base mapea `risk→orange`. Consecuencia: al subir el slider
|
|
|
|
|
|
de temper, `risk` **cambia de familia de hue** (naranja→ámbar) además de temperarse — un
|
|
|
|
|
|
salto perceptivo que no es el contrato del knob ("mantiene su hue"). El donor debe salir
|
|
|
|
|
|
del role-map del tema activo (`getColorRoleScale('risk')`), con el canónico como
|
|
|
|
|
|
fallback.
|
|
|
|
|
|
- **TB3 · P1 — Background de compositing hardcodeado.** `#buildSchemeResult` usa
|
|
|
|
|
|
`mode === 'dark' ? '#111111' : '#ffffff'` (active-eidos.svelte.ts:845) mientras el
|
|
|
|
|
|
fondo real del tema es `--color-surface-default = neutral-1` (`#fcfcfc` light; el dark
|
|
|
|
|
|
de gray-1 no es `#111111`). Las a2/a3 generadas quedan compositadas contra un fondo que
|
|
|
|
|
|
no es el del tema — error pequeño en el base, grande en temas tintados (cristal, brand
|
|
|
|
|
|
oscuro azulado). Resolver el token vía `resolveToken('--color-surface-default')`.
|
|
|
|
|
|
- **TB4 · P2 — `applyTheme` no es atómico en coste** (ya en fable_audit E1): cada builder
|
|
|
|
|
|
se construye dos veces y `apply()` regenera TODO el static CSS. Para el caso de uso
|
|
|
|
|
|
anunciado (sliders del theme builder en vivo) es cuadrático respecto a lo necesario.
|
|
|
|
|
|
- **TB5 · P2 — Literales duplicados**: `onSolidContrast: '#1c1917'` vive como default en
|
|
|
|
|
|
build-scheme.ts:106 Y en los dos bloques de semantics de themes/base.ts; el par
|
|
|
|
|
|
`#ffffff/#1c1917` es una constante de sistema sin nombre.
|
|
|
|
|
|
- **TB6 · P3 — `#renderSchemeCss` traga errores** (fable_audit E3): un seed que rompe en
|
|
|
|
|
|
el re-derive de modo hace desaparecer el scheme sin log.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 Superficies y elevación — el canal "unificado" que no unificó
|
|
|
|
|
|
|
|
|
|
|
|
- **E1 · P1 — Dos sistemas de elevación conviven.** (a) escala `--shadow-{1..6}` +
|
|
|
|
|
|
aliases semánticos (13 refs `--shadow-subtle` en recipes…) consumida directamente por
|
|
|
|
|
|
la mayoría; (b) canal depth `--depth-{plane}-{shadow|halo|blur|translucency}` —
|
|
|
|
|
|
compuesto solo por ~13 consumidores (popover, dialog, menús, tooltip, card…). §29
|
|
|
|
|
|
declara "canal unificado" — la frase describe la intención, no el estado: la
|
|
|
|
|
|
unificación por atributo (`data-depth`) quedó explícitamente como "opción futura" y el
|
|
|
|
|
|
z-index sigue por libre en cada componente (con su banda `--z-index-overlay-*`, esa sí
|
|
|
|
|
|
guardeada). *(Corrección 2026-07-02: la versión inicial de este informe contaba
|
|
|
|
|
|
"23 box-shadow literales" — la regla R-4.1 implementada demostró que todos llevan
|
|
|
|
|
|
`var()` (patrones ring / tokens de recipe); literales reales = 0. El hallazgo queda
|
|
|
|
|
|
reducido a la partición escala-vs-canal y la adopción parcial del halo.)*
|
|
|
|
|
|
- **E2 · P2 — El halo/frost es opt-in y casi nadie lo pide.** `[data-frost]` no aparece
|
|
|
|
|
|
en los recipes del catálogo (solo demos/tema cristal). Correcto como diseño gated, pero
|
|
|
|
|
|
conviene documentar en el contrato de recipe cuándo un overlay DEBE ofrecerlo.
|
|
|
|
|
|
- **E3 · P3 — Ladder de superficies OK** (default<raised<muted<overlay en ambos modos,
|
|
|
|
|
|
verificado en themes/base.ts) — sin hallazgos.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 Tipografía — diseño de dos anclas correcto, tres fuentes de verdad de facto
|
|
|
|
|
|
|
|
|
|
|
|
El modelo documentado (escala numérica para recipes + named styles para primitivas de
|
|
|
|
|
|
contenido, aliases foundation leyendo del named style) es defendible y está razonado.
|
|
|
|
|
|
Pero:
|
|
|
|
|
|
|
|
|
|
|
|
- **T1 · P1 — Los named styles re-declaran line-heights que la escala ya fija.**
|
|
|
|
|
|
`sizes.xxl.lineHeight = '1.05'` Y `styles.h1.lineHeight = '1.05'` (typography.ts:65,89).
|
|
|
|
|
|
Retocar la escala NO propaga a los headings — dos fuentes para el mismo número, sin
|
|
|
|
|
|
guard que las mantenga iguales. Igual en h2/xl (1.1 vs 1.2 — ya divergen), h3/lg
|
|
|
|
|
|
(1.2 vs 1.35 — divergen). ¿Intencional (los styles ajustan óptica) o deriva? No está
|
|
|
|
|
|
documentado cuál manda.
|
|
|
|
|
|
- **T2 · P1 — Tercer sistema tipográfico en accordion.** El doc del 1:1 (§5) reconoce que
|
|
|
|
|
|
accordion usa una progresión propia `--text-N-size` ("sistema de prosa"). Con words/
|
|
|
|
|
|
palabras/chronos excluidos, hay **tres vocabularios** activos: `--font-size-{t-shirt}`,
|
|
|
|
|
|
`--style-{name}-*`, `--text-{N}-*`. El tercero no aparece en la tabla de naming §6.
|
|
|
|
|
|
- **T3 · P2 — `semanticTracking` es un eje muerto**: 6 claves, todas `'0'`
|
|
|
|
|
|
(typography.ts:221-228). Config + tokens emitidos que no expresan nada. Usarlo (caps
|
|
|
|
|
|
de menús → `--tracking-caps`?) o retirarlo.
|
|
|
|
|
|
- **T4 · P2 — Colisión de namespace `--leading-*`**: `leading` (none/tight/…) y
|
|
|
|
|
|
`semanticLeading` (ui/prose/text/…) emiten al MISMO prefijo desde dos mapas distintos
|
|
|
|
|
|
sin validación de claves disjuntas — un `semanticLeading.normal` pisaría
|
|
|
|
|
|
`--leading-normal` silenciosamente.
|
|
|
|
|
|
- **T5 · P2 — El pendiente del label `lg`** (divergencia 2px entre label segmentado por
|
|
|
|
|
|
`calc` y label por token, §5) sigue abierto — es exactamente el tipo de asimetría que
|
|
|
|
|
|
luego se copia a un componente nuevo.
|
|
|
|
|
|
- **T6 · P3 — 12 `font-size: Npx` crudos** en 8 ficheros; la mayoría en tracks excluidos
|
|
|
|
|
|
(words, timeline) pero `fab`, `field`, `password-field`, `command`, `date/time-range-field`
|
|
|
|
|
|
no son excluidos — el guard tipográfico solo cubre TOKENS de recipe, no la propiedad
|
|
|
|
|
|
CSS directa. Ampliar el guard o anotar.
|
|
|
|
|
|
- **T7 · P3 — Dos escalas comparten camiseta con rangos distintos**: el `Size` canónico
|
|
|
|
|
|
termina en `xxl` (+ `full` de layout) y la tipográfica añade `xxxl`. Documentado, pero
|
|
|
|
|
|
es una fuente recurrente de confusión (¿`--size-xxl-font-size` vs `--font-size-xxl`?)
|
|
|
|
|
|
que el naming no desambigua.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.5 Sizes, espaciado, densidad, scaling
|
|
|
|
|
|
|
|
|
|
|
|
- **S1 · P1 — El bundle `--size-{k}-*` sigue con 0 consumidores** (verificado hoy). Está
|
|
|
|
|
|
"realineado al 1:1" pero nadie lo lee: cada recipe re-declara su mapeo size→fuente/
|
|
|
|
|
|
altura/padding. Es EL síntoma de la falta de plantilla de recipe: la coordinación
|
|
|
|
|
|
existe como datos y se re-implementa a mano 90 veces. Decidir de una vez: consumo real
|
|
|
|
|
|
(refactor grande pero mecánico) o degradarlo a documentación y dejar de emitirlo.
|
|
|
|
|
|
- **S2 · P1 — Idioma de ejes partido al 50%**: `padding-x/-y` (18 tokens) vs
|
|
|
|
|
|
`padding-inline/-block` (18). Mismo concepto, dos gramáticas, cero doctrina en §6.
|
|
|
|
|
|
Con RTL en el roadmap (memoria: "rtl" en reference-grade remaining), los `-x/-y` son
|
|
|
|
|
|
además la mitad equivocada.
|
|
|
|
|
|
- **S3 · P2 — Alturas con tres nombres** (`height-*`, `control-height-*`,
|
|
|
|
|
|
`trigger-height-*`) para el mismo concepto de "altura del control por size".
|
|
|
|
|
|
- **S4 · P3 — 17 paddings/gaps/margins en px crudos** en CSS de componentes (fuera de
|
|
|
|
|
|
tokens); pocos, pero sin guard equivalente al tipográfico.
|
|
|
|
|
|
- **S5 — Densidad y scaling: OK.** La composición `calc(raw × density × scaling)` vive en
|
|
|
|
|
|
el foundation (render-css:2397) y los recipes heredan vía `var(--space-*)` — no
|
|
|
|
|
|
encontré doble-escalado en componentes. El diseño post-§20/§23 es limpio.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.6 Estado e interacción (state-layer)
|
|
|
|
|
|
|
|
|
|
|
|
- **I1 · P1 — Adopción 21/135 + 22 tokens huérfanos.** El state-layer es la decisión
|
|
|
|
|
|
correcta (MD3, theme-adaptive) pero quedó a ~30% del catálogo interactivo; los tokens
|
|
|
|
|
|
`*-bg-hover` sobreviven en recipes (22 declarados, 5 CSS aún los consumen) y la poda +
|
|
|
|
|
|
guard están "diferidos por entanglement". Mientras el guard no exista, cada componente
|
|
|
|
|
|
nuevo puede volver al idiom viejo sin que nada falle.
|
|
|
|
|
|
- **I2 · P1 — `archetypes.css` contradice su propio canon**: `close:hover` usa
|
|
|
|
|
|
opacity-dim `0.85` — el idiom que §38 declaró retirado para trigger ("el dim atenuaba
|
|
|
|
|
|
también el texto; el tinte no"). El archivo que define `--state-*` mantiene el
|
|
|
|
|
|
anti-patrón tres líneas más abajo.
|
|
|
|
|
|
- **I3 · P2 — El selector del highlight de item/option** son 10 selectores × 4 `:not()`
|
|
|
|
|
|
repetidos a mano (archetypes.css:208-217). Funciona, pero es la clase de bloque que
|
|
|
|
|
|
`:is()` reduce a 2 líneas legibles y que hoy nadie se atreve a tocar.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.7 Focus
|
|
|
|
|
|
|
|
|
|
|
|
- **F1 · P1 — Dos mecanismos + una justificación stale.** El ring universal de archetypes
|
|
|
|
|
|
es `box-shadow` "porque layout.css pone `outline: none !important` global" — ese reset
|
|
|
|
|
|
vive en `web/routes/layout.css` (la shell de docs), **no en el framework**: la
|
|
|
|
|
|
arquitectura de a11y del framework quedó condicionada por su demo. En paralelo, §32
|
|
|
|
|
|
migró superficies a `outline` (HCM/overflow-safe) y quedan **5 componentes box-shadow
|
|
|
|
|
|
pendientes de migrar** (inventario reference-grade). Resultado: campos con box-shadow
|
|
|
|
|
|
de dos anillos (correcto y deliberado), superficies con outline, archetype-fallback con
|
|
|
|
|
|
box-shadow, y forced-colors emitiendo outline por encima. El modelo final es razonable —
|
|
|
|
|
|
pero nadie lo describe completo en un solo sitio, y el comentario fundacional es falso
|
|
|
|
|
|
para el framework standalone.
|
|
|
|
|
|
- **F2 · P3 — Buena praxis verificada**: exclusiones de item/option/content del ring
|
|
|
|
|
|
universal bien razonadas; `:where()` discipline del resto de archetypes correcta.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.8 Opacidad
|
|
|
|
|
|
|
|
|
|
|
|
- **O1 · P1 — El token dice 0.4; el canon transversal usa 0.5.** `--opacity-disabled =
|
|
|
|
|
|
0.4` (static.ts:294). `archetypes.css` hardcodea `opacity: 0.5` en trigger/thumb/item
|
|
|
|
|
|
disabled (3 sitios). La §35 proclama la unificación; el fichero más transversal del
|
|
|
|
|
|
sistema no la consume. Fix trivial + guard (mismo patrón que el tipográfico).
|
|
|
|
|
|
*(Corrección 2026-07-02: en CSS de componentes, fuera de `@keyframes`, solo quedan
|
|
|
|
|
|
2 con literal (R-4.2) — el conteo inicial de 20 incluía fades de keyframes, que son
|
|
|
|
|
|
animación, no estado, y quedan exentos.)*
|
|
|
|
|
|
|
|
|
|
|
|
### 3.9 Motion
|
|
|
|
|
|
|
|
|
|
|
|
- **M1 · P1 — 49 keyframes locales en 25 CSS, fuera del canal.** La doctrina (events.css
|
|
|
|
|
|
header + eidos-motion §15) dice que firmas y keyframes viven en
|
|
|
|
|
|
`EidosConfig.motion.{keyframes,signatures}` (themeables, generados). Los 49 locales son
|
|
|
|
|
|
invisibles para temas y para el motor — no se pueden retunear ni desactivar
|
|
|
|
|
|
coherentemente. Habrá funcionales legítimos (spin de spinner), pero no hay
|
|
|
|
|
|
clasificación: ni doctrina "cuándo un keyframe puede ser local" ni inventario.
|
|
|
|
|
|
- **M2 · P2 — El cap de reduced-motion solo frena `animation-duration`** (events.css:24,28)
|
|
|
|
|
|
— las transiciones de estado (`transition:`) bajo `data-motion='reduce'` no se capan en
|
|
|
|
|
|
este archivo (THEMING §13 aún muestra una versión con `transition-duration`). Verificar
|
|
|
|
|
|
dónde vive hoy el cap de transiciones o añadirlo.
|
|
|
|
|
|
- **M3 · P3 — Sano**: uso de `--duration-*`/`--ease-*` amplio (40 CSS), `--ease-default`
|
|
|
|
|
|
domina (100 usos), 5 duraciones crudas restantes con excepciones razonadas en §35.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.10 Touch-target y list-surface
|
|
|
|
|
|
|
|
|
|
|
|
- **TT1 — Touch-target: bien diseñado** (gated por `pointer: coarse`, doble función del
|
|
|
|
|
|
`:not()`, exclusiones razonadas). Pendiente reconocido: fila etiquetada de
|
|
|
|
|
|
checkbox/switch (tarea de Field).
|
|
|
|
|
|
- **LS1 · P2 — list-surface con 4 adoptantes** y select/combobox pendientes — otro canon
|
|
|
|
|
|
a medio desplegar; mientras tanto item/option cae al fallback font-driven.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.11 Documentación del theming
|
|
|
|
|
|
|
|
|
|
|
|
- **D1 · P1 — THEMING.md es referencia + changelog fusionados.** 2.583 líneas; §20–§38
|
|
|
|
|
|
son sprints fechados con incidentes y commits; §5 conserva DOS doctrinas revocadas
|
|
|
|
|
|
narradas inline ("supersede… quedó revocada"); §13–§14 están marcados "superseded" pero
|
|
|
|
|
|
ocupan 100 líneas del doc "current"; §21 es un RFC resuelto conservado. La contradicción
|
|
|
|
|
|
interna es inmediata: **§1 dice "5 layers de CSS", §2 dice "Las 6 capas"** (y enumera 6).
|
|
|
|
|
|
- **D2 · P1 — La "guía de implementación por componente" está fragmentada en 6 documentos**
|
|
|
|
|
|
(THEMING §5/§6 + TSC.md + THEMING_GUIDE + components/README + COMPONENT_COMPLETION_CHECKLIST
|
|
|
|
|
|
+ eidos-motion §13) y NINGUNO enumera los sistemas transversales que una recipe debe
|
|
|
|
|
|
consumir (state-layer, focus, elevación, touch, list-surface, 1:1, motion channel). Esa
|
|
|
|
|
|
lista hoy solo existe implícita en los "Pendiente" de cada sprint. **Es la carencia
|
|
|
|
|
|
exacta que el encargo señalaba.**
|
|
|
|
|
|
- **D3 · P2 — Secciones "estado actual" congeladas** en docs atemporales:
|
|
|
|
|
|
eidos/README "Estado actual (2026-05-17)" (wrappers: lista ~20 vs 137 reales),
|
|
|
|
|
|
components/README "Estado de la migración (2026-05-20)". Contradicen docs/authoring.md.
|
|
|
|
|
|
- **D4 · P3 — §4 tabla de mapeo stale** (ver C3) y §3 "13 slots" vs generador (ver C1).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Lista consolidada de bugs y fixes puntuales (accionables ya)
|
|
|
|
|
|
|
|
|
|
|
|
| # | Sev | Qué | Dónde | Fix |
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
| B1 | P0 | Scheme deja a1/a4..a12 del tema anterior | build-scheme.ts:155 | emitir las 12 alphas o retirar a4+ del contrato |
|
|
|
|
|
|
| B2 | P0 | `temper` cambia el hue de `risk` (amber vs orange del tema) | build-scheme.ts:184 | donor desde el role-map del tema activo |
|
|
|
|
|
|
| B3 | P1 | Alpha compositado contra `#fff`/`#111` en vez del surface real | active-eidos:845 | `resolveToken('--color-surface-default')` |
|
|
|
|
|
|
| B4 | P1 | `opacity: 0.5` ×3 en archetypes vs `--opacity-disabled` 0.4 | archetypes.css:54,113,224 | usar el token + guard |
|
|
|
|
|
|
| B5 | P1 | `close:hover` opacity-dim contra doctrina state-layer | archetypes.css:131 | `background-image: linear-gradient(var(--state-hover)…)` |
|
|
|
|
|
|
| B6 | P1 | "5 layers" vs "6 capas" en el mismo doc | THEMING §1 vs §2 | corregir §1 |
|
|
|
|
|
|
| B7 | P1 | Tabla roles §4 desalineada del código (primary/risk/tertiary) | THEMING §4 | regenerar desde `THEME_BASE_COLOR_ROLES` |
|
|
|
|
|
|
| B8 | P2 | reduce-cap sin `transition-duration` | events.css | añadir o documentar dónde vive |
|
|
|
|
|
|
| B9 | P2 | `#1c1917`/on-solid duplicado en 3 sitios | build-scheme, themes/base ×2 | constante nombrada |
|
|
|
|
|
|
| B10 | P3 | hex sin anotación en natural-time-picker | ntp.css:15-20 | comentario "physically-fixed" o tokenizar ink |
|
|
|
|
|
|
| B11 | P3 | 12 `font-size:` px fuera del guard (fab/field/command…) | varios css | ampliar guard R-2.7 a la propiedad |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. Lo que está bien (y hay que proteger al refactorizar)
|
|
|
|
|
|
|
|
|
|
|
|
- **TSC + pipeline de defensas** — intacto y ejemplar (ya valorado en fable_audit).
|
|
|
|
|
|
- **El foundation de color** — cadena de capas respetada en la práctica; el modelo §25
|
|
|
|
|
|
(paleta rica + roles alias + intents derivados) es superior al de las referencias.
|
|
|
|
|
|
- **Los guards existentes** — cada uno tiene deriva ≈0 en su dominio. La plantilla del
|
|
|
|
|
|
éxito ya existe; solo falta replicarla.
|
|
|
|
|
|
- **`archetypes.css` como pieza** — la disciplina `:where()` con las 3 excepciones
|
|
|
|
|
|
intencionales documentadas es de las mejores prácticas del repo (pese a I2/B4).
|
|
|
|
|
|
- **Densidad × scaling** — composición limpia post-§20/§23, sin doble escalado.
|
|
|
|
|
|
- **Los builders puros** (`build-*` testeados, sin DOM) — la separación matemática/
|
|
|
|
|
|
aplicación es correcta; los bugs TB1–TB3 son de datos, no de arquitectura.
|
|
|
|
|
|
- **Touch-target y focus-exclusions** — decisiones a11y razonadas contra referencias
|
|
|
|
|
|
(React Spectrum, WCAG) y verificadas.
|
|
|
|
|
|
- **eidos:purge** — con resultados medidos honestos.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Valoración del sistema de theming frente a los existentes
|
|
|
|
|
|
|
|
|
|
|
|
> Fundada en la comparativa propia del repo (THEMING_NOTES.md, que es honesta y sigue
|
|
|
|
|
|
> vigente) + los hallazgos verificados de esta auditoría. La pregunta que responde:
|
|
|
|
|
|
> *¿qué vale este sistema de theming puesto al lado de Radix Themes, Panda/Chakra,
|
|
|
|
|
|
> Mantine, Tailwind v4, shadcn y Material 3?*
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 La frase que lo resume
|
|
|
|
|
|
|
|
|
|
|
|
> **Eidos tiene el motor de theming más capaz del campo y la gobernanza de consumo más
|
|
|
|
|
|
> débil respecto a su propia ambición. Radix Themes es exactamente lo contrario: un motor
|
|
|
|
|
|
> modesto con gobernanza perfecta.**
|
|
|
|
|
|
|
|
|
|
|
|
Radix Themes ofrece ~un tercio de los ejes de Eidos (accent + gray + radius + scaling),
|
|
|
|
|
|
pero el 100% de sus componentes consume el 100% de su sistema — no existe "adopción
|
|
|
|
|
|
parcial" porque los componentes y el sistema se diseñaron juntos y no hay vía de escape.
|
|
|
|
|
|
Eidos ofrece el triple de ejes y sus componentes los consumen al 30–85% según el
|
|
|
|
|
|
subsistema. La calidad percibida de un theming no la da el motor sino la **uniformidad
|
|
|
|
|
|
del consumo** — y ahí es donde Eidos pierde hoy contra referencias objetivamente menos
|
|
|
|
|
|
capaces.
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 Dónde gana (capacidades que nadie más tiene, verificadas)
|
|
|
|
|
|
|
|
|
|
|
|
| Capacidad | Eidos | Mejor alternativa del campo |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| **Scope de tokens como contrato validado** (TSC: álgebra + colisión cross-axis + composition) | ✅ único | Panda valida parcialmente en build; Radix/Mantine/shadcn: nada — el bug del custom-property congelado es invisible para todos ellos |
|
|
|
|
|
|
| **Builders runtime semilla→sistema en 6 ejes** (color · type · depth · shape · space · gradient, `applyTheme` atómico) | ✅ único | Material 3 lo hace SOLO para color (dynamic color); Radix Themes ni eso (su panel es token-level) |
|
|
|
|
|
|
| **Color science**: OKLCH wide-gamut dual-stack default-on, APCA on-solid, alpha por compositing-inverse, `temper` de intents | ✅ | Radix ships hex sRGB; M3 usa HCT (comparable en rigor, sin P3 dual-stack en web) |
|
|
|
|
|
|
| **Roles evaluativos del libro** (affirm/fulfill/risk/threat/loss ≠ semáforo Bootstrap) | ✅ único | Todos los demás: success/warning/danger/info |
|
|
|
|
|
|
| **Densidad × scaling ortogonales en runtime** | ✅ | Radix tiene scaling; NINGUNO tiene ambos ejes componiendo |
|
|
|
|
|
|
| **Integración perceptiva** (tokens/motion que reaccionan a `data-event-*` de sema) | ✅ único | No existe el concepto fuera de este framework |
|
|
|
|
|
|
| **Contrato CSS introspectable + themes CSS-only + persistencia versionada** (`getCssContract` / `renderContractCss` / envelope) | ✅ | Panda introspecta en build; Mantine expone el theme object; nadie publica el contrato como CSS vacío para temas externos |
|
|
|
|
|
|
| **a11y del theming**: forced-colors, prefers-contrast, touch-target gated por puntero, reduced-motion cross-modal | ✅ | React Spectrum es el único comparable en touch/contrast; ninguna lib de theming CSS lo integra |
|
|
|
|
|
|
| **Purge con cierre transitivo** (13.6 KB gz para 10 componentes) | ✅ competitivo | Tailwind 10 / Panda 12 — misma liga |
|
|
|
|
|
|
|
|
|
|
|
|
Esto no es marketing propio: cada fila de arriba la contrasté contra el código, no contra
|
|
|
|
|
|
los docs. Como **motor**, el sistema está 2–3 años por delante de lo publicado.
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 Dónde pierde (contra referencias concretas)
|
|
|
|
|
|
|
|
|
|
|
|
1. **Contra Radix Themes — uniformidad de consumo.** Es el hallazgo central de esta
|
|
|
|
|
|
auditoría (§1): en Radix no puede existir un componente con 23 sombras literales o un
|
|
|
|
|
|
hover que ignora el state-layer, porque no hay superficie donde escribirlo. En Eidos
|
|
|
|
|
|
las recipes son CSS plano + un mapa de tokens sin forma: la vía de escape está abierta
|
|
|
|
|
|
en cada fichero.
|
|
|
|
|
|
2. **Contra Panda/Chakra v3 — la recipe tipada.** Panda tiene `defineRecipe` /
|
|
|
|
|
|
`defineSlotRecipe`: la forma de una recipe (ejes, variants, slots, compoundVariants)
|
|
|
|
|
|
es un TIPO, y desviarse no compila. Eidos tiene el TSC (que valida *scope*, mejor que
|
|
|
|
|
|
Panda) pero **no valida la forma**: qué ejes existen, cómo se llaman
|
|
|
|
|
|
(`padding-x` vs `padding-inline`), qué sistemas consume. Es la única dimensión del
|
|
|
|
|
|
theming donde Panda es estructuralmente superior hoy — y es exactamente el hueco que
|
|
|
|
|
|
la Fase A propone cerrar.
|
|
|
|
|
|
3. **Contra Tailwind/shadcn — coste de entrada.** 7 capas de tokens, 2.583 líneas de doc
|
|
|
|
|
|
de referencia con secciones revocadas inline, y "cómo hago un componente" repartido en
|
|
|
|
|
|
6 documentos. La comparativa del propio repo lo admite ("Eidos NO es más simple") —
|
|
|
|
|
|
correcto, pero la deriva documental (§3.11) lo empeora más allá de lo necesario.
|
|
|
|
|
|
4. **Contra todos — madurez del catálogo de temas.** 2 temas shipped (base + cristal) y
|
|
|
|
|
|
un builder con 3 bugs P0/P1 en el camino crítico del retint (TB1–TB3). El sistema
|
|
|
|
|
|
*permite* más que ninguno; lo *demostrado* en producción es menos que cualquiera de
|
|
|
|
|
|
las referencias.
|
|
|
|
|
|
5. **Rendimiento del builder en vivo** — `apply()` regenera el foundation entero por
|
|
|
|
|
|
cambio de eje (fable_audit E1); ninguna referencia con theme runtime (Mantine,
|
|
|
|
|
|
Chakra) paga ese coste por knob.
|
|
|
|
|
|
|
|
|
|
|
|
### 6.4 Puntuaciones (escala honesta)
|
|
|
|
|
|
|
|
|
|
|
|
| Dimensión | Nota | Ancla |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| Arquitectura de foundation (capas, roles, escalas, naming de color) | **9** | Mejor que Radix (3 capas) y Panda; C1–C4 son periferia |
|
|
|
|
|
|
| Capacidades del motor (TSC + builders 6 ejes + color science) | **9.5** | Nadie tiene el conjunto |
|
|
|
|
|
|
| Implementación del motor | **7.5** | TB1–TB3 (retint incompleto/incoherente) + E1 (perf) descuentan |
|
|
|
|
|
|
| **Disciplina de consumo del catálogo** | **5** | 21/135 state-layer · 3 sistemas de elevación · 0 consumidores de `--size-*` · 49 keyframes fuera del canal |
|
|
|
|
|
|
| Ergonomía para el autor de componentes | **5** | Sin plantilla ni recipe tipada; el contrato vive en 6 docs + "pendientes" |
|
|
|
|
|
|
| Ergonomía para el autor de temas | **7.5** | Contract introspection + CSS-only + envelope excelentes; solo 2 temas lo validan |
|
|
|
|
|
|
| a11y del theming | **8** | forced-colors/contrast/touch/reduced-motion; F1 y B4 descuentan |
|
|
|
|
|
|
| Documentación como guía | **4** | Referencia=changelog; contradicciones internas verificadas |
|
|
|
|
|
|
| **Global** | **7** | Motor de referencia mundial · gobernanza por debajo de su propia doctrina |
|
|
|
|
|
|
|
|
|
|
|
|
La nota global no es la media: está dominada por la disciplina de consumo, porque es lo
|
|
|
|
|
|
que el usuario final del framework *ve*. Un tema aplicado sobre componentes que bypassean
|
|
|
|
|
|
los canales (sombras literales, hovers bespoke, keyframes locales) produce un retint
|
|
|
|
|
|
incompleto — y eso convierte la ventaja del motor en una promesa, no en un resultado.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. ¿Basta con el contrato por componente? (respuesta directa)
|
|
|
|
|
|
|
|
|
|
|
|
**Sí, el contrato de recipe es la pieza clave — pero solo si se entiende como tres cosas
|
|
|
|
|
|
a la vez, y hay una cuarta parte del problema que el contrato no toca.**
|
|
|
|
|
|
|
|
|
|
|
|
1. **El contrato como documento** (la "guía conceptual" que falta) es necesario pero, por
|
|
|
|
|
|
sí solo, insuficiente. La evidencia está en este mismo repo: los cánones que viven en
|
|
|
|
|
|
prosa derivan al 30–85% (state-layer, depth, opacidad); los que tienen guard mecánico
|
|
|
|
|
|
derivan ~0% (tipografía-literal, z crudo, variants, TSC). Un `RECIPE_CONTRACT.md` sin
|
|
|
|
|
|
enforcement sería el decimoquinto sistema canónico parcialmente adoptado.
|
|
|
|
|
|
2. **El contrato como guard** (`component-audit` R-4.x — Fase A2) es lo que congela la
|
|
|
|
|
|
deriva futura: cada componente nuevo nace obligado. Es barato (el script y los
|
|
|
|
|
|
precedentes de regla existen) y es el paso de mayor palanca.
|
|
|
|
|
|
3. **El contrato como tipo** es el destino natural y la versión superior: una
|
|
|
|
|
|
`defineRecipe` de eidos con ejes canónicos tipados (dimensiones con nombres fijos,
|
|
|
|
|
|
sistemas transversales como flags declarativos, variants contra `EIDOS_VARIANTS`),
|
|
|
|
|
|
igual que Panda — y coherente con la doctrina ya escrita del propio framework
|
|
|
|
|
|
("types over lint": el precedente de `semaSelector` frente al eidos-lint). Las recipes
|
|
|
|
|
|
CSS planas son hoy **la última gran superficie no tipada** del sistema. No hace falta
|
|
|
|
|
|
empezar aquí (es la versión cara), pero el contrato-documento y el contrato-guard
|
|
|
|
|
|
deben redactarse de forma que migren limpio a contrato-tipo.
|
|
|
|
|
|
|
|
|
|
|
|
4. **Lo que el contrato NO arregla** — y por eso el plan tiene 5 fases y no 1:
|
|
|
|
|
|
- **La deuda ya emitida**: los 135 CSS existentes hay que backfillearlos (Fase B);
|
|
|
|
|
|
si no, el guard nace en rojo o con una allowlist eterna que lo desactiva de facto.
|
|
|
|
|
|
- **Los bugs del motor** (TB1–TB3: alphas parciales, donors que ignoran el tema,
|
|
|
|
|
|
background hardcodeado): son del builder, no de las recipes — ningún contrato por
|
|
|
|
|
|
componente los toca (Fase C).
|
|
|
|
|
|
- **Las decisiones de inventario** (alphas huérfanas, slots muertos, bundle
|
|
|
|
|
|
`--size-*`, `semanticTracking` cero): son decisiones de sistema que alguien tiene
|
|
|
|
|
|
que tomar una vez (Fase D). El contrato las *consume*, no las *decide*.
|
|
|
|
|
|
- **La documentación** (Fase E): el contrato debe ser LA página que un implementador
|
|
|
|
|
|
lee, y eso exige que THEMING.md deje de ser un changelog donde esa página no cabe.
|
|
|
|
|
|
|
|
|
|
|
|
En una línea: **el contrato por componente es la llave, pero la cerradura tiene cuatro
|
|
|
|
|
|
piezas — contrato ejecutable + backfill + motor corregido + inventario decidido.** El
|
|
|
|
|
|
orden correcto es A (contrato+guard en WARN) → B (backfill) → guard a FAIL, con C y D en
|
|
|
|
|
|
paralelo, porque redactar el contrato ANTES del backfill es lo que da al backfill su
|
|
|
|
|
|
lista de tareas exacta.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. Plan de acción
|
|
|
|
|
|
|
|
|
|
|
|
### Fase A — El contrato de recipe (la "guía" que falta, como regla mecánica)
|
|
|
|
|
|
|
|
|
|
|
|
**A1.** Redactar `RECIPE_CONTRACT.md` (1 página, E2): el esqueleto canónico de una recipe
|
|
|
|
|
|
— ejes y nombres de tokens dimensionales (**canon: `padding-inline/-block`**,
|
|
|
|
|
|
`control-height-*`), qué sistemas transversales consume obligatoriamente (state-layer
|
|
|
|
|
|
para hover neutro · focus según su arquetipo (campo=2 anillos / superficie=outline) ·
|
|
|
|
|
|
elevación SOLO vía `--shadow-*`/`--depth-*` · tipografía 1:1 · `--opacity-disabled` ·
|
|
|
|
|
|
touch-target implícito por archetype · list-surface si es lista) y las excepciones
|
|
|
|
|
|
válidas con anotación.
|
|
|
|
|
|
|
|
|
|
|
|
**A2.** Convertirlo en reglas `R-4.x` de `component-audit`: (a) ningún `box-shadow`
|
|
|
|
|
|
literal; (b) ningún `opacity` disabled literal; (c) ningún `:hover` con `background`
|
|
|
|
|
|
que no sea state-layer o palette-swap declarado; (d) ejes físicos `-x/-y` prohibidos en
|
|
|
|
|
|
tokens nuevos; (e) `@keyframes` local exige comentario `/* functional: … */`. WARN
|
|
|
|
|
|
primero, FAIL tras la Fase B.
|
|
|
|
|
|
|
|
|
|
|
|
### Fase B — Cerrar las adopciones a medias (mecánico, por barridos)
|
|
|
|
|
|
|
|
|
|
|
|
| | Acción | Cierra |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| B-1 | Completar state-layer (tabs, color-picker + resto neutro), podar los 22 `*-bg-hover` huérfanos de recipes, fix `close:hover` y opacities de archetypes | I1, I2, B4, B5 |
|
|
|
|
|
|
| B-2 | Decidir la unificación escala-vs-canal depth (adopción `data-depth` o doctrina de convivencia explícita) — los box-shadow ya están tokenizados (R-4.1 = 0) | E1 |
|
|
|
|
|
|
| B-3 | Terminar la migración focus box-shadow→outline en los 5 pendientes + un doc-mapa único del modelo de focus (campos/superficies/fallback/HCM) + corregir el comentario stale de archetypes | F1 |
|
|
|
|
|
|
| B-4 | list-surface a select/combobox (pendiente declarado) | LS1 |
|
|
|
|
|
|
| B-5 | Codemod `padding-x/y → padding-inline/block` + unificar naming de alturas | S2, S3 |
|
|
|
|
|
|
| B-6 | Inventariar los 49 keyframes: mover firmas al config, anotar los funcionales | M1 |
|
|
|
|
|
|
|
|
|
|
|
|
### Fase C — Theme builder correcto
|
|
|
|
|
|
|
|
|
|
|
|
C1: B1+B2+B3 (alphas completas, donors del tema, background real) + tests de "retint
|
|
|
|
|
|
completo" (aplicar scheme → grep de tokens del color viejo en el CSS resultante = 0).
|
|
|
|
|
|
C2: memoización de `renderStaticCss` + `apply(scope)` (fable_audit E1). C3: log en
|
|
|
|
|
|
`#renderSchemeCss`.
|
|
|
|
|
|
|
|
|
|
|
|
### Fase D — Decisiones de inventario (una sesión de decisiones, no de código)
|
|
|
|
|
|
|
|
|
|
|
|
- Alpha scales: ¿consumir (focus rings, scrims, hovers → sustituyendo color-mix ad-hoc)
|
|
|
|
|
|
o recortar a a1–a3? (C4/TB1 se resuelven juntos).
|
|
|
|
|
|
- Slots `text-strong`/`separator`/`border-hover`/`hover`/`active`: consumir o dejar de
|
|
|
|
|
|
emitir (C2).
|
|
|
|
|
|
- Bundle `--size-*`: refactor de consumo o degradar a doc y retirar (S1) — lleva un año
|
|
|
|
|
|
en el limbo; cualquiera de las dos opciones es mejor que el limbo.
|
|
|
|
|
|
- `semanticTracking` todo-cero: usar o retirar (T3).
|
|
|
|
|
|
- `tertiary`: sigue "RESERVED" sin consumidor — fijar fecha de revisión.
|
|
|
|
|
|
|
|
|
|
|
|
### Fase E — Documentación
|
|
|
|
|
|
|
|
|
|
|
|
E1: Partir THEMING.md — `THEMING.md` (referencia atemporal ≤600 líneas: mental model,
|
|
|
|
|
|
capas, roles+slots generados desde código, sizes, naming con los ejes dimensionales,
|
|
|
|
|
|
punteros a TSC/GUIDE) + `THEMING_CHANGELOG.md` (§20–§38 tal cual). E2: retirar §13/§14/§21
|
|
|
|
|
|
al changelog. E3: borrar las secciones "estado actual" congeladas o regenerarlas por
|
|
|
|
|
|
script. E4: tabla única de slots/roles/mapeos autogenerada (`generate:contracts-docs` ya
|
|
|
|
|
|
existe como precedente).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. Nota final
|
|
|
|
|
|
|
|
|
|
|
|
La frase del encargo — *"se ha ido desviando con cada modificación"* — es correcta como
|
|
|
|
|
|
descripción del **proceso**, pero la desviación no es entropía aleatoria: es una serie de
|
|
|
|
|
|
mejoras genuinas (state-layer, depth, 1:1, opacidad, touch) que se quedaron sin las dos
|
|
|
|
|
|
piezas que el propio repo demostró que funcionan: **el guard mecánico y el cierre del
|
|
|
|
|
|
rollout**. La materia prima para el "esquema coherente" ya existe; lo que falta es
|
|
|
|
|
|
promover el contrato de recipe a regla ejecutable (Fase A) y pagar la deuda de adopción
|
|
|
|
|
|
(Fase B). Con eso, eidos pasa de "federación de sprints" a el sistema que sus propios
|
|
|
|
|
|
docs describen.
|
|
|
|
|
|
|
|
|
|
|
|
*Cifras verificadas por grep a 2026-07-01 sobre `alpha-0.1-sec-dom` (y recalibradas el
|
|
|
|
|
|
2026-07-02 con las reglas R-4.x, que parsean declaraciones completas en vez de líneas):
|
|
|
|
|
|
135 CSS de componentes; state-layer 21; `*-bg-hover` 22 declarados / 5 consumidores;
|
|
|
|
|
|
box-shadow literales **0** (los 23 matches iniciales llevaban `var()`); keyframes
|
|
|
|
|
|
locales 49 en 25 ficheros — 23 componentes sin anotación funcional (R-4.5); `--size-*`
|
|
|
|
|
|
0 consumidores; alphas directas 0; slots huérfanos text-strong/separator/border-hover
|
|
|
|
|
|
0 usos; padding-x/y 18 vs inline/block 18 (2 componentes, R-4.4); opacity literales
|
|
|
|
|
|
fuera de `@keyframes` 2 componentes (R-4.2) + 3 en archetypes.css; hex crudos 6
|
|
|
|
|
|
(1 fichero); `var(--scale-*)`/`--primitive-*` directos 2 componentes (R-4.6);
|
|
|
|
|
|
duraciones crudas 5.*
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Addendum 2026-07-02 — Fase A ejecutada + desfase del tooling
|
|
|
|
|
|
|
|
|
|
|
|
1. **El Recipe Contract existe**: [`src/uix/eidos/RECIPE_CONTRACT.md`](src/uix/eidos/RECIPE_CONTRACT.md)
|
|
|
|
|
|
(canon E2 — esqueleto de recipe, sistemas transversales obligatorios, excepciones
|
|
|
|
|
|
anotadas) + sección **C4 / reglas R-4.1–R-4.6** en `COMPONENT_COMPLETION_CHECKLIST.md`
|
|
|
|
|
|
y `scripts/component-audit.ts` (severidad WARN; graduarán a error tras el backfill).
|
|
|
|
|
|
Referenciado desde THEMING §8, eidos/README y docs/README (mapa E2). Tracks
|
|
|
|
|
|
`words`/`palabras`/`chronos` excluidos.
|
|
|
|
|
|
|
|
|
|
|
|
2. **Línea base R-4.x** (primer run): R-4.1 = 0 · R-4.2 = 2 · R-4.3 = 0 · R-4.4 = 2 ·
|
|
|
|
|
|
R-4.5 = **23** · R-4.6 = 2. El grueso del backfill de Fase B es el canal de motion
|
|
|
|
|
|
(keyframes sin clasificar), no la elevación ni los hovers — mejor de lo estimado.
|
|
|
|
|
|
|
|
|
|
|
|
3. **Backfill Fase B (mecánico) ejecutado — 2026-07-02.** R-4.1/4.2/4.3/4.4/4.6 = **0**:
|
|
|
|
|
|
- R-4.4: `padding-x/y-*` → `padding-inline/block-*` en popover + tooltip
|
|
|
|
|
|
(recipes/base.ts + popover.css + tooltip.css + natural-time-picker.css + regen;
|
|
|
|
|
|
diff del generated = exactamente los 14 tokens). **Verificado en navegador**:
|
|
|
|
|
|
popover viewport `padding: 12px`, tooltip `6px 10px`, token viejo vacío, 0 errores
|
|
|
|
|
|
de consola.
|
|
|
|
|
|
- R-4.2: scroll-frames `0.5` → `var(--opacity-50)`, spinner ring-track `0.6` →
|
|
|
|
|
|
`var(--opacity-60)` (value-preserving, la escala numérica dual de §35 existía para
|
|
|
|
|
|
esto).
|
|
|
|
|
|
- R-4.6: menu-dial scrim → `var(--color-neutral-text-strong)` (primer consumidor del
|
|
|
|
|
|
slot huérfano C2, value-preserving); media-player amber anotado
|
|
|
|
|
|
`/* literal: theme-stable accent over arbitrary video */`.
|
|
|
|
|
|
- R-4.5: los 13 keyframes de **maquinaria continua** (spinner ×3, progress ×3,
|
|
|
|
|
|
shimmer ×2, spin ×4, toast-pulse) anotados `/* functional: … */`. Quedan **15
|
|
|
|
|
|
componentes** como backlog real de migración al canal de motion — entradas/salidas
|
|
|
|
|
|
de overlays (link-preview, nav-menu, menús, select, combobox, tabs, tooltip,
|
|
|
|
|
|
collapsible, card, onion/menu-dial, clipboard-pop, timeline-reveal) y la reacción a
|
|
|
|
|
|
`data-event-*` de metrics-flash. Migrarlos exige verificación visual por componente
|
|
|
|
|
|
(presets `data-animation-style` / firmas en config) — deliberadamente NO se hizo en
|
|
|
|
|
|
esta pasada mecánica.
|
|
|
|
|
|
- Tests eidos: los 15 fallos existentes son WIP paralelo pre-existente (palabras,
|
|
|
|
|
|
natural-time-picker, calendar view-switch, paleta 31→33) — ninguno menciona los
|
|
|
|
|
|
tokens renombrados; `recipe-css-contract` pasa limpio sobre popover/tooltip.
|
|
|
|
|
|
|
|
|
|
|
|
3.ter **Migración R-4.5 — segunda tanda (2026-07-02): 7/15.** Tres patrones más,
|
|
|
|
|
|
todos verificados en navegador:
|
|
|
|
|
|
- **tooltip** (máquina tri-estado `delayed-open`/`instant-open`): tras la
|
|
|
|
|
|
objeción del usuario ("¿sale fuera del canon?") la solución intermedia
|
|
|
|
|
|
(keyframes del canal sin preset) se sustituyó por la **plenamente canónica**:
|
|
|
|
|
|
el generador gana `PRESET_STATE_ALIASES` (render-css.ts) — `delayed-open` es
|
|
|
|
|
|
alias de apertura del trigger de presets (selector propio, bySide y reglas
|
|
|
|
|
|
reduce incluidos), e `instant-open` deliberadamente NO lo es (su semántica ES
|
|
|
|
|
|
"aparecer sin entrada"). Tooltip estampa `motion='scale-fade'` como cualquier
|
|
|
|
|
|
overlay (preset conmutables + política reduce del preset) conservando sus
|
|
|
|
|
|
duraciones vía hooks. Verificado: `delayed-open → scale-in, fade-in` 0.18s;
|
|
|
|
|
|
`instant-open → none`; cero keyframes locales; generated emite los alias.
|
|
|
|
|
|
El alias sirve a cualquier futura máquina soma con vocabulario de apertura
|
|
|
|
|
|
más rico.
|
|
|
|
|
|
- **link-preview**: el caso de libro — sus 8 keyframes por-lado eran la
|
|
|
|
|
|
re-implementación manual del `bySide`; ahora preset `slide-fade`. Verificado
|
|
|
|
|
|
con `side='top' → slide-from-bottom` (la side-awareness que costaba 8 keyframes).
|
|
|
|
|
|
- **collapsible** (momento-EVENTO, no estado): sus keyframes reaccionaban a
|
|
|
|
|
|
`data-event='expand/collapse'` de sema → migrados a **`BUILTIN_SIGNATURES`**
|
|
|
|
|
|
(firma genérica por verbo emerge.expand/collapse, como present/dismiss) +
|
|
|
|
|
|
keyframes `expand-reveal`/`collapse-conceal` en el canal. Beneficia a CUALQUIER
|
|
|
|
|
|
emisor futuro de esos verbos. Verificado en pleno hold: `expand-reveal` 0.24s /
|
|
|
|
|
|
`collapse-conceal` forwards.
|
|
|
|
|
|
- Fix de la regla R-4.5: el scanner ahora blanquea comentarios antes de buscar
|
|
|
|
|
|
`@keyframes` (un comentario con la palabra daba falso positivo); el lookback de
|
|
|
|
|
|
la anotación sigue sobre el texto original.
|
|
|
|
|
|
**Backlog restante: 8** — navigation-menu (≈shared-axis-x), tabs (content
|
|
|
|
|
|
fade/slide ×3), card (card-emerge≈scale-fade/present), clipboard-pop, metrics-flash
|
|
|
|
|
|
(reacción a data-event → candidata a signature per-intent), timeline-reveal,
|
|
|
|
|
|
onion-menu y menu-dial (radial/backdrop — preset propio o excepción razonada).
|
|
|
|
|
|
|
|
|
|
|
|
3.bis **Migración R-4.5 en curso (2026-07-02) — 4/15 hechos, patrón validado.**
|
|
|
|
|
|
`dropdown-menu` · `context-menu` · `select` · `combobox` migrados al preset
|
|
|
|
|
|
canónico `slide-fade`: prop `motion` (default `'slide-fade'`, `'none'` opt-out)
|
|
|
|
|
|
en el Content/SubContent eidos → `data-animation-style`; keyframes locales
|
|
|
|
|
|
retirados del recipe. **Los hooks `--motion-duration-enter/-exit` del preset
|
|
|
|
|
|
permiten migración value-preserving**: select conserva sus
|
|
|
|
|
|
`--select-open/close-duration` alimentándolos al hook (verificado en navegador:
|
|
|
|
|
|
0.18s exactos); combobox ídem con `--duration-normal`. Los 4 verificados en
|
|
|
|
|
|
navegador (attr + `animationName: slide-from-top, scale-in, fade-in` side-aware +
|
|
|
|
|
|
0 errores de consola) y PASS en el audit. Ganancia colateral: los paneles ganan
|
|
|
|
|
|
exit animation + política reduced-motion que sus keyframes locales no tenían.
|
|
|
|
|
|
**Backlog restante: 11** (tooltip, link-preview, navigation-menu, tabs,
|
|
|
|
|
|
collapsible→preset `collapse`, card, clipboard, metrics, timeline, onion-menu,
|
|
|
|
|
|
menu-dial). **Hallazgo pre-existente** (no regresión, detectado al verificar):
|
|
|
|
|
|
el stagger de items de menú (`[data-stagger][data-state='open'] > [data-animation-style]`)
|
|
|
|
|
|
exige hijo DIRECTO — los items dentro de `[data-*-group]` nunca matchean; el
|
|
|
|
|
|
cascade solo anima items sin agrupar. Candidato a fix en el generador de
|
|
|
|
|
|
`renderMotionBlocks` (selector descendant o variante group-aware).
|
|
|
|
|
|
|
|
|
|
|
|
3.quater **Migración R-4.5 — tercera tanda (2026-07-02): 10/15.** Tres casos de
|
|
|
|
|
|
MONTAJE, cada uno con su mecanismo correcto:
|
|
|
|
|
|
- **clipboard** (bubble "Copied!"): entrada de contenido pura →
|
|
|
|
|
|
**`motionAttrs('scale-fade')`** en el wrapper del Indicator (el mecanismo
|
|
|
|
|
|
canónico de mount). Hooks preservan el pop original (`--motion-scale-enter:
|
|
|
|
|
|
0.85` + `fast`). Verificado con sonda MutationObserver (el bubble vive
|
|
|
|
|
|
`timeout` ms y el click sintético no da user-activation a la Clipboard API —
|
|
|
|
|
|
hizo falta click CDP real): `scale-in, fade-in` a 0.12s, scale 0.85.
|
|
|
|
|
|
- **card** (emerge de montaje): NO puede usar `motionAttrs` — la card tiene
|
|
|
|
|
|
`data-state` SEMÁNTICO (máquina selected) que el `data-state='open'` constante
|
|
|
|
|
|
pisaría. **Patrón materiales**: la regla del recipe consume los keyframes
|
|
|
|
|
|
registrados (`slide-from-bottom` + `fade-in`) con sus tokens; el token de
|
|
|
|
|
|
distancia alimenta `--motion-distance-md` scoped. Verificado por inyección:
|
|
|
|
|
|
0.32s (slow preservado), 8px.
|
|
|
|
|
|
- **timeline** (reveal de live-feed): el trigger es CONDICIONAL al flag
|
|
|
|
|
|
`data-live` del ROOT — un stamp incondicional animaría timelines no-live.
|
|
|
|
|
|
Patrón materiales con la regla container-gated. Verificado por inyección en
|
|
|
|
|
|
su ruta (recipes code-split: el CSS solo carga donde el componente monta):
|
|
|
|
|
|
0.24s, distancia 12px del token vía override scoped.
|
|
|
|
|
|
Doctrina emergente de la tanda: **preset stamp cuando el elemento no tiene
|
|
|
|
|
|
data-state semántico ni trigger condicional; patrón materiales (keyframes
|
|
|
|
|
|
registrados + razón documentada) cuando lo tiene**. Hallazgo colateral
|
|
|
|
|
|
preexistente (chip creado): el demo de card anida `<button>` en `<button>`
|
|
|
|
|
|
(Card interactiva + Buttons hijos) — `node_invalid_placement_ssr` en consola.
|
|
|
|
|
|
**Backlog restante: 5** — navigation-menu, tabs, metrics, onion-menu, menu-dial.
|
|
|
|
|
|
|
|
|
|
|
|
3.quinquies **Migración R-4.5 COMPLETADA (2026-07-02): 15/15 — R-4.5 graduada a
|
|
|
|
|
|
`error`.** Cuarta tanda (5 componentes), verificada en navegador por inyección:
|
|
|
|
|
|
- **onion-menu** y **menu-dial**: sus keyframes eran fades puros → `fade-in`
|
|
|
|
|
|
registrado (trivial).
|
|
|
|
|
|
- **tabs**: sus 3 sabores (`data-motion` fade/slide × orientación — eje
|
|
|
|
|
|
per-componente que el trigger genérico no modela) → materials: `fade-in` /
|
|
|
|
|
|
`slide-from-bottom`+`fade-in` / `slide-from-right`+`fade-in` con el nudge de
|
|
|
|
|
|
4px preservado vía `--motion-distance-sm`. Verificado los tres.
|
|
|
|
|
|
- **navigation-menu**: enter → `slide-from-top`+`scale-in`+`fade-in` (panel
|
|
|
|
|
|
CSS-posicionado sin `data-side`); los 4 swaps direccionales → el par
|
|
|
|
|
|
shared-axis M3 **completado en el canal** (`slide-axis-x-in/out-reverse`,
|
|
|
|
|
|
adición genérica: la dirección backward faltaba) con travel de 12px vía
|
|
|
|
|
|
`--space-3`. Verificados los 5 triggers.
|
|
|
|
|
|
- **metrics**: keyframe genérico **`value-flash`** registrado (tinte vía hook
|
|
|
|
|
|
`--motion-flash-tint`); el recipe conserva su graduación por intent y el
|
|
|
|
|
|
trigger (materialización por-componente de la señal, más rica que el pulse
|
|
|
|
|
|
genérico de announce).
|
|
|
|
|
|
**Doctrina final consolidada en RECIPE_CONTRACT §4**: preset stamp / firma de
|
|
|
|
|
|
evento / patrón materiales — los tres modos legítimos de consumir el canal, con
|
|
|
|
|
|
el criterio de cuándo aplica cada uno. Verificación de cierre: audit global
|
|
|
|
|
|
estable (75/50/5, la graduación costó cero), `generated-css` +
|
|
|
|
|
|
`recipe-css-contract` con solo los 3 fallos pre-existentes ajenos, `npm run
|
|
|
|
|
|
check` con la línea base intacta (61 pre-existentes, 0 propios).
|
|
|
|
|
|
|
|
|
|
|
|
3.sexies **Fase C — theme builder corregido (2026-07-02): TB1 + TB2 + TB3 cerrados.**
|
|
|
|
|
|
`buildScheme` reescrito en dos fases (planificar escalas → emitir):
|
|
|
|
|
|
- **TB1**: emite la rampa alpha COMPLETA `a1..a12` por rol (antes solo a2/a3 —
|
|
|
|
|
|
el resto quedaba apuntando al tema anterior tras `applyColorScheme`).
|
|
|
|
|
|
Verificado en vivo en `/temas/color`: el scheme aplicado pasa de 18 a
|
|
|
|
|
|
**108 alphas** (9 roles × 12).
|
|
|
|
|
|
- **TB2**: nueva opción `intentSeeds` — la base del `temper` es el step-9 del
|
|
|
|
|
|
tema ACTIVO por intent (`ActiveEidos.#resolveIntentSeeds`), con la convención
|
|
|
|
|
|
canónica solo como fallback. El knob ya no cambia la familia de hue de `risk`
|
|
|
|
|
|
(naranja del tema vs ámbar de la convención). Test dedicado.
|
|
|
|
|
|
- **TB3**: sin background hardcodeado — las alphas compositan sobre el step 1
|
|
|
|
|
|
del neutral DERIVADO (la superficie real de la página bajo el scheme,
|
|
|
|
|
|
mode-correcta gratis porque los donors son por-modo). `options.background`
|
|
|
|
|
|
queda como pin explícito. Test de no-op contra el neutral derivado.
|
|
|
|
|
|
- Bonus: `options.mode` ahora fuerza también el DONOR (vía el theme resolver) —
|
|
|
|
|
|
antes solo tocaba el background (gap doc↔impl preexistente); y el demo
|
|
|
|
|
|
`/temas/color` dejó de pasar el `#fff/#111` heredado (dogfooding del fix).
|
|
|
|
|
|
Verificación: build-scheme 10/10 + resolve-token 16/16; suite eidos con los
|
|
|
|
|
|
mismos 15 fallos pre-existentes (cero nuevos); `npm run check` en línea base
|
|
|
|
|
|
(61, 0 propios).
|
|
|
|
|
|
|
|
|
|
|
|
3.septies **Fase D — decisiones de inventario tomadas y ejecutadas (2026-07-02).**
|
|
|
|
|
|
Las 4 decisiones (usuario eligió la recomendada en todas) + `tertiary` resuelta
|
|
|
|
|
|
por los hechos (el builder la deriva vía M3 → se queda, documentada como slot de
|
|
|
|
|
|
jerarquía para apps):
|
|
|
|
|
|
- **semanticTracking BORRADO** (eje todo-cero): typography.ts + config-types +
|
|
|
|
|
|
validación + emisión; su único consumidor (`tabs.trigger-letter-spacing`) pasó
|
|
|
|
|
|
a `--tracking-normal` (value-preserving). 0 tokens `--tracking-{role}` en
|
|
|
|
|
|
generated.
|
|
|
|
|
|
- **Slot `border-hover` RETIRADO** del generador y del contrato (0 consumidores;
|
|
|
|
|
|
los 19 `--{c}-*-border-hover` restantes son tokens bespoke de recipe,
|
|
|
|
|
|
legítimos). Comentario tombstone en `COLOR_ROLE_SLOTS` por si vuelve un caso.
|
|
|
|
|
|
- **`separator` ADOPTADO**: sweep de los divisores de línea (5 tokens de recipe
|
|
|
|
|
|
+ combobox/command css + el componente Separator) de `border-subtle` (step 4,
|
|
|
|
|
|
lavado) al slot canónico (step 6 — tono Radix de divider; Separator standalone
|
|
|
|
|
|
queda idéntico porque 6==border-default). Verificado en navegador (dropdown:
|
|
|
|
|
|
`oklch(0.3485)` = slot) + screenshot.
|
|
|
|
|
|
- **Alphas: contrato completo + workstream de adopción abierto** — sustituir
|
|
|
|
|
|
color-mix ad-hoc por steps alpha donde el semántico coincida (candidatos:
|
|
|
|
|
|
tintas soft, rings, scrims). Sin código en esta pasada más allá del TB1 que ya
|
|
|
|
|
|
las emite bien.
|
|
|
|
|
|
- **Bundle `--size-*`: refactor de consumo APROBADO + patrón establecido con el
|
|
|
|
|
|
pilot `toggle`** — consumir la coordenada canónica donde el valor coincide
|
|
|
|
|
|
(height/font-size, alias idénticos, verificado 36px/16px en navegador) y
|
|
|
|
|
|
conservar las desviaciones deliberadas (px/gap) visibles. THEMING §5
|
|
|
|
|
|
actualizado; el barrido de ~90 recipes es el workstream abierto (por tandas
|
|
|
|
|
|
verificadas).
|
|
|
|
|
|
Verificación: regen limpio, recipe-css-contract con solo los 3 fallos
|
|
|
|
|
|
pre-existentes, guard R-4.x en verde.
|
|
|
|
|
|
|
|
|
|
|
|
4. **Tracks WIP excluidos consistentemente (2026-07-02).** `words` / `palabras` /
|
|
|
|
|
|
`chronos` son componentes SIN TERMINAR (vías de desarrollo paralelas) — pero solo el
|
|
|
|
|
|
guard tipográfico los excluía; el resto de checks de `recipe-css-contract.test.ts` y
|
|
|
|
|
|
el `component-audit` completo los contaban como catálogo roto. Unificado en un set
|
|
|
|
|
|
`WIP_TRACKS` compartido: el audit los saca del catálogo y los veredictos (130
|
|
|
|
|
|
componentes; BROKEN 7→5; auditables a demanda vía `--only words`), y las 3
|
|
|
|
|
|
suites del contrato que fallaban por ellos ahora señalan solo deuda REAL de otros
|
|
|
|
|
|
frentes en curso: `navigation-menu-indicator-h` sin declarar, los hex de
|
|
|
|
|
|
`natural-time-picker` (C5, pendiente de decisión physically-fixed) y los 14 tokens
|
|
|
|
|
|
huérfanos de drawer/color-field/calendar (WIP del view-switch del calendario).
|
|
|
|
|
|
|
|
|
|
|
|
5. **El tooling también había derivado** (hipótesis del encargo extendida a los
|
|
|
|
|
|
scripts, confirmada): `component-audit` auditaba contra la arquitectura anterior —
|
|
|
|
|
|
E-2.2 exigía el import del CSS desde `eidos/index.css` (el patrón actual es el
|
|
|
|
|
|
import del wrapper) y fallaba en 120/132; D-1.2 exigía el template de 6 tabs
|
|
|
|
|
|
(el canónico v2 es de 9) y **fallaba precisamente en las demos ya migradas**;
|
|
|
|
|
|
D-3.1 exigía `somaSnippet` (v2 usa snippet único); `TabsVariant` en el vocabulario
|
|
|
|
|
|
espejo no tenía `segmented`. **Corregido en la misma pasada.** Veredictos antes/después
|
|
|
|
|
|
del fix del tooling: 0 PASS / 117 NEEDS-WORK / 15 BROKEN → **75 / 50 / 7**.
|
|
|
|
|
|
Moraleja para la Fase E: los espejos manuales dentro de scripts
|
|
|
|
|
|
(`SHARED_VARIANT_VOCAB`, templates de demo) necesitan el mismo tratamiento
|
|
|
|
|
|
anti-drift que los docs — generarlos o testearlos contra el canon.
|