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/docs/process/PLAN-theming.md

1230 lines
108 KiB

docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# PLAN — Theming: que TODO componente sea personalizable por un tema (eje `theme-reach`)
docs(theming): el handoff, reescrito para que otra sesión arranque sin arqueología La sesión llega llena. El handoff había crecido por acumulación —cada bloque añadía su sección— y para saber qué hacer había que leerlo entero y deducirlo. Ahora la AGENDA va arriba y el registro histórico abajo, separado por su propio encabezado. Arriba: qué comprobar en los primeros cinco minutos (las dos cifras que deben cuadrar, el dev server antes de medir, leer el veredicto §5); tres opciones con recomendación —revisión adversarial de F2-A, el diseño de `calendar-surface`, o seguir la cola por alcance, con la cola ya recalculada—; el protocolo en diez pasos; y **lo que este bloque enseñó**, que es lo que evita repetir el día: el veredicto orienta pero la medición decide (tres de ocho tenían un error de lectura) · un token que no mueve nada miente, y lo que procede es retirar la declaración muerta · un alias puro se borra, no se renombra · lo que la capa posee el consumidor no lo acuña · y la sonda miente de tres maneras distintas, así que ante un diff inesperado se corre DOS VECES sobre el mismo código antes de sospechar del cambio. El plan §8 gana la entrada de la sesión con las cifras y los commits, y su cabecera de estado pasa a 43 %. Estado que hereda la sesión siguiente: alcance global 43 % (era 33 % al abrir el eje), 15 componentes con contrato, 49 sin él, 7 al 100 %, gramática con 0 desviadas y guard en `error`. 29 commits, sin pushear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> **Estado (act. 2026-08-21): EN EJECUCIÓN — alcance global 43 %, 15
> componentes con contrato, 49 sin él. Vocabulario CERRADO (codemod + R-5.3 +
> muro de tipos) y bloque F2-A CERRADO. Las D-TH.1…8 de §4 FIRMADAS TODAS el
> 2026-08-20** (más las
docs(theming): el acta — las catorce decisiones que faltaban, firmadas El eje llevaba desde el 19 ejecutando sólo lo que no dependía de una firma. Hoy se presentaron las catorce con recomendación fundada y el autor las firmó en bloque. Quedan escritas donde se ejecutan: §4 del plan fila a fila, y una sección de acta en el handoff con el QUÉ ejecutable de cada una. La que cambió de sentido al presentarla fue D-TH.6. La pregunta no era «normalizar hacia lo documentado» sino una CONTRADICCIÓN entre dos doctrinas firmadas: theming §6.7 r7 dice que el slot de tinta es `fg`; el principio de plataforma del codemod px/py («el token se llama como la propiedad») dice `color`. Se adjudicó `fg`, y con una razón que acota el principio en vez de romperlo: gobierna los ejes DIMENSIONALES, no la pareja `bg`/`fg` — si la gobernara, `bg` tendría que llamarse `background-color`, y nadie lo propone. De la misma lectura salió que el inventario heredado estaba inflado: de las 370 claves «desviadas», 47 son `{rol}-{slot-de-rol}` (`primary-solid-hover`, `palette-hover`) donde COLOR_ROLE_SLOTS pone el modificador detrás POR CONSTRUCCIÓN. Un codemod sobre las 370 las habría roto — `button` entero. El inventario real es 301, y la gramática es POR FAMILIA, no única. Los 22 «falsos amigos» resultaron ser tres cosas distintas: los que se quedan (la familia del sistema `--focus-ring-*`, el color como sustantivo de `aura`, y `stop-color` que ES una parte de gradient-builder), los que el morfo ya resuelve (`partial`, `read`, `failed` son valores declarados de `data-state` y `data-delivery`), y los dos `scrim-color-on-*`, que chocaban con el prefijo canónico `on-` del acento y pasan a `scrim-fg-over-*`. Y una que no era limpieza sino decisión: los hovers neutros NO se renombran, MIGRAN a la capa de estado (§38 + R-4.3). Renombrarlos habría presumido que sobreviven. Con las firmas, la revisión de los 7 componentes ya corregidos contra la gramática nueva: 6 limpios y una corrección (`gradient-builder.checker-color` → `checker-fg`), que entra en el codemod. Que salgan limpios no es suerte — el backfill de este eje ya venía escribiendo `fg`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> tres decisiones hermanas del mismo día: familia calendar = capa compartida,
> mandato Field ejecutable, hover→capa de estado — ver «Firmas del 2026-08-20»
> del CONTINUE). El registro con cifras y commits está en §8, y el punto
> de entrada diario es [`CONTINUE-theming.md`](./CONTINUE-theming.md). Toda
> decisión NUEVA que surja se sigue presentando una por mensaje, con las
> cinco preguntas.
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
>
> **Origen (conversación 2026-08-19)**: al arreglar `navigation-menu` salió que
> el radio de su trigger vivía en un privado clavado a `--radius-default` y que
> el contrato del componente sólo publicaba tres knobs: ningún tema podía decir
> «píldora» sin mover el radio del sistema entero. El autor, textual: **«esto
> ha abierto una duda importante, y es si los componentes creados son
> themables; si no lo son, es un error como framework. Necesitaríamos
> desarrollar un plan para auditar todos los componentes, verificar qué grado
> de personalización soportan y desarrollar una corrección a cada uno de ellos
> para que sean totalmente personalizables.»** Y sobre la ejecución: **«lo
> ejecutará Opus, y Opus falla mucho, por lo que tienes que establecer procesos
> de verificación en todo el plan.»** Este documento obedece a las dos frases:
> §1 es la auditoría MEDIDA (reproducible por script, no por memoria), §5 son
> las fases, y §7 es el protocolo de verificación que cada sesión ejecuta sin
> excepción.
>
> **Kickoff para sesión nueva**: _«Lee `docs/process/PLAN-theming.md` ENTERO y
> después `CONTINUE-theming.md`; si las D-TH de §4 están firmadas, ejecuta el
> siguiente bloque de §5 con el protocolo de §7, un componente por commit; si
> no, PARA y preséntalas una a una.»_
>
> Fuentes de doctrina que este plan cita y NO copia:
> [`docs/theming/reference.md`](../theming/reference.md) §1bis, §3, §5, §6,
> §7, §12, §16 · [`docs/canon/tsc.md`](../canon/tsc.md) ·
> [`docs/canon/recipe-contract.md`](../canon/recipe-contract.md) §1–§5 ·
> [`docs/theming/motion.md`](../theming/motion.md) §"When both write
> `animation` on ONE node" · [`docs/guides/component-guide.md`](../guides/component-guide.md)
> §Build contract · [`src/uix/eidos/components/README.md`](../../src/uix/eidos/components/README.md)
> · el precedente ejecutado y firmado: [`PLAN-sidebar.md`](./PLAN-sidebar.md)
> §3 + F3 (el eje `size` de la fila, 2026-08-19) y `navigation-menu`
> (`ae9e277fa` · `a82794845`, el cromo del trigger en tokens públicos).
---
## 0. Veredicto corto
1. **La doctrina ya lo exige; los guards no lo comprueban.** recipe-contract §1:
«Every recipe declares its knobs in `lib/recipes/base.ts`»; theming §6: el
token público de componente es `--{c}-{slot}`; la TSC decide dónde se emite.
Pero R-1…R-4 comprueban que **no haya literales** (y que lo que haya sea un
`var(`), no que cada knob sea **alcanzable por un tema**. Una receta con
todo en `var(--radius-md)` y en privados `--_{c}-*` pasa `component:audit`
en PASS y la dimensión 10 «Adopción theming» de la ficha en ✓ — y sólo se
puede temar moviendo el planeta. Es un error de framework porque rompe la
promesa que el framework hace por escrito.
2. **Medido, no opinado** (§1, `scripts/theming-census.ts`): de 5.205 knobs
de apariencia en 162 recetas, **1.621 (33 %) pasan por un token público del
componente**; 1.880 atan directo a un primitivo global; 856 a privados;
614 son literales. **62 componentes no tienen una sola entrada en el
contrato**; 59 tienen alcance < 20 %; 6 están al 100 %. `navigation-menu`,
tras el trabajo de hoy, está en 44 %: el eje no se cierra arreglando un
componente.
3. **La corrección es mecánica y ya tiene forma firmada**: la de la fila del
`Sidebar` (D-SB.5/D-SB.6, `PLAN-sidebar.md` §3) y la del trigger del nav
(`ae9e277fa`): tokens públicos por talla que apuntan al bundle
`--size-{k}-*`, nombre RESUELTO por `data-size`, acentos de estado en
`background-color`, y **el default queda idéntico** — sólo cambia quién
puede moverlo. Lo que NO hay que hacer: rediseñar nada por el camino.
4. **Sin guard no hay cierre**: el censo de hoy se repetirá en seis meses si
R-5 no existe. F0 crea el guard en `warn` con la cifra de hoy como suelo; F3
lo gradúa a `error` cuando el backfill termina. Igual que hizo R-4.x el
2026-07-02.
---
## 1. Inventario medido (2026-08-19)
**Instrumento**: `node --import tsx/esm scripts/theming-census.ts` (añadido
con este plan; `--json` para máquina; `--only {c}` por componente). Cuenta, por
receta CSS, cada declaración de APARIENCIA (lista `KNOB_PROPS` en el script:
fondo, tinta, borde, radio, sombra, padding, gap, tipografía, tamaño, opacidad,
fill/stroke, filtros) y la clasifica por el ORIGEN de su valor:
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Clase | Qué es | ¿La alcanza un tema de componente? |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `public` | `var(--{c}-…)` — token del contrato del componente | **Sí** |
| `private` | `var(--_{c}-…)` — nombre interno | No (salvo que el privado DERIVE de un público — el script no lo distingue, cuenta como no) |
| `global` | cualquier otro `var(--…)`: `--space-*`, `--radius-*`, `--color-*`, `--font-size-*`, `--size-*`… | Sólo moviendo el sistema |
| `literal` | sin `var(`: `8px`, `1.25`, `#fff` | No, y además viola R-4/R-2 |
| `system` | sistemas transversales que una receta CONSUME: capa de estado, anillo de foco, planos de depth, motion, bandas z, opacidad, shape, `floating-gap` | Sí, **a nivel de sistema**, por diseño (recipe-contract §2) — fuera del ratio |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Alcance** = `public / (public + private + global + literal)`.
### 1.1 Totales
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Medida | Valor |
| -------------------------------------- | ---------------- |
| Componentes con receta CSS | 162 |
| Knobs de apariencia | 5.205 |
| Por token público del componente | **1.621 (33 %)** |
| Por privado | 856 |
| Directo a primitivo global | 1.880 |
| Literales | 614 |
| Sistema transversal (fuera del ratio) | 234 |
| **Sin ninguna entrada en el contrato** | **62** |
| Alcance < 20 % | 59 |
| Alcance = 100 % | 6 |
| Con eje `data-size` en la receta | 82 |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### 1.2 Por familia (propuesta de familias en D-TH.4 — el autor la firma o la cambia)
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Familia | Comp. | Knobs | Alcance | Sin contrato | Fuera del contrato |
| ----------------------------------- | ----: | ----: | ------: | -----------: | -----------------: |
| Tiempo (chronos · fecha/hora) | 15 | 879 | 9 % | 9 | 774 |
| Campos | 24 | 905 | 38 % | 9 | 526 |
| Controles · marcas | 33 | 867 | 39 % | 9 | 502 |
| Menús · listas · navegación | 24 | 735 | 37 % | 10 | 432 |
| Superficies · overlays · estado | 34 | 807 | 45 % | 10 | 429 |
| WIP (`palabras`) | 1 | 471 | 22 % | 1 | 363 |
| Texto · tipografía | 12 | 238 | 9 % | 10 | 217 |
| Media · chat | 9 | 283 | 61 % | 1 | 104 |
| Layout (primitivos) | 9 | 14 | 93 % | 3 | 1 |
| (`separator`, sin familia asignada) | 1 | 6 | 67 % | 0 | 2 |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### 1.3 Los tres defectos que el censo NO ve y la ejecución sí tiene que mirar
El script es un regex sobre texto: sobre-reporta, nunca infra-reporta. Tres
clases de defecto de theming que un alcance del 100 % no garantiza:
- **Privado que no deriva de un público** (`--_c-radius: var(--radius-default)`):
cuenta como `private`; la corrección es que el privado lea el público o
desaparezca.
- **Velo o acento en el nodo equivocado**: `archetype: 'item'` en un `<li>`
envoltorio (`navigation-menu`, `822b78cf2`), o el shorthand `background`
matando la capa de estado (`Sidebar` F3, `navigation-menu` `ae9e277fa`). No
es un knob: es un nodo. Se detecta midiendo desde el píxel hacia arriba (§7.3).
- **Doble animación al mover un sello** a una superficie con animación propia
(`navigation-menu` `822b78cf2`: `present-rise` + la entrada de receta). Se
detecta con un registro de `animationstart/end`, no con `getComputedStyle`.
### 1.4 Tabla completa por componente
Al final del documento (§9), ordenada por alcance. Se REGENERA con el script
en cada bloque; no se edita a mano.
---
## 2. Contrato objetivo — qué significa «totalmente personalizable» (los cuatro ejes)
Un componente es personalizable cuando un tema puede moverlo **sin tocar el
sistema ni la receta**, en estos ejes:
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Eje | Mecanismo canónico | Cómo se comprueba |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **A · Tokens** (forma, color, espacio, tipografía) | todo knob de apariencia lee `--{c}-{slot}` del contrato (`recipes/base.ts`), nombre RESUELTO por talla/estado; los privados sólo derivan de públicos | censo al 100 % **+** prueba de centinela (§7.4) |
| **B · Talla** | `size` → `data-size` en el wrapper; tokens `{part}-{eje}-{k}` apuntando al bundle `--size-{k}-*` (theming §5; recipe-contract §1) | computed por talla = bundle; `md` idéntico al default anterior |
| **C · Color / variante** | `data-color` / `data-variant` vía paleta (`--{c}-{role}-{slot}`, `--{c}-palette-*`), los 9 roles (theming §4) | cada variante lee tokens, ninguna un `--scale-*` (R-4.6) |
| **D · Estados** | hover/press/selected neutros = la capa del sistema en el nodo CON forma; acentos de estado (`on-bg`…) en `background-color` para que la capa componga | `background-image` presente en hover, `<li>`/envoltorios sin velo, abierto + hover = acento + capa |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Regla de oro del eje**: **el default no cambia.** Cada componente se mide
antes y después en sus estados (reposo · hover · activo/abierto · disabled ·
foco) y tallas, y el diff de computed styles es **cero** salvo donde una
decisión firmada diga lo contrario. Este eje mueve QUIÉN puede cambiar cada
cosa, no QUÉ se ve.
---
## 3. Contrato objetivo — la regla R-5 «alcance de tema»
Nueva regla de `component-audit` (familia R-5), misma mecánica que R-4.x:
- **R-5.1** — Toda declaración de apariencia (`KNOB_PROPS`) en una receta lee
(a) un público `var(--{c}-…)`, (b) un privado `--_{c}-*` cuya definición en
la misma receta lee un público, o (c) un token de sistema transversal (la
lista `SYSTEM` del censo). Otra cosa es fallo.
- **R-5.2** — Todo componente con receta tiene entrada en `recipes/base.ts`.
- **R-5.3** — Los nombres de los tokens siguen recipe-contract §1 (ejes
lógicos, `{part}-{eje}-{k}`) y theming §6 regla 7 (slots de color: `bg, fg,
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
border, on-bg, on-fg, on-border, hover-bg, on-hover-bg`). **Hoy no se
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
cumple** ni en recetas viejas (`tabs`: `trigger-color-*`) ni en las de hoy
(`navigation-menu`: `trigger-bg-active`, `trigger-color-active`) — ver
D-TH.6.
- **Válvula** (recipe-contract §3): `R-5.1 exception: <razón>` en el README
del componente, greppable, UNA por knob o grupo de knobs con la razón (p. ej.
«glifo ∝ diámetro, excepción canónica avatar/marker»). Sin razón escrita no
hay excepción.
docs(theming): el acta — las catorce decisiones que faltaban, firmadas El eje llevaba desde el 19 ejecutando sólo lo que no dependía de una firma. Hoy se presentaron las catorce con recomendación fundada y el autor las firmó en bloque. Quedan escritas donde se ejecutan: §4 del plan fila a fila, y una sección de acta en el handoff con el QUÉ ejecutable de cada una. La que cambió de sentido al presentarla fue D-TH.6. La pregunta no era «normalizar hacia lo documentado» sino una CONTRADICCIÓN entre dos doctrinas firmadas: theming §6.7 r7 dice que el slot de tinta es `fg`; el principio de plataforma del codemod px/py («el token se llama como la propiedad») dice `color`. Se adjudicó `fg`, y con una razón que acota el principio en vez de romperlo: gobierna los ejes DIMENSIONALES, no la pareja `bg`/`fg` — si la gobernara, `bg` tendría que llamarse `background-color`, y nadie lo propone. De la misma lectura salió que el inventario heredado estaba inflado: de las 370 claves «desviadas», 47 son `{rol}-{slot-de-rol}` (`primary-solid-hover`, `palette-hover`) donde COLOR_ROLE_SLOTS pone el modificador detrás POR CONSTRUCCIÓN. Un codemod sobre las 370 las habría roto — `button` entero. El inventario real es 301, y la gramática es POR FAMILIA, no única. Los 22 «falsos amigos» resultaron ser tres cosas distintas: los que se quedan (la familia del sistema `--focus-ring-*`, el color como sustantivo de `aura`, y `stop-color` que ES una parte de gradient-builder), los que el morfo ya resuelve (`partial`, `read`, `failed` son valores declarados de `data-state` y `data-delivery`), y los dos `scrim-color-on-*`, que chocaban con el prefijo canónico `on-` del acento y pasan a `scrim-fg-over-*`. Y una que no era limpieza sino decisión: los hovers neutros NO se renombran, MIGRAN a la capa de estado (§38 + R-4.3). Renombrarlos habría presumido que sobreviven. Con las firmas, la revisión de los 7 componentes ya corregidos contra la gramática nueva: 6 limpios y una corrección (`gradient-builder.checker-color` → `checker-fg`), que entra en el codemod. Que salgan limpios no es suerte — el backfill de este eje ya venía escribiendo `fg`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Severidad**: `warn` desde F0 con el suelo del censo; `error` en F3 (D-TH.7
— firmada con enmienda: **R-5.3 gradúa a `error` directo tras el codemod**,
su deuda muere en el mismo pass; R-5.1/5.2 esperan F3).
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## 4. Decisiones que firma el autor (una por mensaje, con sus cinco preguntas)
docs(theming): el acta — las catorce decisiones que faltaban, firmadas El eje llevaba desde el 19 ejecutando sólo lo que no dependía de una firma. Hoy se presentaron las catorce con recomendación fundada y el autor las firmó en bloque. Quedan escritas donde se ejecutan: §4 del plan fila a fila, y una sección de acta en el handoff con el QUÉ ejecutable de cada una. La que cambió de sentido al presentarla fue D-TH.6. La pregunta no era «normalizar hacia lo documentado» sino una CONTRADICCIÓN entre dos doctrinas firmadas: theming §6.7 r7 dice que el slot de tinta es `fg`; el principio de plataforma del codemod px/py («el token se llama como la propiedad») dice `color`. Se adjudicó `fg`, y con una razón que acota el principio en vez de romperlo: gobierna los ejes DIMENSIONALES, no la pareja `bg`/`fg` — si la gobernara, `bg` tendría que llamarse `background-color`, y nadie lo propone. De la misma lectura salió que el inventario heredado estaba inflado: de las 370 claves «desviadas», 47 son `{rol}-{slot-de-rol}` (`primary-solid-hover`, `palette-hover`) donde COLOR_ROLE_SLOTS pone el modificador detrás POR CONSTRUCCIÓN. Un codemod sobre las 370 las habría roto — `button` entero. El inventario real es 301, y la gramática es POR FAMILIA, no única. Los 22 «falsos amigos» resultaron ser tres cosas distintas: los que se quedan (la familia del sistema `--focus-ring-*`, el color como sustantivo de `aura`, y `stop-color` que ES una parte de gradient-builder), los que el morfo ya resuelve (`partial`, `read`, `failed` son valores declarados de `data-state` y `data-delivery`), y los dos `scrim-color-on-*`, que chocaban con el prefijo canónico `on-` del acento y pasan a `scrim-fg-over-*`. Y una que no era limpieza sino decisión: los hovers neutros NO se renombran, MIGRAN a la capa de estado (§38 + R-4.3). Renombrarlos habría presumido que sobreviven. Con las firmas, la revisión de los 7 componentes ya corregidos contra la gramática nueva: 6 limpios y una corrección (`gradient-builder.checker-color` → `checker-fg`), que entra en el codemod. Que salgan limpios no es suerte — el backfill de este eje ya venía escribiendo `fg`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Id | Decisión | Propuesta | Estado |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **D-TH.1** | ¿R-5 es regla DURA? («todo knob público salvo sistema transversal o excepción firmada») | Sí. Es lo que recipe-contract §1 ya dice sin guard. | **FIRMADA 2026-08-20** tal cual. Ancla: la medición 2026-07-01 (canon sin guard deriva 30-85 %) y el mandato origen del eje. |
| **D-TH.2** | Perímetro de «knob» | La lista `KNOB_PROPS` del script (apariencia; layout fuera). Cambios a la lista = cambio del guard, firmados aquí. | **FIRMADA 2026-08-20** con la opción (b) de la ficha `heading`: la familia tipográfica B5 mide su superficie por `--style-*` como sistema transversal (verdad estructural — la vertebración tipográfica RECHAZA acoplar componentes a la capa semántica; acuñar `--heading-*` = 36 alias de una capa ya pública, la clase que §39 mató). No se acuñan `--{c}-*` para los primitivos tipográficos. |
| **D-TH.3** | Tracks WIP (`palabras`, `words`, `chronos` y su familia de fecha/hora) | En el CENSO siempre; en R-5 `error` sólo cuando salgan de su carril (coherente con R-4.x, que los excluye). | **FIRMADA 2026-08-20** tal cual. |
| **D-TH.4** | Familias y ORDEN del backfill | El de §5 F2: menús/navegación → campos → controles → superficies → texto → media/chat → tiempo (el último por ser el más grande y estar en carril propio). Dentro de cada familia, sin contrato primero, luego por knobs fuera del contrato. | **FIRMADA 2026-08-20** con enmienda: la cola REVISADA del CONTINUE (fichas con veredicto §5 verificado) tiene prioridad sobre el orden de familias — trabajo revisado no se deja enfriar. |
| **D-TH.5** | El default NO cambia en este eje | Sí. Toda mejora visual que aparezca se anota como deuda y se firma aparte (como hoy con `navigation-menu`). | **FIRMADA 2026-08-20** sin matices — es lo que hace verificable el eje (diff de computed = 0 como gate). Única excepción viva: la migración hover→capa de estado, firmada aparte el mismo día (ver «Firmas del 2026-08-20» del CONTINUE), que mueve píxel con captura y diff explicado. |
| **D-TH.6** | Vocabulario de slots (R-5.3): ¿se NORMALIZAN los nombres que ya se desvían (`trigger-color-*` de `tabs`, `*-bg-active`/`*-color-active` del nav de hoy, …) con un codemod value-preserving como el de px/py del 2026-07-06? | Sí, en F1, ANTES del backfill — si no, el backfill escribirá más nombres fuera de vocabulario. Inventario exacto de desviaciones sale en F0 (el censo añade `--names`). | **FIRMADA COMPLETA 2026-08-20**, en dos actos. (1) El slot de tinta es **`fg`**; `color` como slot MUERE (196 claves cola). Razón: el principio de plataforma del 2026-07-06 gobierna los ejes DIMENSIONALES, no la pareja `bg`/`fg` (si gobernara, `bg` sería `background-color`); `color` colisiona con `--color-*`, `data-color` y los 9 roles; `on-fg` compone. (2) Modificador recipe-level DELANTE — regla: **delante lo interactivo, detrás lo dimensional y contextual** (`hover-bg` pero `control-height-md`, `scrim-fg-over-dark`); rol/paleta conserva su gramática propia (detrás, `COLOR_ROLE_SLOTS` — las 47 `{rol}-{slot-de-rol}` son canónicas). Modificador válido ∈ vocabulario universal (+`current`, aria) ∪ valores `data-state`/ejes del MORFO del componente (así `partial` de rating-group y `read/failed` de chat-message validan contra el compilado, igual que la parte). `waveform.played/buffered` = pseudo-partes eidos-only (`played-fg`). Los hovers neutros se EXCLUYEN del codemod: migran a la capa de estado (firma aparte, mismo día). Detalle operativo: CONTINUE §«Mañana». |
| **D-TH.7** | Cuándo gradúa R-5 a `error` | Cuando el censo global dé 100 % o excepción documentada (F3), nunca antes de terminar F2 — un `error` a medio backfill rompe el `component:audit` de todo el parque. | **FIRMADA 2026-08-20** con enmienda: **R-5.3 (gramática de nombres) gradúa a `error` DIRECTO tras el codemod** — su deuda muere en el mismo pass (precedente R-4.4, «sin allowlist»). R-5.1/R-5.2 esperan F3 como propone la fila. |
| **D-TH.8** | ¿Entra la exposición en la demo de temas (`/temas`: un panel que liste los tokens públicos por componente y permita moverlos)? | Sí pero DESPUÉS (F4, opcional): es la prueba viva de A; sin el backfill no hay nada que exponer. | **FIRMADA 2026-08-20** tal cual — el instrumental existe (`listRecipes()` / `getRecipeTokens()`; la ruta `/temas` existe). |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## 5. Fases y gates
Cada fase: **qué leer ANTES** (entero), **qué producir**, **guard**. Ninguna
arranca sin la anterior en verde. Todo bloque de F2 ejecuta el protocolo de §7
**por componente**, sin atajos: es la parte del plan que existe porque «Opus
falla mucho».
### F0 — Instrumento + guard en `warn` (una sesión)
- Leer: theming §1bis, §3, §5, §6, §7, §12, §16 · tsc.md entero ·
recipe-contract §1–§5 · `scripts/component-audit.ts` (cómo se declara una
regla R-x, `readmeException`, severidades) · `src/uix/eidos/recipe-css-contract.test.ts`
(qué ya guarda) · `PLAN-sidebar.md` §3 y F3 · este plan entero.
- Producir: (a) `scripts/theming-census.ts` endurecido: `--names` (inventario
de nombres de token fuera del vocabulario §1/§6.7), detección «privado que
deriva de público» (leer `--_c-x: var(--c-y)` en la misma receta y
reclasificar), salida `--json` estable; (b) **R-5.1/5.2/5.3 en
`component-audit`** con severidad `warn` y la válvula `R-5.x exception:`;
(c) `docs/theming/reference.md` §12 (tabla de tooling) gana la fila del censo;
test(theming): el suelo del alcance, con sus dos muta-pruebas — cierra F0.d `src/uix/eidos/theming-reach-floor.test.ts`. El plan lo encargaba en F0 («un test que fije el suelo: el número SUBE o el test falla») y nunca se escribió, así que hasta hoy **nada impedía que el alcance BAJARA entre sesiones**: una receta podía cambiar un token público por un primitivo y sólo se vería leyendo el censo a mano. Cuatro trinquetes, medidos el 2026-08-22 tras el endurecimiento del censo: alcance global ≥ **56 %** · literales ≤ **564** · knobs sobre global crudo ≤ **1.156** · componentes al 100 % ≥ **14**. Cuando el eje avanza, se suben en el mismo commit que se lo gana. **LAS DOS MUTA-PRUEBAS** (memoria `a-guard-that-inspects-nothing-passes`: un guard sólo vale cuando lo has VISTO rojo). No se dan por supuestas — se ejecutaron: 1. Inyectar UN literal (`border-radius: 7px` en `mark`) → rojo en `maxLiteral` y en la sonda del clasificador. Revertido, verde, árbol idéntico. 2. Cambiar CINCO públicos de `tree-view` por primitivos crudos (92 % → 73 % en ese componente) → rojo en `maxGlobal`. Revertido, vuelve a 92 %. Y de ahí sale una observación que queda escrita en el propio test: **los techos son el detector FINO y el porcentaje el grueso.** Cinco knobs de ~4.700 no mueven un porcentaje redondeado, pero sí un techo — una regresión demasiado pequeña para esconderse de `reachPct` sigue disparando `maxGlobal`. Lo que el test NO asegura, a propósito: el alcance POR COMPONENTE, que ya gobiernan el censo `--only` y las excepciones R-5.x — un número bajo puede ser doctrina bien aplicada (`menu-dial` paga por consumir bien una capa compartida, `link` por heredar la tipografía ambiente). Y `system`, que es temable a nivel de sistema por diseño y queda fuera del ratio. Gates: los 5 tests en verde · suite eidos 439/440 (el único rojo sigue siendo el conocido `skin-media-player`) · prettier sobre el fichero nuevo · docs:check 0 · check limpio. `PLAN §5 F0.d` y la entrada de §13 quedan cerradas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
(d) **HECHO 2026-08-22** — `src/uix/eidos/theming-reach-floor.test.ts`, que
**fija el suelo**: alcance global ≥ el de hoy (33 %) y literales ≤ 614 — el
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
número SUBE o el test falla (muta-prueba: meter un literal en una receta y ver
el rojo antes de dar el test por bueno — memoria `a-guard-that-inspects-nothing-passes`.
Ya pasó una vez HOY: la primera versión del censo no contaba una regla de una
sola línea, y la mutación `padding-inline: 8px` no movió la cifra; se endureció
el regex y la mutación pasó a 6→7→8).
- Guard: `node --import tsx/esm scripts/theming-census.ts` reproduce §1.1 a
la cifra · `npm run component:audit` NO añade ningún `error` (sólo `warn`) ·
`npx vitest run src/uix/eidos` sin nuevos rojos (el rojo conocido es
`skin-media-player`, ajeno) · `npm run check` con cero errores en los
ficheros tocados (§7.6).
### F1 — Doctrina + vocabulario (una sesión)
- Leer: F0 + theming §6 (reglas estrictas) + el codemod del 2026-07-06
(recipe-contract §1, bloque «Normalización ejecutada») como molde.
- Producir: recipe-contract §1 gana la regla R-5 (y §2 una fila «Alcance de
tema»), §4 la tabla de enforcement, §5 el checklist (punto 12: «censo del
componente al 100 % o excepción»); theming §6 referencia cruzada; el
checklist de cierre (`docs/guides/completion-checklist.md`) y el
`COMPONENT_AUDIT_GUIDE` endurecen las dimensiones 5 y 10 («usa tokens» ≠
«es temable»: la ficha exige el % del censo). **D-TH.6**: codemod
value-preserving de los nombres fuera de vocabulario (lista de F0), con
`generated/base.css` regenerado y diff = renombres 1:1, computed idéntico en
navegador (mismo triple muro que px/py).
- Guard: `docs:check` 0 · el diff de `generated/base.css` son SOLO renombres ·
censo idéntico antes/después del codemod (un rename no cambia el alcance) ·
sonda de computed en 3 componentes renombrados = idéntico.
docs(theming): el acta — las catorce decisiones que faltaban, firmadas El eje llevaba desde el 19 ejecutando sólo lo que no dependía de una firma. Hoy se presentaron las catorce con recomendación fundada y el autor las firmó en bloque. Quedan escritas donde se ejecutan: §4 del plan fila a fila, y una sección de acta en el handoff con el QUÉ ejecutable de cada una. La que cambió de sentido al presentarla fue D-TH.6. La pregunta no era «normalizar hacia lo documentado» sino una CONTRADICCIÓN entre dos doctrinas firmadas: theming §6.7 r7 dice que el slot de tinta es `fg`; el principio de plataforma del codemod px/py («el token se llama como la propiedad») dice `color`. Se adjudicó `fg`, y con una razón que acota el principio en vez de romperlo: gobierna los ejes DIMENSIONALES, no la pareja `bg`/`fg` — si la gobernara, `bg` tendría que llamarse `background-color`, y nadie lo propone. De la misma lectura salió que el inventario heredado estaba inflado: de las 370 claves «desviadas», 47 son `{rol}-{slot-de-rol}` (`primary-solid-hover`, `palette-hover`) donde COLOR_ROLE_SLOTS pone el modificador detrás POR CONSTRUCCIÓN. Un codemod sobre las 370 las habría roto — `button` entero. El inventario real es 301, y la gramática es POR FAMILIA, no única. Los 22 «falsos amigos» resultaron ser tres cosas distintas: los que se quedan (la familia del sistema `--focus-ring-*`, el color como sustantivo de `aura`, y `stop-color` que ES una parte de gradient-builder), los que el morfo ya resuelve (`partial`, `read`, `failed` son valores declarados de `data-state` y `data-delivery`), y los dos `scrim-color-on-*`, que chocaban con el prefijo canónico `on-` del acento y pasan a `scrim-fg-over-*`. Y una que no era limpieza sino decisión: los hovers neutros NO se renombran, MIGRAN a la capa de estado (§38 + R-4.3). Renombrarlos habría presumido que sobreviven. Con las firmas, la revisión de los 7 componentes ya corregidos contra la gramática nueva: 6 limpios y una corrección (`gradient-builder.checker-color` → `checker-fg`), que entra en el codemod. Que salgan limpios no es suerte — el backfill de este eje ya venía escribiendo `fg`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Repaso de lo ya ejecutado (2026-08-20)**: firmar una gramática obliga a
releer lo corregido ANTES de ella. Auditados los 7 de §8 contra `fg` +
familias (`scripts/__names-audit7.ts`): 6 limpios, y una sola corrección —
`gradient-builder.checker-color` → `checker-fg` (slot, no sustantivo; 4
consumos en `gradient-builder.css`). Las 4 claves `stop-color-*` del mismo
componente son falso amigo verificado (`stop-color` es la PARTE) y NO se
tocan. La corrección entra DENTRO del codemod. Detalle y tabla:
[`CONTINUE-theming.md`](./CONTINUE-theming.md) §«Primera posición».
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### F2 — Backfill por familias (6–8 bloques de sesión; **un componente = un commit**)
Orden propuesto (D-TH.4). Dentro de cada bloque: sin contrato primero, luego
por knobs fuera del contrato. Cada componente sigue §7 entero.
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Bloque | Familia | Comp. | Knobs fuera | Entran primero (sin contrato) |
| ------ | ----------------------------------------------------- | ----: | ----------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| B1 | Menús · listas · navegación | 24 | 432 | `command`, `listbox`, `table`, `tree-grid`, `tree-view`, `grid-list`, `virtual-list`, `virtual-grid`, `menu-dial`, `feed` — y `navigation-menu` (44 %) hasta el 100 % |
| B2 | Campos | 24 | 526 | `combobox` (1), `textarea`, `css-field`, `mask-field`, `number-field`, `field-langs`, `gradient-builder`, `gradient-picker`, `label` |
| B3 | Controles · marcas | 33 | 502 | `knob`, `avatar`, `kbd`, `link`, `mark`, `highlight`, `clipboard`, `proof-of-human`, `aspect-ratio` |
| B4 | Superficies · overlays · estado | 34 | 429 | `alert-dialog`, `announce`, `carousel`, `link-preview`, `skeleton`, `spinner`, `scroll-frames`, `motion`, `mockup`, `cascade`, `card-group` |
| B5 | Texto · tipografía | 12 | 217 | `heading`, `text`, `s-text`, `display`, `code`, `code-block`, `text-*` (los que no tienen contrato) |
| B6 | Media · chat | 9 | 104 | `skin-media-player` |
| B7 | Tiempo (chronos · fecha/hora) — **si D-TH.3 lo mete** | 15 | 774 | `range-calendar`, `month-grid`, `year-grid`, `date-picker`, `date-range-picker`, `time-picker`, `time-range-picker`, `natural-time-picker`, `picker-shell` |
| B8 | `palabras` — **si D-TH.3 lo mete** | 1 | 363 | — |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Por componente se produce: entrada (o ampliación) en `recipes/base.ts` con los
nombres de §1/§6.7 — dimensionales por talla si el componente tiene `size`
(patrón `Sidebar`: `{part}-{eje}-{k}` + nombre resuelto con `declarations` por
`scope: 'size:{k}'`), colores por rol/slot, acentos de estado; la receta
consume SOLO nombres resueltos (nunca un primitivo crudo, nunca un literal);
privados sólo si derivan de públicos; `generated/base.css` regenerado por el
script (**jamás a mano**, theming §16 F); README de eidos con la tabla de
tokens («Talla y tema», molde: `navigation-menu`); registro en §8.
- Guard por componente: §7 entero, y el censo `--only {c}` al **100 %** (o
`R-5.x exception:` con razón) — si no llega, el commit NO se hace.
- Guard por bloque: **revisión adversarial** (§7.7) antes de dar el bloque por
cerrado: un pase escéptico por componente que intente REFUTAR «el default es
idéntico» y «cada token alcanza» con medición propia, no con la del autor
del commit.
### F3 — R-5 a `error` + cierre (una sesión)
- Producir: severidad `error` en R-5.1/5.2/5.3 (D-TH.7); el test del suelo
pasa a exigir 100 % global (o excepciones listadas); theming §12 y
recipe-contract §4 lo reflejan; `CONTINUE-theming.md` cerrado.
- Guard: `component:audit` PASS en los 162 · censo 100 % · `docs:check` 0.
### F4 — Exposición en la demo de temas (opcional, D-TH.8)
- Producir: en `/temas` (o en cada demo) un panel que lista los públicos del
componente desde `recipes/base.ts` y los mueve en vivo — la prueba viva del
eje A, y el detector de tokens que no alcanzan (mover y que nada cambie).
- Guard: mover cada token del componente en la demo y ver cambiar el computed
(automatizable con el centinela de §7.4).
---
## 6. Deuda transversal que este plan destapa — NO se ejecuta aquí
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Id | Dónde | Qué |
| ---------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **T-TH.1** | `component-audit` dims 5 y 10 | Dan ✓ a «usa tokens»; el criterio se endurece en F1. Las fichas ya escritas (`docs/audit/components/*.md`) quedan con el ✓ viejo: se corrigen al pasar cada componente por F2, no en masa. |
| **T-TH.2** | Vocabulario de slots (theming §6.7) | Existe y no se guarda: `tabs` (`trigger-color-*`), `navigation-menu` (`-bg-active`, `-color-active`) y más lo incumplen. D-TH.6. |
| **T-TH.3** | `menubar` | Misma clase que el nav: cromo de barra en privados («the bar itself styles via foundation privates», `recipes/base.ts`) + `commit-select` en el trigger al DESPLEGAR (otra sesión, sema). |
| **T-TH.4** | `archetype: 'item'` en envoltorios | Hay que censar qué otras partes `<li>`/wrapper lo declaran (memoria `archetype-item-pulls-interactive-styling`: Timeline, Breadcrumb, NavigationMenu ya corregidos). Un guard de morfo («un `item` no envuelve a un `trigger`») es candidato. |
| **T-TH.5** | `--control-height-*` en `:root` (T-6 del sidebar) | La densidad/zoom por SUBÁRBOL no re-deriva; afecta a la promesa de theming «por ámbito». Eje de theming aparte. |
| **T-TH.6** | 25 knobs visuales fuera del morfo (`component-visual-attrs`, eje Affix) | Filas sin firmar `image/skeleton/qr-code/s-text`. El eje C (variantes) depende de ellas. |
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## 7. PROTOCOLO DE VERIFICACIÓN — por componente, sin excepciones
Existe porque el que ejecuta falla, y porque en un solo día (2026-08-19,
`navigation-menu`) cuatro de estos pasos ausentes costaron cuatro commits de
corrección: se dio por arreglado un hover leyendo el nodo equivocado; se
resolvió un doble de animación en la dirección contraria a la doctrina; se
inventó un hover por componente; y se declaró «idéntico» sin captura. Cada paso
tiene un **artefacto** (número, captura, salida de comando) que va al mensaje
de commit. Sin artefacto, el paso no se ha hecho.
### 7.1 Antes de tocar el componente (las cinco preguntas + lectura)
1. Las cinco preguntas del autor, UN componente por mensaje cuando haya
decisión (nombres fuera de vocabulario, una excepción, un default que
parece defecto): qué soluciona · leído todo o presupuesto · qué afecta ·
qué doctrina · qué modificaciones. Si no hay decisión nueva (el componente
sólo necesita tokens con nombres canónicos), se ejecuta sin preguntar y se
reporta.
2. Leer ENTEROS: la receta del componente, su entrada en `recipes/base.ts`,
su README de eidos y de soma, su morfo (¿qué partes llevan arquetipo?), su
ficha en `docs/audit/components/`. Y la doctrina de §0 (fuentes) si la
sesión no la ha leído hoy.
3. `git status` + `git log -1`: ¿hay otra sesión con el árbol sucio?
**Nunca `git stash`** para medir una base (le arranca el trabajo a otro).
### 7.2 Medir ANTES (el suelo del «idéntico»)
4. `node --import tsx/esm scripts/theming-census.ts --only {c}` → guardar la
línea.
5. Sonda Playwright **headless desde la raíz del repo** (la pantalla oculta
congela rAF; `playwright` no resuelve desde el scratchpad) contra la demo
del componente: para cada parte visible y cada talla, computed de los
knobs (`backgroundColor`, `backgroundImage`, `color`, `borderRadius`,
`padding*`, `fontSize`, `lineHeight`, `blockSize`/`inlineSize`,
`boxShadow`, `borderColor`) en reposo · hover · activo/abierto ·
disabled · `:focus-visible`. Muestrear FUERA de callbacks de
`MutationObserver` (miden el instante anterior al flush). Guardar JSON.
6. Captura 2× de la demo en reposo y en hover (sirve de «antes» para la
comparación a ojo).
### 7.3 Editar (la forma firmada — nada más)
7. Tokens con los nombres de recipe-contract §1 / theming §6.7. Por talla si
hay `size`. Acentos de estado en `background-color`. Hover/press/selected
neutros = la capa del sistema (nunca `background:` a mano, nunca
`background-image: none` para «limpiar», nunca una escalera de color
propia). Privados sólo derivando de públicos. Literales: cero.
8. `npm run generate:eidos-css` y `git diff --stat src/uix/eidos/generated/`:
el diff son SOLO los tokens del componente. Si toca otra cosa, parar.
9. Si un envoltorio lleva `archetype: 'item'`/`'option'`/`'trigger'` sin ser
el interactivo, es una DECISIÓN (morfo): se presenta, no se toca de oficio.
### 7.4 Medir DESPUÉS (los tres artefactos)
10. **Idéntico**: repetir la sonda de 7.2 → diff de computed por parte/estado/talla
= **vacío**. Un solo valor distinto = parar y explicar (o es defecto
previo que se firma, o es error del commit).
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
11. **Alcanza**: la **prueba de centinela**, que desde 2026-08-21 es el guard
**R-5.4** (`npm run theming:sentinel -- {c} <url>`, doctrina en
recipe-contract §4) — por cada token público, un valor imposible de
confundir (`1234px`, `rgb(1, 2, 3)`, `0.123`) y algún computed tiene que
seguirlo. Si un token no mueve nada, es un token que miente (fantasma,
nombre equivocado, o una cascada que jamás gana) — o su silencio tiene una
razón MEDIDA escrita en el ledger
(`scripts/theming-sentinel-exceptions.ts`): el guard falla con un muerto
sin adjudicar y avisa de excepciones STALE.
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
12. **Desde el píxel hacia arriba** en los estados de hover: `elementsFromPoint`
en el centro del control y la pila de ancestros con `backgroundImage` /
`backgroundColor` — el velo cae en el nodo CON forma y en ningún
envoltorio (memoria `measure-the-node-the-user-points-at`). Si hay
animación propia y un sello de sema en el mismo nodo, registro de
`animationstart/end` (no `getAnimations` en un solo instante): UNA
entrada por ocurrencia (memoria motion.md §"when both write `animation`").
13. Captura 2× después, al lado de la de antes. Mirarla.
### 7.5 Guards mecánicos (todos, cada componente)
14. `node --import tsx/esm scripts/theming-census.ts --only {c}` → **100 %** o
excepción escrita. Y el global NO baja.
15. `node --import tsx/esm scripts/component-audit.ts --only {c}` → PASS, sin
R-5 nuevos (en `warn` durante F2, pero se listan y se resuelven).
16. `node --import tsx/esm scripts/eidos-lint.ts {c}` → 0 invalid.
17. `npx vitest run src/uix/eidos` → sin rojos nuevos (comparar **por
fichero de test**, no por cifra: el rojo conocido es `skin-media-player`).
18. `npm run rtl:check` 0 · `npm run docs:check` 0 · `npx prettier --check`
en los ficheros tocados (ojo: `autocrlf=true` hace que prettier se queje
del CRLF del checkout — comprobar el contenido normalizado a LF, no el
fichero; git guarda LF).
19. `npm run check`: atribuir errores **por fichero** (`grep ERROR | grep {c}`),
nunca por cifra global; la rama tiene sesiones concurrentes y el total
se mueve solo. Si el log no llega a `COMPLETED`, es `tmp/lexical/`:
relanzar.
### 7.6 Commit (uno por componente)
20. `git add` con rutas EXPLÍCITAS (sólo lo del componente + `recipes/base.ts`
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- `generated/base.css` + su README + este plan §8); `git diff --cached --stat`
y `git show :ruta` para verificar el ÁRBOL INDEXADO, no el de trabajo;
`git status` para ver que no entra nada de otra sesión. Mensaje con
`git commit -F fichero` (backticks en `-m` se ejecutan en bash), en
castellano, con: qué knobs entran, la cifra del censo antes → después,
el diff de computed (vacío), el centinela (n tokens probados), los
guards. **Nunca `--amend`** en la rama compartida.
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
21. Registro en §8 de este plan (componente · commit · censo antes→después ·
excepciones) y, si el bloque cierra, en `CONTINUE-theming.md`.
### 7.7 Revisión adversarial por bloque
22. Al cerrar un bloque, una pasada escéptica (otra sesión o un pase separado)
que por componente intente REFUTAR con medición propia: «el default es
idéntico» (sonda 7.2 vs 7.4 rehecha, no leída del commit), «cada token
alcanza» (centinela rehecho), «el velo cae donde debe» (píxel arriba),
«no hay doble animación». Hallazgos reales se arreglan en el bloque; los
refutados se anotan. El molde es la revisión del `Sidebar` (21
hallazgos · 17 refutados · 4 reales, `bae03e55c`).
### 7.8 Lo que NO se hace en este eje (y se anota como deuda si aparece)
- Rediseñar un default (radio, color, altura…): deuda firmable, no cambio.
- Tocar sema/morfo salvo un arquetipo en un envoltorio (decisión aparte).
- Cascadear a otro componente «ya que estamos» (memoria `no-cascade-changes`).
- Inventar nombres de token fuera de §1/§6.7, o un hover/estado por componente.
- Editar `generated/base.css` a mano.
- Dar un paso por hecho sin su artefacto.
---
## 8. Registro
uix(aura): temable — 43 % → 80 %, y doce silencios que son diseño 13 claves (24 → 37). Censo aura 43 % → **80 %**; global del eje 61 %. Su texto de estado y su detalle bajan UN paso del bundle por talla, y las TRES coordenadas viajan juntas (tamaño, interlineado, tracking): doce declaraciones, doce valores distintos, doce claves — 1:1, sin inflación. El valor sigue siendo del bundle; el knob pasa a ser del componente. La decimotercera es `idle-opacity`: el reposo no desaparece, se retira a media opacidad. Fuera por identidad: el `--radius-full` del anillo (redondo por definición), los cuatro `100%` del canvas y del SVG, y el `1px` × 2 de la región viva — la técnica sr-only, la misma de `text-blur`. DOCE ADJUDICACIONES, TODAS MEDIDAS, Y NINGUNA ES DEUDA: - **Las seis tintas de estado y el halo pintan el FONDO DEL INDICADOR**, y la demo corre con el canvas WebGL montado: la regla `[data-aura-indicator]:has(canvas)` pone `background: none` A PROPÓSITO — el orbe ES la presencia, y un disco detrás enturbia su silueta. Quitando el canvas en vivo, el gradiente vuelve y las siete siguen al token (oklch(0.6434 0 0) → rgb(1,2,3) dentro del radial-gradient). - **El arco y el halo son SLOTS del anillo que sólo existen en los estados de trabajo**; la demo se queda en la pista. Montados como `<circle data-aura-ring-slot>` dentro del SVG real —que es exactamente lo que el CSS selecciona—: 2px → 1234px, 1px → 1234px, 0.5 → 0.123. - Las dos de transición, congeladas por el guard como en todo el catálogo. Y una ceguera más del instrumento, la tercera de la sesión: **el guard no veía el SVG del anillo** porque sus `<circle>` no llevan atributo `data-aura*`. `extraNodes` los mete en el conjunto medido. Gates: sonda antes/después **0 diffs en 1.710 valores** (9 nodos × 7 estados) · centinela 25/37 con las doce adjudicadas · censo --only 80 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`aura` 43 % → 80 %** (13 claves). Global 61 %.
Su texto de estado baja un paso del bundle por talla y las TRES coordenadas
viajan juntas: doce declaraciones, doce valores, doce claves (1:1, sin
inflación). Más `idle-opacity`. Identidad fuera: el radio del anillo, los
cuatro `100%` y el `1px` de la región sr-only.
**Doce adjudicaciones medidas**: las seis tintas de estado y el halo pintan el
fondo del INDICADOR y la demo corre con el canvas WebGL montado —
`:has(canvas)` pone `background: none` a propósito—; quitando el canvas en
vivo, el gradiente vuelve y las siete siguen. El arco y el halo son SLOTS que
sólo existen en los estados de trabajo: montados como `<circle>` en el SVG
real, alcanzan. Y el guard no veía ese SVG (sus `<circle>` no llevan
`data-aura*`): tercera vez que `extraNodes` salva tokens vivos.
Gates: sonda 0 diffs en 1.710 valores · centinela 25/37 con las doce
adjudicadas · censo 80 % · `--names` sin desviadas · audit PASS · lint 0 ·
suite con el rojo conocido · rtl 0 · docs 0.
uix(color-picker): temable — 51 % → 75 %, y un prefijo ABREVIADO fuera de todo el árbol 20 claves de costura (54 → 71) y cinco nombres renombrados. Global del eje 60 % → **61 %**. `--cp-*` VIOLABA theming §6 r5 («no abrevies el nombre del componente»). Cinco nombres —`checker`, `checker-cell`, `current-color`, `channel-gradient`, `trigger-color`— y **45 ocurrencias en 8 ficheros**, tres de ellos de SOMA: el provider los estampa inline, su test los lee y sus tipos los declaran. Renombrados a `--_color-picker-*` con `git ls-files` delante; dos de ellos —el damero y el gradiente de canal— ya derivaban de un público, así que el censo los reclasifica solo. Precedente: los `--gb-*` / `--_ntp-*` de F2-A. Cosido: el ritmo del componente, la tinta de la etiqueta, la tipografía del disparador y la del VALOR —que es metadato: mono, regular, tinta e interlineado propios—, el borde de la muestra, el raíl del canal y su pulgar, el hueco del anillo de la muestra y el borde del cuentagotas. UNA SOMBRA QUE SON DOS Y HACEN UNA COSA: el pulgar del canal lleva contorno + elevación en un solo `box-shadow`, así que se acuña como UN token y la regla de foco lo compone con su tercer anillo, en vez de repetir las dos sombras. EL INSTRUMENTO NO VEÍA DOS FAMILIAS DE NODO, y las dos se arreglaron midiendo: las muestras del picker **son `<ColorSwatch>`** (llevan `data-color-swatch`, no `data-color-picker-*`) y sus sliders de canal **son SLIDERS** (el pulgar y el raíl llevan `data-slider-*`). Cuatro tokens vivos leían muertos por eso. LO ROJO QUE QUEDA ES ANTERIOR A ESTE COMMIT: el guard reporta 23 sin adjudicar —las muestras, la rueda de tono (`hue-0..360` + `saturation-floor`), el ancho del panel por talla, el cuentagotas y las dos de transición—, todos del bloque que ya existía. Antes eran **24**, así que la cifra baja. Su pase de medición queda anotado en §13 con dos causas ya descartadas: el panel SÍ abre y sobrevive al blur, al parqueo del ratón y al forzado de `data-size`, y el guard ya ve sus nodos. Lo que este commit añadió está medido: dos adjudicadas montando el disparador de muestra dentro del panel abierto, el resto en vivo. Gates: sonda antes/después **0 diffs en 1.350 valores** (8 estados), medida DOS veces —una tras el renombrado, otra tras la costura— · tests de soma del color-picker **7/7** tras el renombrado · censo --only 75 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`color-picker` 51 % → 75 %** (20 costuras + un prefijo
abreviado retirado). Global **60 % → 61 %**.
**`--cp-*` violaba theming §6 r5** (no abreviar el nombre del componente):
cinco nombres, **45 ocurrencias en 8 ficheros**, tres de SOMA —el provider los
estampa inline, su test los lee, sus tipos los declaran—. Renombrados a
`--_color-picker-*` con `git ls-files` y 0 diffs; dos ya derivaban de un
público, así que el censo los reclasifica solo. Precedente: los `--gb-*` de
F2-A.
Cosido el ritmo, la tinta de la etiqueta, la tipografía del disparador y la
del VALOR (metadato: mono/regular/tinta propia), el borde de la muestra, el
raíl de canal y su pulgar, el hueco del anillo y el borde del cuentagotas.
**El pulgar lleva DOS sombras que hacen una cosa** (contorno + elevación):
un token, y la regla de foco lo compone con su tercer anillo.
**El instrumento no veía dos familias de nodo**: las muestras del picker SON
`<ColorSwatch>` (`data-color-swatch`) y sus sliders SON sliders
(`data-slider-*`); cuatro tokens vivos leían muertos. Con `extraNodes` para
las dos, 46/71.
**Lo rojo que queda es anterior**: 23 sin adjudicar (eran 24), del bloque
viejo — su pase propio queda en §13, con dos causas ya descartadas (el panel
SÍ abre y sobrevive al forzado de talla; el guard ya ve sus nodos).
Gates: sonda 0 diffs en 1.350 valores, medida DOS veces (tras el renombrado y
tras la costura) · tests de soma del color-picker 7/7 · censo 75 % ·
`--names` sin desviadas · audit PASS · lint 0 · suite con el rojo conocido ·
rtl 0 · docs 0 · `check` sin errores propios.
uix(popover): temable — 46 % → 67 %, y un huérfano que el guard de huérfanos no veía Once claves para el disparador + una RETIRADA (36 → 46). Global del eje 60 %. EL DISPARADOR GENÉRICO ES CROMO PROPIO. El que no lleva el arquetipo `field-trigger` —que tiene su tratamiento aparte— montaba alto, padding, borde, radio, superficie y tinta sobre primitivos crudos. Dos notas: `trigger-height` lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo que el CSS traía, y hover y abierto llevan CADA UNO su par de claves aunque hoy compartan valor — son dos estados, y un tema puede querer el abierto más marcado. `bg` RETIRADO: ninguna regla CSS lo consumía. El único sitio del corpus que lo nombraba era un COMENTARIO de `popover-arrow.svelte` («fill: var(--popover-bg)»), y el test `does not leave declared public recipe variables orphaned` busca el nombre como TEXTO en los ficheros del componente — así que un comentario mantiene vivo un token para siempre. La superficie del panel la pinta el plano de profundidad (`--depth-overlay-surface`), que es lo que un plano debe poseer; el comentario queda corregido y el hueco del test, en §13. LOS DOCE QUE SIGUEN FUERA SON LA FIRMA DE SALIDA, y no se cosen aquí: `data-last-action` × 3 intents × 4 lados tiñendo el borde durante el cierre. La misma matriz está repetida en `drawer` (22 reglas) y `dialog` (9) — y ya ha derivado: dialog tiñe sólo `border-top-color` mientras popover y drawer usan el shorthand con `--border-width-medium`, así que en dos de tres el GROSOR también cambia al salir. La tabla morfo→eidos la sitúa como mapeo transversal («`events[].prewrite` → tinting the exit anim by cause»), misma fila que los arquetipos: es canal visual de sema, no cromo de componente. Coserlo tres veces sería triplicar un vocabulario que debe vivir una vez → §13. DOS CEGUERAS DEL INSTRUMENTO, arregladas: 1. La página tiene CINCO disparadores y el guard abría el primero del documento —un ejemplo de más abajo—, no el del escenario: las partes que el escenario monta nunca entraban. `openWith` apuntando al escenario. 2. `content-z` se consume sobre el ENVOLTORIO FLOTANTE (`:has(> [data-popover-content])`), un nodo sin atributo `data-popover*` que el filtro no veía: leía muerto moviendo 80 → 4321. TRECE ADJUDICACIONES, TODAS MEDIDAS: nueve son partes que la demo no monta (Title, Description) y dos el overlay opcional —montados en el panel ABIERTO con los atributos que el CSS selecciona, alcanzan—. **Las dos que no son de montaje**: `font-family` y `line-height` no pueden ganar porque el plano `overlay` impone la tipografía del panel — el hallazgo §12.9, que espera tu firma y afecta a VEINTE componentes. Aquí queda medido otra vez. Gates: sonda antes/después **0 diffs en 420 valores** (8 estados; el escenario monta un disparador y el panel portala fuera, así que el estado `abierto` es el que mide el panel: 7 nodos) · centinela 33/46 · censo --only 67 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`popover` 46 % → 67 %** (11 claves para el disparador + una
RETIRADA). Global 60 %.
El disparador genérico —el que no lleva el arquetipo `field-trigger`— montaba
su cromo sobre primitivos crudos: once costuras, con `trigger-height` leyendo
el BUNDLE y no el primitivo, y hover/abierto con su par cada uno (mismo valor
hoy, dos estados distintos).
**`bg` retirado: era un huérfano que el test de huérfanos no veía.** Ninguna
regla lo consumía; lo único que lo nombraba era un COMENTARIO de
`popover-arrow.svelte`, y ese test busca el nombre como texto en los ficheros
del componente. La superficie la pinta el plano de profundidad. → §13.
**Los doce que quedan son la FIRMA DE SALIDA y no se cosen aquí**: la misma
matriz `data-last-action` × 3 intents × 4 lados está repetida en drawer (22)
y dialog (9), y ya ha derivado (dialog tiñe sólo el color; popover y drawer
también el grosor). La tabla morfo→eidos la sitúa como mapeo transversal, así
que la salida es una regla sobre el arquetipo, no tres copias. → §13.
**Dos cegueras del instrumento**: la página tiene CINCO disparadores y el
guard abría el primero del documento en vez del del escenario; y `content-z`
se consume sobre el ENVOLTORIO FLOTANTE, un nodo sin atributo `data-popover*`,
así que leía muerto moviendo 80 → 4321. Las dos arregladas en
`COMPONENT_OVERRIDES`.
Gates: sonda 0 diffs en 420 valores (8 estados) · centinela 33/46 con trece
adjudicaciones medidas —nueve de partes que la demo no monta, dos del overlay
opcional y **dos que el plano `overlay` gana** (§12.9, pendiente de firma)— ·
censo 67 % · `--names` sin desviadas · audit PASS · lint 0 · suite con el rojo
conocido · rtl 0 · docs 0.
uix(radio-cards): 61 % es su techo — 21 de sus 23 knobs no son suyos Una sola clave nueva (`invalid-border`, 39 → 40) y su veredicto §5. RadioCards es un **skin sobre RadioGroup**: renderiza `<RadioGroup.Provider>` y su indicador ES un radio. Por eso lee `--radio-group-item-size-*`, `--radio-group-indicator-*` y el puente de paleta del anfitrión (`--_radio-group-palette-*`, declarado en el host `[data-radio-group]` donde esta tarjeta vive). El censo los marca «⤴ prestado» y el mandato de composición (`theming-audit` §5.3-3) dice que **se teman en la ficha del dueño**: coserlos aquí sería vocabulario paralelo y dejaría que un tema rompiera «un radio es un radio» sólo dentro de las tarjetas. Lo acuñado es lo que sí es suyo: el borde de la TARJETA en validación fallida. Los dos `--radius-full` del indicador son identidad (un radio es redondo por definición, misma clase que el `50%` de un círculo) y los cuatro literales son geometría del glifo (el `46%` del punto interior, el `100%` del SVG). LA LECCIÓN DE LA MEDICIÓN: la primera lectura forzada de `invalid-border` dio «sin efecto». No era el token: la tarjeta transiciona `border-color`, y leer justo después de escribir devuelve el valor de PARTIDA — exactamente el motivo por el que el guard congela las transiciones. Con la congelación puesta, `oklch(0.9555 0.0207 13.86)` → `rgb(1,2,3)`. **Una medición forzada a mano tiene que congelar también.** Queda escrito en el veredicto y en el ledger. Las otras cuatro adjudicaciones, con su medición: `horizontal-basis` sólo bajo `[data-orientation=horizontal]` (192px → 1234px), `disabled-opacity` sólo bajo `[data-disabled]` (0.4 → 0.123) y las dos de transición, que el guard congela (0,12s → 11,5s · cubic-bezier → steps(4)). Gates: sonda antes/después **0 diffs en 3.540 valores** (19 nodos × 7 estados) · centinela 35/40 con las cinco adjudicadas · censo --only 61 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`radio-cards` 60 % → 61 %: una sola clave, y ése es su techo.**
21 de sus 23 knobs fuera de alcance **no son suyos**: es un SKIN sobre
RadioGroup —renderiza `<RadioGroup.Provider>` y su indicador ES un radio—, así
que lee `--radio-group-*` y el puente de paleta del anfitrión. Mandato de
composición (`theming-audit` §5.3-3): se teman en la ficha del dueño. Lo suyo
es la TARJETA: `invalid-border`. Los dos `--radius-full` del indicador son
identidad (un radio es redondo por definición).
**La lección de la medición**: la primera lectura de `invalid-border` dio «sin
efecto» porque la tarjeta transiciona `border-color` y leer justo después de
escribir devuelve el valor de PARTIDA. Con las transiciones congeladas —lo que
el guard hace por defecto y mi sonda a mano no— alcanza. **Una medición
forzada tiene que congelar también.**
Gates: sonda 0 diffs en 3.540 valores · centinela 35/40 con las cinco
adjudicadas y medidas · censo 61 % · `--names` sin desviadas · audit PASS ·
lint 0.
uix(prose): temable — 1 % → 83 %, la costura por ELEMENTO y dos cegueras del instrumento 35 claves nuevas (2 → 37). Censo prose 1 % → **83 %** con `global` a CERO; global del eje **59 % → 60 %** (1.064 → 1.021 knobs `global` en el catálogo). Prose estiliza HTML CRUDO (`h1`, `code`, `table`, `kbd`…) a través de `:where([data-prose] el)`, así que sus «partes» son elementos y la costura va POR PARTE, como en `result`: `heading-*` (+ `h1-font-weight`, lo único que distingue al h1 de sus hermanos), `link-*`, `strong-`/`dt-`/`th-font-weight`, `mark-*`, `blockquote-*`, `code-*`, `pre-*`, `kbd-*`, `media-radius`, `hr-*`, `table-*`, más `fg`, `fg-muted` y el eje `font-size-{sm,md,lg}`. Los valores siguen siendo los del canon; lo que faltaba era el knob (PLAN §2-A). LA ESCALA EDITORIAL NO ENTRA, Y ES DOCTRINA FIRMADA: los `2.25em`, `1.5em`, `0.9em`, sus interlineados y su tracking son `em`-relativos a propósito —un `font-size` raíz re-deriva encabezados y márgenes, por eso NO hay una escala duplicada por talla como en tailwind-typography (su README: «No 5× duplicated scale»)— y van anotados `/* literal: */`, la válvula de recipe-contract §3. Son las 21 excepciones firmadas que el censo aprendió a leer esta misma sesión. Dos literales se cambiaron por su paso de escala, con el mismo valor: el `3px` del borde-acento del blockquote ES `--border-width-thick`, y los `1px` de kbd, `hr` y la tabla son `--border-width`. EL INSTRUMENTO ESTABA CIEGO DE DOS MANERAS, arregladas ANTES de medir: 1. **Sonda y centinela filtran por `data-{c}-*`** y aquí NINGUNA parte lo lleva: la sonda medía **1 nodo** y el guard daba **27 de 37** tokens por muertos. Los dos ganan un selector de nodos extra (`EXTRA_NODES` en la sonda, `extraNodes` en `COMPONENT_OVERRIDES`) — con él, 63 nodos y un gate que significa algo. 2. **El centinela no leía el color de un borde que no fuera el de arriba.** El blockquote tiñe su `border-inline-start` y la tabla sus bordes de bloque: tres tokens vivos leían muertos. Añadidos los cuatro lados lógicos y `borderBottomColor`. OCHO ADJUDICACIONES, TODAS MEDIDAS: cinco las tapa el harness de la documentación —`[data-uix-docs] code|pre` (0,2,0) gana a `:where(…)` (0,0,0), que es la incidencia ya escrita para `code-block` un piso más arriba— y tres son elementos que el HTML de ejemplo no usa (`<mark>`, `<dl>`), montados DENTRO del contenedor real: un `<mark>` a pelo es exactamente lo que esta receta estiliza, así que el nodo es fiel (la lección de `drag-drop` cumplida). Gates: sonda antes/después **0 diffs en 11.910 valores** (63 nodos × 7 estados) · centinela 29/37 con las ocho adjudicadas · censo --only 83 % · `--names` 37/37 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Componentes del ledger re-verificados tras tocar el guard (text-gradient, chart, result, tree-view, textarea): sin regresiones ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`prose` 1 % → 83 %** (35 claves de costura, 2 → 37).
Global **59 % → 60 %**; `global` del catálogo 1.064 → 1.021.
Estiliza HTML CRUDO por `:where([data-prose] el)`, así que sus partes son
ELEMENTOS y la costura va por parte, como en `result`. Su escala editorial
`em`-relativa no entra: es decisión de diseño de su README («No 5×
duplicated scale») y va anotada `/* literal: */` — las 21 excepciones que el
censo aprendió a leer esta misma sesión. Dos literales se cambiaron por su
paso de escala con idéntico valor (`3px` → `--border-width-thick`, los `1px`
→ `--border-width`).
**El instrumento estaba ciego de dos maneras**, las dos arregladas antes de
medir: sonda y centinela filtran por `data-{c}-*` y aquí NINGUNA parte lo
lleva —la sonda medía **1 nodo** y el guard daba 27 de 37 por muertos—, así
que ambos ganan un selector de nodos extra (63 nodos con él); y el centinela
no leía el color de un borde que no fuera el de arriba, con lo que el
blockquote (`border-inline-start`) y la tabla (bordes de bloque) leían
muertos estando vivos.
Ocho adjudicaciones medidas: cinco las tapa el harness de la documentación
(`[data-uix-docs] code|pre` (0,2,0) gana a `:where(…)` (0,0,0) — la
incidencia ya escrita para `code-block`) y tres son elementos que el HTML de
ejemplo no usa, montados dentro del contenedor real.
Gates: sonda **0 diffs en 11.910 valores** (63 nodos × 7 estados) · centinela
29/37 · censo 83 % · `--names` 37/37 · audit PASS · lint 0 · suite con el
rojo conocido · rtl 0 · docs 0 · `check` sin errores propios. Los del ledger
re-verificados tras tocar el guard: sin regresiones.
uix(listbox): 68 % es su techo — coserlo como sus hermanos habría mentido Sin cambios de código: su veredicto §5 y la incidencia registrada. Cinco de sus nueve privados ADOPTAN la capa `list-surface` (el ritmo de fila que `lib/list-surface.css` resuelve por talla sobre `data-list-surface`, compartido con select, combobox, command y los tres menús); dos son el puente de paleta THM-2 (misma razón que `tag-group`) y el noveno sí deriva de un público. La cola decía «cose como sus hermanos», porque `dropdown-menu` y `context-menu` sí acuñan el puente (`--dropdown-menu-item-height: var(--list-item-height)`). Medido antes de copiarlo, con el menú abierto y la fila a 36 px: tema escribe --dropdown-menu-item-height: 1234px en :root -> 36px tema escribe --list-item-height: 1234px en :root -> 36px **Ninguno alcanza.** Es cascada: la capa declara sobre `[data-list-surface][data-size]` y el puente sobre `[data-{c}-content]`; las dos ganan a `:root` siempre. Copiar el puente aquí habría subido la cifra sin dar alcance a nadie — un token que miente, que es justo la clase que R-5.4 existe para cazar. Se arregla EN LA CAPA, no en el consumidor: dándole a `list-surface` una entrada de receta como se hizo con `calendar-surface` (el gancho resuelve por talla, el vocabulario vive en la receta). Toca diez componentes de golpe → §13, eje propio. Y deja un aviso sobre el instrumento, también en §13: **el centinela da por vivos los tokens de esta forma** porque escribe inline en el nodo, que gana a la regla del gancho. Para un token que un tema sólo puede escribir en `:root`, «mueve un computed» no equivale a «un tema lo alcanza». Lo demás de su 32 %: el `background` del item resaltado es la incidencia ya escrita del velo de estado (§12) y el `100%` de `[data-block]` es identidad. docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`listbox` ADJUDICADO: 68 % es su techo, y coserlo habría
mentido.** Sin cambios de código: su veredicto §5.
Cinco de sus nueve privados adoptan la capa `list-surface` (el ritmo de fila
compartido con select, combobox, command y los tres menús), dos son el puente
de paleta y el noveno sí deriva de un público. La cola decía «cose como sus
hermanos» —`dropdown-menu` y `context-menu` sí acuñan el puente—, y la
medición lo desmontó: **ninguno de los dos nombres alcanza desde `:root`**
(fila a 36 px, `--dropdown-menu-item-height` y `--list-item-height` a
`1234px` en `:root` → 36 px). La capa declara sobre
`[data-list-surface][data-size]` y el puente sobre `[data-{c}-content]`: las
dos ganan a `:root`. Copiar el puente habría subido la cifra sin dar alcance
— un token que miente, la clase que R-5.4 existe para cazar.
Se arregla EN LA CAPA (entrada de receta, como `calendar-surface`), toca diez
componentes y es su propio eje → §13. **Y avisa sobre el instrumento**: el
centinela da por vivos estos tokens porque escribe inline en el nodo; para un
token que un tema sólo puede escribir en `:root`, «mueve un computed» no
equivale a «un tema lo alcanza».
tooling(theming): el censo aprende a leer la válvula del canon — clase `exception` Global 58 % → **59 %** sin tocar una línea de CSS: 54 knobs que eran deuda resultan ser desviaciones que el canon ya había firmado. `recipe-contract` §3 dice que una desviación anotada `/* literal: <razón> */` EN SU PROPIA DECLARACIÓN es válida, y `component-audit` la honra desde siempre (`declarationWindow` + `/literal:/i` en R-2.1, R-2.6 y R-2.7). El censo era el único instrumento que no: su `strip()` blanquea los comentarios ANTES de clasificar, así que las **81 anotaciones del catálogo** contaban como literales sin excusa. Dos instrumentos, un canon, dos respuestas — y la que mandaba era la del que no sabía leerlo. Ahora un literal anotado cae en la clase **`exception`, fuera del ratio** como `system`, y por la misma razón: no es que un tema no lo alcance, es que el canon ya aceptó por escrito que ahí no va un token. **54 knobs en 12 componentes**: `prose` 21 (su escala editorial em-relativa, que su README documenta como decisión — «No 5× duplicated scale»), `proof-of-human` 16 (la escena de tono fijo), `palabras` 3, `timeline` 2 (73 % → **77 %**), `result` 1 (86 % → **90 %**), y siete más con una cada uno. Detalles que costaron medición: - **La anotación vale para la DECLARACIÓN entera**, no sólo para su primera línea: un valor partido en varias líneas lleva la nota al final. Se comprueban todas las líneas que abarca la declaración. - **`strip()` blanquea en el sitio** (sustituye por espacios, conserva los saltos), así que el cuerpo despojado y el crudo quedan alineados línea a línea y basta con guardar el crudo al lado. - Muta-prueba: retirar una anotación de `prose` mueve el knob de `exception` a `literal` (21 → 20) y el conteo vuelve al restaurarla. Efecto colateral bueno: cada ficha gana una **§1.4 «Excepciones firmadas»** que las LISTA con su razón. Estaban escritas en el CSS y no se leían desde ninguna parte; ahora la ficha del componente las enseña, que es donde se decide si una sigue siendo cierta. Las 163 fichas cambian porque la plantilla cambia (sección nueva + la línea de reparto); el README de la auditoría gana la fila de la clase y la nota de que `system` y `exception` quedan fuera del denominador. Guards: `--names` 4.105 claves, DESVIADAS 0 · component-audit PASS · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **El censo aprende a leer la válvula del canon: clase
`exception`.** Global **58 % → 59 %**, sin tocar una línea de CSS.
`recipe-contract` §3 dice que una desviación anotada
`/* literal: <razón> */` **en su propia declaración** es válida, y
`component-audit` ya la honra desde siempre (`declarationWindow` +
`/literal:/i` en R-2.1/2.6/2.7). El censo era el único instrumento que no:
su `strip()` blanquea los comentarios ANTES de clasificar, así que las
**81 anotaciones del catálogo** contaban como deuda. Dos instrumentos, un
canon, dos respuestas.
Ahora un literal anotado cae en la clase `exception`, **fuera del ratio**
como `system` — y por la misma razón: no es que un tema no lo alcance, es que
el canon ya aceptó por escrito que ahí no va un token. **54 knobs** en 12
componentes: `prose` 21 (su escala editorial `em`-relativa, decisión de
diseño firmada en su README), `proof-of-human` 16 (la escena de tono fijo),
`palabras` 3, `timeline` 2 (73 % → **77 %**), `result` 1 (86 % → **90 %**),
y siete más con una cada uno.
**La anotación vale para la DECLARACIÓN entera**, no sólo para su primera
línea: un valor partido en varias líneas lleva la nota al final, y así se
mide. Muta-prueba: retirar una anotación de `prose` mueve el knob de
`exception` a `literal` (21 → 20) y devuelve el conteo al restaurarla.
Efecto colateral bueno: las fichas ganan una **§1.4** que LISTA las
excepciones con su razón. Antes estaban escritas en el CSS y no se leían
desde ninguna parte.
uix(theming): tres techos que no son deuda — el PUENTE DE PALETA no se acuña Sin cambios de código: los veredictos §5 de `tag-group` (65 %), `stepper` (79 %) y `timeline` (73 %), y la clase registrada. Los tres tienen `global` a CERO. Lo único fuera de alcance son sus privados `--_{c}-palette-*`: el puente THM-2 que la capa compartida alimenta POR INSTANCIA desde `[data-color]`. **Un público encima del puente (`--tag-group-selected-bg: var(--_tag-group-palette-surface)`) dejaría que un tema lo fijara y matara en silencio el `color=` de cada instancia** — la misma clase de daño que «declarar un público de facto mata una escala» (`knob`). Y no hace falta: los componentes ya exponen sus nueve roles × variante como claves públicas (91 en `tag-group`). Ésa ES su superficie de color, y está completa. `timeline` suma una segunda razón, también firmada: sus `--_timeline-hue-*` son un CONMUTADOR —cada `data-intent` los re-apunta a las ranuras de rol y el intent por defecto al puente—, y aplanarlo obligaría a duplicar cada regla por intent (doctrina F2-B, cinco precedentes). MEDIDO, para no adjudicar de oído: de las **687** declaraciones del catálogo que leen un privado, **130 leen un `--_{c}-palette-*`**, y son las que dominan la columna `private` de todo componente con paleta — tag-group 14/14, stepper 11/11, radio-group 10, checkbox 8, file-upload 8, calendar / editable / los tres grids 7 cada uno. Consecuencia que queda escrita en §13: **el techo de un componente con paleta no es el 100 %**, y su cifra no compara con la de uno sin ella, porque el denominador incluye un puente que por diseño no se acuña. Si algún día se quiere comparabilidad, la salida es una clase `bridge` en el censo — no acuñar los tokens. Los otros seis heredan la razón cuando les toque, pero ojo: ésos tienen ADEMÁS knobs `global` y sí les toca trabajo real. Los cuatro literales de los tres son identidad anotada: el `line-height: 1` del botón de quitar y el del glifo del marcador, el `100%` de layout del stepper y los dos `1em` que hacen que título y descripción del timeline hereden el tamaño escalado del item en vez del tamaño UA del encabezado. docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **Tres techos adjudicados: el PUENTE DE PALETA no es deuda.**
`tag-group` 65 %, `stepper` 79 %, `timeline` 73 % — sin cambios de código,
sus tres veredictos §5.
Los tres tienen `global` a cero: lo único fuera de alcance son sus privados
`--_{c}-palette-*`, el puente THM-2 que la capa compartida alimenta por
instancia desde `[data-color]`. **Un público encima del puente dejaría que un
tema lo fijara y matara en silencio el `color=` de cada instancia** — la misma
clase de daño que «declarar un público de facto mata una escala». Ya exponen
sus nueve roles × variante en claves públicas (91 en `tag-group`): ésa ES su
superficie de color. `timeline` suma la segunda razón firmada: sus
`--_timeline-hue-*` son un CONMUTADOR por `data-intent` (doctrina F2-B, cinco
precedentes) cuya rama por defecto es el puente.
Medido para no adjudicar de oído: de las **687** declaraciones del catálogo
que leen un privado, **130 leen un `--_{c}-palette-*`**, y son las que dominan
la columna `private` de todo componente con paleta (tag-group 14/14, stepper
11/11, radio-group 10, checkbox 8, file-upload 8, calendar / editable / los
tres grids 7). **El techo de un componente con paleta no es el 100 %** y su
cifra no compara con la de uno sin ella — registrado en §13, junto con la
única salida si algún día se quiere comparabilidad: una clase `bridge` en el
censo, no acuñar los tokens.
uix(chart): la COSTURA del texto — 49 % → 89 %, y `global` a cero Seis claves. El valor sigue siendo el del canon; lo que faltaba era el knob. CORRIGE MI LECTURA DE HACE DOS HORAS. Al tokenizar chart di por doctrina que «el texto no recibe tokens del componente» porque su valor viene de la capa de estilos con nombre, y dejé el CSS leyendo `var(--style-caption-font-size)` y `var(--color-content-*)` A PELO. Eso es media doctrina: PLAN §2-A pide que TODO knob de apariencia lea `--{c}-{slot}`. Sin la costura, un tema no puede aclarar las anotaciones de un gráfico denso sin mover `caption` en toda la app, y `getRecipeTokens()` / `/temas` no ven esa superficie. El molde ya estaba escrito en la vertebración tipográfica (`--accordion-trigger-font-family: var(--style-label-font-family)`), y D-TH.2 aclara que sólo los SEIS primitivos quedan fuera: «un componente que sólo LEE un named style para un eje no es un primitivo y no está en el conjunto». POR ROL, NO POR PARTE — y se decidió midiendo: --chart-caption-font-size var(--style-caption-font-size) --chart-caption-fg var(--style-caption-color) --chart-label-font-size var(--style-label-font-size) --chart-label-fg var(--style-label-color) --chart-label-font-weight var(--style-label-font-weight) --chart-gauge-label-font-size var(--size-xl-font-size) Dos roles × tres ejes = 6 claves para 19 nodos de texto; nombrarlo por parte habrían sido ~30 claves con dos valores. **Tamaño y tinta son ortogonales a propósito**: `empty` y `bar-list-value` son tamaño `label` con tinta `caption`, y el CSS lo dice. El readout del gauge no tiene rol en el canon —un `h2` es un encabezado, no un dato— así que su costura toma el bundle de talla; con eso se cierra la escotilla que dejé abierta en el commit anterior. Tres nombres de mi primera propuesta no aguantaron la revisión y no entran: `label-font-size` a secas chocaba con las seis partes `*-label` del componente (se acepta el choque, pero nombrando por el ROL del canon, que dice la verdad: las etiquetas de eje SON captions), `emphasis-*` inventaba vocabulario fuera de §1/§6.7, y `fg-muted` se apoyaba en un único precedente. La tinta lee el color DEL ROL (`var(--style-caption-color)`), no la ranura a pelo. Y RETIRA UNA NOTA EQUIVOCADA DE §13: escribí que el censo penalizaba consumir la capa tipográfica cuando no eres un primitivo. El censo medía bien; faltaba la costura. Con ella, `global` cae a CERO y el alcance a 89 % — el techo, porque lo que queda son los diez literales de identidad (nueve `100%` + el `max-content` del tooltip). La lección queda escrita: cuando la métrica y la doctrina discrepan, sospecha de la lectura antes que del instrumento. `listbox` (68 %, ritmo de fila prestado de `list-surface`) es la misma pregunta un piso más arriba y queda pendiente de revisar con este molde. Gates: sonda antes/después **0 diffs en 161.850 valores computados** (16 rutas × 7 estados) · centinela **43/45**, las dos de transición adjudicadas · censo --only **89 %** con `global` 36 → 0 · --names 45/45 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **LA COSTURA: `chart` 49 % → 89 % y `result` 23 % → 86 %**
(6 + 15 claves). Global **57 % → 58 %**; `global` del catálogo 1.113 → 1.064.
**Corrige una lectura mía de las dos horas anteriores.** Al tokenizar `chart`
y `result` di por doctrina que «el texto no recibe tokens del componente»
porque su valor viene del canon —la capa de estilos con nombre, las ranuras de
contenido, el bundle de talla— y lo dejé leyendo el canon A PELO desde el CSS.
Eso es media doctrina: **el valor es del sistema, pero el knob tiene que ser
del componente** (PLAN §2-A: todo knob de apariencia lee `--{c}-{slot}`). Sin
la costura, un tema no puede aclarar las anotaciones de un gráfico denso sin
mover `caption` en toda la app, y `getRecipeTokens()` / `/temas` no ven esa
superficie. El molde ya estaba escrito en la vertebración tipográfica
(`--accordion-trigger-font-family: var(--style-label-font-family)`) y D-TH.2
aclara que **sólo los seis PRIMITIVOS** quedan fuera («un componente que sólo
LEE un named style para un eje no es un primitivo»).
**Dos formas, porque los componentes son distintos** — y ésa es la parte que
se firmó:
- `chart`, **por ROL**: dos roles del canon (`caption` para las anotaciones,
`label` para el cromo) × tres ejes = 6 claves para 19 nodos de texto.
Nombrar por parte habría dado ~30 claves con dos valores. Tamaño y tinta
son ORTOGONALES a propósito (`empty` y `bar-list-value` son tamaño `label`
con tinta `caption`). El readout del gauge no tiene rol en el canon y su
costura toma el bundle.
- `result`, **por PARTE**: cinco partes con tipografía distinta y ningún rol
que las cubra → 15 claves `title-*`, `description-*`, `extra-*`, `code-*`,
`media-*`. Es la forma dominante del catálogo.
**Nombres**: los tres que propuse primero no aguantaron la revisión
(`label-font-size` chocando con las seis partes `*-label`, `emphasis-*`
inventando vocabulario, `fg-muted` apoyado en un solo precedente). Se nombra
por el ROL DEL CANON que el texto adopta, y la tinta lee el color DEL ROL
(`var(--style-caption-color)`), no la ranura a pelo.
Con esto **`global` cae a CERO en los dos**: lo que queda fuera son los diez
literales de identidad de chart (nueve `100%` + `max-content`) y tres de
result.
Gates: sonda **0 diffs en 161.850 valores** (chart, 16 rutas × 7 estados) y
**990** (result) · centinela chart **43/45** y result **15/27**, y las doce
que callan están medidas SOBRE NODOS REALES con los controles de la demo
(chip `not-found` para el código HTTP, casilla del `extra`, quitar
`data-status` para la tinta por defecto del glifo) · censo 89 % / 86 % ·
`--names` 45/45 y 27/27 · audit PASS · lint 0 · suite con el rojo conocido ·
rtl 0 · docs 0 · `check` sin errores propios.
**Y retira una nota equivocada de §13**: escribí que el censo penalizaba
consumir la capa tipográfica. El censo medía bien; faltaba la costura. La
lección queda escrita ahí: cuando la métrica y la doctrina discrepan,
sospecha de la lectura antes que del instrumento. `listbox` (68 %, ritmo de
fila prestado de `list-surface`) es la misma pregunta un piso más arriba y
queda pendiente de revisar con este molde.
uix(result): temable — 14 % → 23 %, la receta posee su CAJA y nada más Cuatro claves nuevas (8 → 12). Censo result 14 % → **23 %**; global 57 %; alcance <20 % 18 → 17. Aplicación directa de la doctrina que cerró `chart`: lo que la receta posee es su **caja** —el inset de página (`padding-block`/`-inline`) y el ritmo entre bloques (`actions-margin-block-start`, `extra-margin-block-start`)—, más los cuatro acentos de estado que ya tenía. Ni una clave más. LO QUE NO SE ACUÑA, Y POR QUÉ: Su tipo ya leía el BUNDLE DE TALLA (`var(--size-{xl,md,sm}-font-size)`), que es literalmente lo que recipe-contract §1 prescribe; la tinta lee las ranuras de contenido, el peso y el interlineado sus escalas, y el código grande la familia mono del tema. Acuñar `--result-title-font-size` habría sido tipografía por componente, prohibida hoy. El censo cuenta esas trece lecturas de canon como `global`: es la nota de `listbox` y `chart` (next-features §13), **tercera ocurrencia**, no deuda de este componente. Los cuatro literales son identidad documentada: el `calc(var(--size-xl-font-size) * 3)` del glifo de estado (el display es TRES veces el paso xl), el `1em` del código grande que hereda esa escala a propósito, su `0.04em` de tracking óptico y el `100%` del extra. Higiene de paso: el privado `--_result-media-color` pasa a `--_result-media-fg` — la gramática firmada (D-TH.6) dice `fg` y R-5.3 no mira los privados. CINCO ADJUDICACIONES, TODAS SOBRE NODOS REALES: El guard da 7/12. Las cinco que callan se midieron con los CONTROLES DE LA DEMO, no fabricando nodos (la lección de `drag-drop`): los tres acentos sólo pintan bajo su `data-status` —se pulsó su chip: oklch → rgb(1,2,3) en los tres— y el bloque `extra` es opcional —se marcó su casilla: 16px → 1234px y 559,44px → 1234px—. Las cinco alcanzan. Gates: sonda antes/después **0 diffs en 990 valores** (5 nodos × 7 estados) · censo --only 23 % · --names 12/12 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`result` 14 % → 23 %** (4 claves, 8 → 12). Global 57 %,
<20 % 18 → 17. Aplicación directa de la doctrina que cerró `chart`: **la
receta posee su CAJA** —inset de página, ritmo entre bloques— y nada más. Su
tipo ya leía el BUNDLE DE TALLA (`--size-{k}-font-size`), que es lo que
recipe-contract §1 prescribe, y su tinta las ranuras de contenido: acuñar
`--result-title-font-size` habría sido tipografía por componente. Las trece
lecturas de canon siguen contando `global` — §13, tercera ocurrencia.
Higiene de paso: el privado `--_result-media-color` → `--_result-media-fg`
(D-TH.6 dice `fg`; R-5.3 no mira privados).
Gates: sonda **0 diffs en 990 valores** · centinela 7/12 con **cinco
adjudicaciones medidas sobre nodos REALES** —los tres acentos pulsando su
chip de estado, el bloque `extra` marcando su casilla— y no sobre nodos
fabricados (la lección de `drag-drop`) · censo --only 23 % · --names 12/12 ·
audit PASS · lint 0 · suite con el rojo conocido · rtl 0 · docs 0 · `check`
sin errores propios.
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor 22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart 9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18). LO QUE DECIDIÓ LA MEDICIÓN, no la ficha: Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9× `content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6× `font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`. Nombrar por parte habría dado cinco claves para el pelo que separa marcas y nueve para la tinta de anotación: la inflación de `spinner` con otra cara. **El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar `--chart-label-font-size` sería tipografía por componente, prohibido el mismo día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`, `announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a `var(--style-caption|label-font-size)`, mismo valor. **La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento), `smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment). **Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA `var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract` prohíbe— con un respaldo muerto. El contrato guarda el bundle `var(--size-sm-control-height)`, 1:1 con él. **Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el `min-inline-size: max-content` del tooltip, misma clase ya adjudicada en `date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5. EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir: 1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo: `/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de 0/31 con seis sin adjudicar a 6/31 sin tener que saberlo. 2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` / `strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un componente que dibuja en SVG leían muertas estando vivas. Misma clase que el hueco de `filter` / `backdrop-filter` de ayer. 3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía `rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía muerta. La dimensión se decide ahora por el FINAL del nombre. Los 28 componentes con ledger re-verificados tras los tres cambios: cero regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.) Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados, 892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease` adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier → steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y 0 en los ficheros tocados. Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica** si no eres un primitivo —las doce declaraciones correctas siguen contando `global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número del gauge no tiene estilo con nombre que lo represente (un readout de dato no es un `h2`), así que sigue leyendo `--font-size-xl`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`chart` 9 % → 49 %** (22 claves nuevas fusionadas en su bloque,
17 → 39). El mayor de los de alcance <20 % que no estaba bloqueado.
**La ficha pedía 71 claves y ~20 habrían llevado el mismo valor.** Lo decidió
la medición: las 84 filas fuera de alcance colapsan a **26 valores distintos**
(9× `content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`,
6× `font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`). La
inflación de `spinner` con otra forma: nombrar por parte lo que es un
concepto.
**El texto no recibe tokens de chart**: toma su ROL DE TAMAÑO de los estilos
con nombre (`caption` para anotaciones, `label` para el cromo) y su tinta y
peso de las ranuras del sistema. Acuñarlos sería tipografía por componente,
lo que el autor prohibió el mismo día. Precedente de consumir la capa:
`code-block`, `kbd`, `announce`. **La receta sólo posee la tinta de lo que no
es texto** — `axis-fg`, `grid-fg`, `separator-fg` (el pelo de superficie que
separa marcas contiguas: cinco partes, una idea), `smith-boundary-fg`,
`track-bg` — más 17 de geometría de cromo. Identidad fuera de contrato: los
nueve `100%` y el `max-content` del tooltip.
**Incumplimiento heredado corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` (respaldo muerto); el contrato
guarda el bundle `var(--size-sm-control-height)`, 1:1.
**El instrumento estaba ciego de TRES maneras, arregladas antes de medir:**
1. **La superficie de chart vive en 16 rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo que sigue pendiente.
De paso `picker-shell` recibe la suya (su ruta propia es un 404, se mide
dentro del date-picker: 0/31 → 6/31 sin pasar la URL a mano).
2. **El guard no leía NINGUNA propiedad SVG** — sin `fill`/`stroke`/
`strokeWidth`/`fillOpacity`/`r`/`rx`, doce claves de un componente que
dibuja en SVG leían muertas estando vivas. Misma clase que el hueco de
`filter` del día anterior.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width`
recibía `rgb(1, 2, 3)` por contener «stroke» — inválido para una longitud,
así que leía muerta. Ahora la dimensión se decide por el final del nombre.
Gates: sonda **0 diffs en 161.850 valores** (16 rutas × 7 estados, 892 nodos
en reposo) · centinela **37/39** con dos adjudicaciones medidas sin la
congelación (`transition-duration` 0.12s → 11.5s, `-ease` → steps(4)) ·
censo `--only` 49 % y global **56 % → 57 %** (2.638 → 2.675 públicos,
<20 % 19 → 18) · `--names` 39/39 conformes · audit PASS · eidos-lint 0
invalid · suite eidos con el único rojo conocido · rtl 0 · docs 0 · `check`
sin errores en los ficheros tocados. **Los 28 componentes con ledger
re-verificados tras los tres cambios de instrumento: cero regresiones, cero
STALE.**
Abre (→ §13): el censo **penaliza consumir la capa tipográfica** si no eres
un primitivo (las doce declaraciones correctas siguen contando `global`) — la
nota de `listbox` con una segunda ocurrencia; y el número del gauge no tiene
estilo con nombre que lo represente (un readout de dato no es un `h2`).
uix(theming): text-scramble no tiene contrato que escribir — y el barrido de fantasmas, hecho Sin cambios de código: su veredicto §5 y el registro. Su 50 % no es ni deuda ni logro, es la suma de dos cosas que no son knobs: - **Los dos literales son la técnica sr-only** (`width`/`height: 1px` en `[data-text-scramble-sr]`), idéntica a `text-blur` y al resto del catálogo: existe para que el lector de pantalla lea el texto entero mientras la versión animada se parte en caracteres. Moverlos rompe la técnica. - **Los dos «públicos» son tipografía que el componente no debería acuñar** (`--text-scramble-font` / `-size`, con fallback y sin declarar): es la firma 1.bis pendiente. Escribirles contrato ahora sería acuñar exactamente lo que esa firma propone retirar. Con esto, 1.bis son **7 claves en 4 de los 5** componentes de la familia; `text-blur` es el único limpio: hereda. Y el `font` apunta a un FANTASMA, medido en navegador: `var(--font-mono, …)` no existe —el canónico es `--font-family-mono`— así que pinta `ui-monospace` e **ignora la mono del tema** (`Azeret Mono`, que está ahí al lado). EL BARRIDO MECÁNICO QUE PEDÍAN `--radius-xs` Y `--font-size-4xl`, HECHO: 5.793 custom properties declaradas en todo `src/uix/eidos` (fundación + capas + recetas + `generated/`) contra los `var(--x, fallback)` de las recetas cuyo `--x` sea vocabulario de SISTEMA → **9 referencias fantasma**. (Las que van sin fallback ya las guarda `recipe-css-contract`; el hueco eran justo las que «funcionan por accidente».) Tabla completa en next-features §13: - `--color-content-tertiary` ×3 (float-panel, proof-of-human clock y rotate-align) — **es el caso histórico** que `recipe-contract` §1 cita como origen de su propia regla, y sigue vivo en tres recetas. - `--style-{body,prose,label,caption}-font-variation-settings` ×4 (`text.css`) — los estilos con nombre emiten 6 claves y ésta no está: **el eje de fuente variable no llega nunca al primitivo `Text`**. - `--font-family-sans` (palabras, track WIP) y `--font-mono` (text-scramble). Ninguna se arregla aquí. El barrido debería ser un guard: misma lógica que el test de fantasmas sin fallback que ya existe, con el filtro de vocabulario de sistema encima. Guards: censo `--only` sin cambio (50 %, es el veredicto lo que cambia) · component-audit PASS · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`text-scramble` ADJUDICADO: sin contrato que escribir** (y el
barrido de fantasmas, hecho). Sin cambios de código: su veredicto §5.
Su 50 % no es ni deuda ni logro. **Dos literales son la técnica sr-only**
(`width`/`height: 1px`), idéntica a `text-blur`. **Los dos «públicos» son
tipografía que el componente no debería acuñar** —`font` y `size`,
consumidos con fallback y sin declarar—, o sea la firma **1.bis**:
escribirles contrato ahora sería acuñar exactamente lo que esa firma propone
retirar. Con `text-scramble` la cuenta de esa firma sube a **7 claves en 4 de
los 5** componentes de la familia (`text-blur` es el único limpio: hereda).
Y su `font` apunta a un fantasma: `var(--font-mono, …)` no existe —el
canónico es `--font-family-mono`— así que pinta `ui-monospace` e **ignora la
mono del tema** (`Azeret Mono`). Medido en navegador.
**El barrido mecánico que `--radius-xs` y `--font-size-4xl` pedían está
HECHO**: 5.793 nombres declarados en `src/uix/eidos` contra los
`var(--x, fallback)` de las recetas → **9 referencias fantasma**, tabla en
next-features §13. La más grave no es de esta familia:
`--color-content-tertiary` sigue vivo en TRES recetas (el caso histórico que
`recipe-contract` §1 cita como origen de su regla) y los cuatro
`--style-*-font-variation-settings` de `text.css` significan que el eje de
fuente variable **no llega nunca al primitivo `Text`**. Ninguna se arregla
aquí; el barrido debería ser un guard (misma lógica que el test de fantasmas
sin fallback, con el filtro de vocabulario de sistema encima).
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`text-gradient` 67 % → 100 %** (7 claves, entrada nueva).
Tercero de la familia text-* con la forma de `text-circular` / `text-focus`
(públicos «de facto» con fallback y sin declarar), pero la propuesta §4
generada fallaba en las tres decisiones y las tres se resolvieron midiendo
contra el CATÁLOGO, no contra el CSS de partida:
- **`weight` → `font-weight`**: 77 claves `font-weight` en el catálogo
contra 4 `weight` a secas, dos de ellas escritas el mismo 22 en esta
familia. Y `500` **es** `var(--font-weight-medium)`.
- **`calc(100% - 2px)` no son dos knobs de eje físico** (que es lo que la
ficha marcaba con ⚠) sino UNO: los 2 px son el trazo del marco asomando a
cada lado. Knob = `border-width` con `var(--border-width)` (el paso
`thin`, no un literal igual a un paso), y el hueco DERIVA de él.
- **`blur(4px)` → `var(--blur-sm)`**: `blur` es familia MÉTRICA del eje
`scaling` (theming §23) y un literal es ciego al zoom global.
Fuera del contrato por naturaleza: `duration` y `play-direction`, que el
wrapper escribe INLINE desde `animationSpeed`/`yoyo` — misma clase que el
`item-gap` de `carousel` y el `preview-z` de `drag-drop`.
**Dos puntos ciegos del instrumento, arreglados ANTES de medir** (la lección
de F2-B aplicada por adelantado): la demo arranca con `showBorder=false`, así
que sonda y centinela veían 2 nodos y NINGUNO de los tres tokens del marco;
y la sonda no leía `backdropFilter`, que el censo sí cuenta como knob. La
sonda gana un mapa `DEMO_VARIANTS` (chips de variante que se re-pulsan tras
cada recarga) y el centinela una entrada `openWith` que no abre un panel sino
que ENCIENDE UNA VARIANTE — un uso nuevo del mismo gancho.
Gates: 4 nodos · 720 valores × 7 estados · **0 diffs** · centinela **7/7 sin
excepciones** · censo `--only` 100 % y global 2.634 → 2.638 públicos ·
`--names` 7/7 conformes · audit PASS · eidos-lint 0 invalid · suite eidos con
el único rojo conocido (`skin-media-player`) · rtl 0 · docs 0 · `check` sin
errores en los ficheros tocados. Demo con pestaña **Tokens** (y fuera la
tabla de tokens escrita a mano, que este mismo renombrado dejaba obsoleta).
Abre (→ next-features §13, **pendiente de firma, apuntado para después**):
**la familia text-effects ACUÑA tipografía propia contra el canon** — cinco
claves (`text-focus.word-size` vía el fantasma `--font-size-4xl` y
`word-weight` · `text-circular.weight` y `font-size` ·
`text-gradient.font-weight`, ésta subida aquí), opiniones del seed que el
«valor verbatim» hizo contrato. Doctrina F2-B regla 2: se CONSUME la capa
(`--style-*`) o se HEREDA (`link`); un efecto de pintura sobre texto real no
tiene tipografía propia. Resolución propuesta: retirar las cinco y heredar
(mueve defaults → firma). El módulo de tipografía es canon y no se toca. Las
demos NO forman parte del eje (autor, 2026-08-22).
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(theming): correcciones de la revisión de F2-B — el empate no se resuelve de oficio Auditoría de la sesión con medición. Cinco correcciones; la primera toca código y es la que importa. 1. **`link-preview`: RESTAURADAS `font-family` y `line-height`.** Las retiré como «declaraciones muertas» apoyándome en los precedentes de `table.selected-row-fg` y del trigger de `gradient-picker`, pero **no son el mismo caso**: allí perdían por ESPECIFICIDAD, aquí empatan a (0,1,0) con el plano `[data-depth='overlay']` y decide el ORDEN DE CARGA. Retirarlas no es neutral — **fija el empate a favor del plano** y con él su 1.25 donde la receta pide `--leading-normal` (1.5), que es justo la decisión que §12.9 tiene pendiente para VEINTE componentes. Un empate por orden de carga se registra, no se resuelve dentro de un commit de tokenización. Y la medición era más débil de lo que el commit daba a entender: estable en cinco recargas **en DEV**, sin verificar en un bundle de producción. Restauradas → **0 diffs contra el estado PRE-SESIÓN** (550 valores × 5 tallas), censo de vuelta a 100 %, y las dos adjudicadas en el ledger con esa razón. §12.9 y el README dicen ahora que el par se deja declarado A PROPÓSITO. 2. **Marcas `R-5.x exception:` en 16 READMEs.** El gate del plan es «censo al 100 % o excepción escrita», y la válvula que `component-audit` lee es esa marca (`readmeException`), no la prosa. Tenía la razón documentada en veredicto, README y commit, pero sin la marca: cuando F3 active R-5.1/5.2, los 16 habrían dado error. Una marca por clase medida — forward de paleta THM-2 · conmutador de dos fuentes · eje propiedad de una capa · identidad de layout. Los tres al 100 % (`code`, `label`, `link-preview`) no la necesitan. 3. **Continuidad restaurada.** El handoff seguía diciendo «45 %» y «empieza por grid-list», y `PLAN §8` no tenía una sola línea de la sesión pese a que el protocolo lo pide por componente: una sesión nueva habría arrancado con el mapa viejo. Reescrita la agenda de `CONTINUE-theming.md` (cifras, la revisión adversarial pendiente COMO PRIMERA TAREA, la cola restante, las 8 firmas), registrado el bloque en `PLAN §8` con su tabla de 22 commits y sus seis lecciones, y actualizada la memoria del eje. 4. **`recipe-contract §4`** documenta las SEIS capacidades que R-5.4 ganó en el bloque, cada una nombrando la forma en que el instrumento mentía, más la lección que generaliza: una sonda que inspecciona pocos nodos pasa en falso — `picker-shell` medía CERO y reportaba éxito. 5. **«un paso por debajo desde lg» → «desde md»** en `tree-view`, `virtual-list` y `virtual-grid` (README + ficha). El escalón empieza en md (md→sm); los valores eran correctos (0 diffs), el texto no. Lo que esta revisión NO arregla, y queda como primera tarea del eje: **F2-B sigue sin su revisión adversarial §7.7**. Sus 21 componentes llevan sólo la medición de quien los escribió, y el plan la exige antes de cerrar un bloque. El handoff nombra las dos hipótesis concretas que hay que atacar. Gates: censo global 50 % · `--names` DESVIADAS 0 sobre 3.993 claves · `component:audit` 162 PASS / 4 NEEDS-WORK (los cuatro preexistentes, ninguno con un commit de esta sesión en su código) · R-5.4 verde en los 22 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **Bloque F2-B: 21 componentes tokenizados + 5 adjudicados como
«0 % estructural».** Global **45 % → 50 %**; sin contrato 49 → 29; al 100 %
7 → 10; alcance <20 % 43 → 22. Uno por commit, protocolo §7 entero, sonda
antes/después = 0 diffs en todos.
| componente | alcance | commit |
| --- | ---: | --- |
| `grid-list` | 0 → 85 % | `60b5b9896` |
| `textarea` | 0 → 80 % | `893aeaf81` |
| `tree-view` | 0 → 92 % | `dd188f125` |
| `code-block` | 0 → 81 % | `354d166df` |
| `card-group` | 0 → 90 % | `c56d6544e` |
| `spinner` | 0 → 70 % | `42a3e7413` |
| `picker-shell` | 0 → 76 % | `67b4c810c` |
| `link-preview` | 0 → 100 % | `7a0fc2ead` |
| `drag-drop` | 0 → 89 % | `650d76aa0` |
| `kbd` | 0 → 86 % | `adb77863c` |
| `link` | 0 → 36 % | `d3089e880` |
| `code` | 0 → 100 % | `e4bcd505d` |
| `virtual-list` | 0 → 67 % | `070145786` |
| `virtual-grid` | 0 → 73 % | `e3f39ce28` |
| `skeleton` | 0 → 45 % | `a6d4d4f5a` |
| `label` | 0 → 100 % | `7565eff47` |
| `announce` | 0 → 88 % | `d568577eb` |
| `clipboard` | 0 → 63 % | `151e5159b` |
| `scroll-frames` | 0 → 33 % | `1f692629e` |
| `mark` | 0 → 60 % | `9f24a31eb` |
| `menu-dial` | 0 → 25 % | `471ba96d2` |
| 5 estructurales (fichas) | 0 % FINAL | `ed2e3cd7c` |
**Lo que el bloque enseñó, y no estaba en el plan:**
1. **Un alcance bajo puede ser doctrina bien aplicada.** `menu-dial` 25 %
porque sus ejes de colocación son de la capa `viewport-placement` y la
receta escribe en sus RANURAS DE OVERRIDE; `link` 36 % porque sus
escotillas caen en `inherit`, que es semántico; `scroll-frames` 33 %
porque es layout puro. El % no es la métrica de calidad.
2. **Un privado con DOS fuentes es un conmutador, no deuda** — cinco casos
con el mismo veredicto (`textarea`, `spinner`, `skeleton`, `code`,
`label`): aplanarlo obligaría a duplicar cada regla por color.
3. **Lo que la capa tipográfica posee no se acuña** — cinco casos
(`code-block`, `kbd`, `announce`, `code`, `label`).
4. **Cinco componentes tienen 0 % POR NATURALEZA** y ninguna deuda:
`aspect-ratio` (faceta de Box, knob prestado), `text-blur` (sr-only),
`cascade` y `motion` (gate antiparpadeo del canal), `date-picker`
(corrección `max-content` de composición). El censo no los distingue de
la deuda real, **así que el gate de F3 «censo 100 %» es inalcanzable por
construcción** hasta que exista una clase `structural` — §13.
5. **La propuesta §4 generada hereda el incumplimiento del CSS de partida**:
copia el valor verbatim, y si ese valor ya violaba `recipe-css-contract`
(primitivo crudo en vez del bundle), la propuesta lo repite. Lo cazó la
suite en `code-block`.
6. **La inflación del generador puede ser 4×**: en `spinner` proponía 71
tokens para 23 knobs, multiplicando UNA coordenada de talla por
variante × parte × dimensión.
**El guard R-5.4 ganó SEIS capacidades**, todas nacidas de una medición y
con los componentes del ledger re-verificados sin regresión tras cada una:
puntero parqueado tras el blur (`reopen()` corre antes de cada token),
`::placeholder` en el snapshot (la nota de §13 que decía que no se lee era
falsa), `COMPONENT_OVERRIDES` de prefijo y selector de apertura, apertura
por `hover` para hover-cards, y `text-decoration-thickness` /
`-underline-offset` en la lista de propiedades.
uix(theming): revisión adversarial de F2-B — 4 hipótesis, 3 refutadas, 1 token que mentía El plan (§7.7) exige una revisión escéptica antes de cerrar un bloque, y F2-B no la tenía. Molde F2-A: worktree en la base del bloque (`8400e5bdd`) con su propio dev server, medición independiente. **REFUTADA \u2014 «el default cambió en algún sitio que la demo no mira».** 77 casos FORZADOS (variantes, formas, tallas y estados que las demos NO montan, en los 21 componentes) comparados base vs HEAD: **0 diferencias**. Era la sospecha más fuerte, porque cuatro componentes miden UN nodo (`link`, `code`, `skeleton`, `link-preview`) y sus tokens viven justo en las variantes que la demo no renderiza. **REFUTADA \u2014 «hay tokens huérfanos».** 339 públicos de los 21 bloques, 0 sin consumo. El primer extractor daba «SIN BLOQUE» en los 21 \u2014 el mismo antipatrón que audita este eje \u2014 y se rehízo contando llaves; muta-prueba: un token falso inyectado sale detectado y el árbol se restaura. **REFUTADA \u2014 «el gate de 0 diffs pasó sobre casi nada».** Contados los nodos que ve cada sonda; los cuatro de un nodo se cubrieron forzando sus variantes arriba. **HALLAZGO REAL \u2014 `drag-drop.preview-z` mentía, y lo escondía una adjudicación mía sobre un nodo SINTÉTICO.** El ledger lo daba por vivo («forzado → alcanza, 100 → 4321»), pero ese nodo lo había creado yo con JS. El preview REAL que soma monta durante un arrastre lleva `z-index: 9999` **INLINE** (`drag-drop-provider.svelte.ts:663,671`): el token resuelve a 4321 y el nodo computa 9999. Ninguna cascada puede ganarle. **Retirado con 0 diffs** contra el snapshot original (1.827 valores × 7 estados), misma clase que el `item-gap*` de carousel que retiró la revisión de F2-A. Era el único público que drag-drop tenía antes del eje; su alcance se mantiene en 89 %. **La lección que generaliza, y va al protocolo**: una adjudicación «forzado → alcanza» vale lo que valga el nodo que se forzó \u2014 si lo creaste tú, no lleva lo que soma le pone. Re-verificado el resto de la misma clase contra nodos reales: `clipboard` (indicador tras un clic real) y `picker-shell` (pie real) coinciden con lo adjudicado y no llevan inline; el inline de `skeleton-line` es de `inline-size`, no del eje tokenizado. DOS HALLAZGOS DE INSTRUMENTO en la propia revisión (→ §13): - **El orden de carga NO es el mismo en dev que en producción.** Medido con `npm run build` + preview y CDP: en dev la receta de `link-preview` va ANTES del plano `overlay`; en producción queda EN MEDIO de sus dos reglas. El ganador coincide por casualidad. La fragilidad de §12.9 queda medida en los dos entornos, y confirma que restaurar el par fue lo correcto. - **Un worktree cuyo `node_modules` es una junction fuera del root NO HIDRATA**: Vite responde 403 a `@fs/…` y SvelteKit no arranca, así que la página SSR se ve pero ningún componente monta. La primera pasada de esta revisión comparó contra una base MUERTA y devolvió «77 casos, 1 diferencia» sin que nada lo delatara. Se arregla con `server: { fs: { allow: [...] } }`; queda escrito en el handoff junto a la regla: **comprueba que un componente monta antes de comparar**. Gates: censo global 50 % · `--names` DESVIADAS 0 · drag-drop 89 % · component:audit PASS · eidos-lint 0 invalid · R-5.4 verde · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. Worktree eliminado quitando la junction ANTES del `remove --force`, con el `node_modules` real verificado intacto (memoria `git-worktree-remove-recurses-into-junctions`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
**Revisión adversarial §7.7 — HECHA 2026-08-22.** Molde F2-A: worktree en la
base del bloque (`8400e5bdd`) con su propio dev server, medición
independiente. **4 hipótesis · 3 refutadas · 1 hallazgo real.**
- **REFUTADA «el default cambió en algún sitio que la demo no mira»**: 77
casos FORZADOS (variantes, formas, tallas y estados que las demos NO
montan, en los 21 componentes) comparados base vs HEAD → **0 diferencias**.
- **REFUTADA «hay tokens huérfanos»**: 339 públicos de los 21 bloques, 0 sin
consumo. Con muta-prueba: un token falso inyectado sale detectado, y el
árbol se restaura.
- **REFUTADA «el gate de 0 diffs pasó sobre casi nada»**: se contaron los
nodos que ve cada sonda. Cuatro componentes miden 1 nodo (`link`, `code`,
`skeleton`, `link-preview`), y por eso se forzaron sus variantes en el
punto anterior — ahí es donde viven sus tokens, y salieron idénticos.
- **HALLAZGO REAL: `drag-drop.preview-z` mentía.** El ledger lo daba por vivo
con «forzado → alcanza», pero el nodo se había creado a mano; el preview
REAL que soma monta durante un arrastre lleva `z-index: 9999` INLINE, así
que ninguna cascada puede ganarle. Retirado con 0 diffs. Misma clase que el
`item-gap*` de carousel en F2-A. **La lección: una adjudicación «forzado →
alcanza» vale lo que valga el nodo que se forzó.** Re-verificado el resto de
la clase contra nodos reales (`clipboard`, `picker-shell`): correctos.
Dos hallazgos de INSTRUMENTO en la propia revisión, ambos registrados en §13:
el orden de carga **no es el mismo en dev que en producción** (medido con
build + CDP; el ganador coincide por casualidad), y un **worktree cuyo
`node_modules` es una junction fuera del root no hidrata** — Vite responde 403
a `@fs/…` y la primera pasada de esta revisión comparó contra una base
muerta sin que nada lo delatara.
uix(theming): correcciones de la revisión de F2-B — el empate no se resuelve de oficio Auditoría de la sesión con medición. Cinco correcciones; la primera toca código y es la que importa. 1. **`link-preview`: RESTAURADAS `font-family` y `line-height`.** Las retiré como «declaraciones muertas» apoyándome en los precedentes de `table.selected-row-fg` y del trigger de `gradient-picker`, pero **no son el mismo caso**: allí perdían por ESPECIFICIDAD, aquí empatan a (0,1,0) con el plano `[data-depth='overlay']` y decide el ORDEN DE CARGA. Retirarlas no es neutral — **fija el empate a favor del plano** y con él su 1.25 donde la receta pide `--leading-normal` (1.5), que es justo la decisión que §12.9 tiene pendiente para VEINTE componentes. Un empate por orden de carga se registra, no se resuelve dentro de un commit de tokenización. Y la medición era más débil de lo que el commit daba a entender: estable en cinco recargas **en DEV**, sin verificar en un bundle de producción. Restauradas → **0 diffs contra el estado PRE-SESIÓN** (550 valores × 5 tallas), censo de vuelta a 100 %, y las dos adjudicadas en el ledger con esa razón. §12.9 y el README dicen ahora que el par se deja declarado A PROPÓSITO. 2. **Marcas `R-5.x exception:` en 16 READMEs.** El gate del plan es «censo al 100 % o excepción escrita», y la válvula que `component-audit` lee es esa marca (`readmeException`), no la prosa. Tenía la razón documentada en veredicto, README y commit, pero sin la marca: cuando F3 active R-5.1/5.2, los 16 habrían dado error. Una marca por clase medida — forward de paleta THM-2 · conmutador de dos fuentes · eje propiedad de una capa · identidad de layout. Los tres al 100 % (`code`, `label`, `link-preview`) no la necesitan. 3. **Continuidad restaurada.** El handoff seguía diciendo «45 %» y «empieza por grid-list», y `PLAN §8` no tenía una sola línea de la sesión pese a que el protocolo lo pide por componente: una sesión nueva habría arrancado con el mapa viejo. Reescrita la agenda de `CONTINUE-theming.md` (cifras, la revisión adversarial pendiente COMO PRIMERA TAREA, la cola restante, las 8 firmas), registrado el bloque en `PLAN §8` con su tabla de 22 commits y sus seis lecciones, y actualizada la memoria del eje. 4. **`recipe-contract §4`** documenta las SEIS capacidades que R-5.4 ganó en el bloque, cada una nombrando la forma en que el instrumento mentía, más la lección que generaliza: una sonda que inspecciona pocos nodos pasa en falso — `picker-shell` medía CERO y reportaba éxito. 5. **«un paso por debajo desde lg» → «desde md»** en `tree-view`, `virtual-list` y `virtual-grid` (README + ficha). El escalón empieza en md (md→sm); los valores eran correctos (0 diffs), el texto no. Lo que esta revisión NO arregla, y queda como primera tarea del eje: **F2-B sigue sin su revisión adversarial §7.7**. Sus 21 componentes llevan sólo la medición de quien los escribió, y el plan la exige antes de cerrar un bloque. El handoff nombra las dos hipótesis concretas que hay que atacar. Gates: censo global 50 % · `--names` DESVIADAS 0 sobre 3.993 claves · `component:audit` 162 PASS / 4 NEEDS-WORK (los cuatro preexistentes, ninguno con un commit de esta sesión en su código) · R-5.4 verde en los 22 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
feat(calendar-surface): la capa que ya existía, con nombre, casa y su agujero tapado Firma 1 del acta, diseño presentado y firmado. **La medición desmontó el encargo**: el handoff la vendía como una capa NUEVA de ~220 knobs, y la capa ya existía de hecho, sin nombre. `--calendar-*` se emite en `:root` (76 claves) y sus consumidores no acuñan NADA — medido: `range-calendar` 114 referencias prestadas y 0 propias, `month-grid` 77/0, `year-grid` 77/0, y lo único ajeno que leen es sistema puro (`--focus-ring-*`, `--state-hover`). Su 0 % era el artefacto de `listbox` (§13), pero total. **`lib/calendar-surface.css`** (hook `data-calendar-surface`): - cuatro coordenadas por talla — `padding`, `control-size`, `day-size`, `font-size` — xs..lg, porque la familia NO tiene xl, y con la celda DOS pasos por debajo del bundle de control. Esa desviación estaba escondida en cuatro bloques `[data-size]` idénticos, uno por receta, cada uno puenteando a un privado con otro nombre; ahora se lee en un sitio. - la FORMA del anillo de evento y de la marca de festivo. **Capa HÍBRIDA, y es lo que la distingue de sus hermanas**: `list-surface` y `viewport-placement` componen primitivos del sistema, así que declaran sus públicos en el fichero y no tienen entrada de receta. Ésta no puede: su vocabulario son 76 claves SEMÁNTICAS que un tema alcanza una a una por config, así que la entrada `calendar` de `recipes/base.ts` pasa a ser la de la FAMILIA y la capa posee sólo lo que una entrada de receta no sabe expresar. **El defecto que la justificaba, medido**: `--calendar-event-shadow` y `--calendar-day-holiday-shadow` se emitían con ámbito `[data-calendar]` (audit B.2 los host-scopeó por buenas razones) mientras `range-calendar`, `month-grid` y `year-grid` los leían desde hosts que nunca llevan ese atributo: variable VACÍA, `box-shadow` inválido en computed, **el anillo sema de `commit-select` / `commit-set` no pintaba jamás en tres componentes**. Un token prestado cuyo ÁMBITO no te cubre no es un préstamo, es un agujero silencioso, y ningún guard lo veía. Ahora la forma vive en la capa y el acento entra por `--_calendar-surface-accent`, que cada superficie alimenta con su propio forward de paleta THM-2. **Siete wrappers estampan, no cuatro** — y esto casi se me cuela: `DatePicker` y `DateRangePicker` renderizan la superficie soma por sus PROPIOS wrappers (`date-picker-calendar`, `-month-view`, `-year-view`, `date-range-picker-calendar`) y un panel portalado no hereda nada del root del picker. Con sólo los cuatro standalone sellando, ambos quedaban con `--calendar-padding` VACÍA y el panel a padding 0 (medido). La tentación era enganchar la capa a las cuatro identidades de componente: eso viola la regla 1 de capas compartidas, y la respuesta correcta es un sello por wrapper. computed 0 diffs en range-calendar (19.285 valores) · month-grid (3.451) · year-grid (3.451) · date-picker (464) · date-range-picker (406). `calendar` da 12, y son del INSTRUMENTO: dos corridas del MISMO código dan 24 en los mismos nodos y las mismas dos propiedades. Los «missing node» son el propio sello entrando en la clave. **Una incidencia nueva, medida y NO arreglada aquí** (§13): el font-size de los selectores month/year es una moneda al aire — `[data-calendar-month-select][data-button]` (0,2,0) empata con `[data-popover-trigger]:not([data-archetype='field-trigger'])` (0,2,0), la MISMA regla de popover que dejó muerto el cromo de `gradient-picker`, y gana la hoja que cargue después: 16px o 14px según la recarga. Arreglarlo fija el píxel en un lado ⇒ decisión. De paso, `calendar-select.css` deja de puentear un privado que sólo `[data-calendar]` declaraba: en range / date-range corría SIEMPRE por el fallback, clavado a md fuera cual fuera la talla. **El censo deja de penalizar hacer lo correcto**: `LAYER_VOCABULARY` en `theming-census.ts`, mismo precedente que D-TH.2-b con `--style-*`. Global 43 % → **45 %**, `calendar` 75 % → **84 %**. `list-surface` NO se registra: sus consumidores puentean por privados, otra forma, y mueve diez componentes de golpe. Y aparece el techo de debajo, anotado: a los tres consumidores sólo les quedan los forwards de paleta THM-2 —que el censo cuenta como `private` en TODO el catálogo— así que siguen leyendo 0 %. `recipe-css-contract` aprende que una CAPA también declara públicos (antes sólo miraba la receta, y una capa que comparte prefijo con un componente la hacía fallar). Sin debilitarla: un nombre que no declara nadie sigue en rojo. eidos-lint 0 invalid (calendar 29/8 · range-calendar 41/12 · los grids 23/5) · audit --only calendar PASS · vitest eidos 434/435 (el rojo conocido) · rtl 0/181 · docs 0/813 · check 0 errores en tocados · prettier: revertido el reformateo en masa que se coló en cuatro README, el test y el censo (churn ajeno, no mío) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-21 — **La capa `calendar-surface`** (firma 1 del acta, diseño
presentado y firmado el mismo día). La medición desmontó el encargo: la capa
ya existía de hecho — `--calendar-*` en `:root`, y `range-calendar` /
`month-grid` / `year-grid` con 114 / 77 / 77 referencias prestadas y CERO
tokens propios. Se le dio nombre y casa (`lib/calendar-surface.css`, hook
`data-calendar-surface`, cuatro coordenadas xs..lg sin xl, celda dos pasos
bajo el bundle) y se cerró el defecto que la justificaba: las dos sombras del
anillo se emitían con ámbito `[data-calendar]` y los otros tres las leían
vacías, así que el anillo sema **no pintaba** en tres componentes. Capa
HÍBRIDA: el vocabulario se queda en la entrada de receta (76 claves
semánticas alcanzables por config), la capa posee la resolución por talla y
la forma del anillo. **Siete wrappers estampan**, no cuatro — los pickers
renderizan la superficie soma por los suyos y un panel portalado no hereda.
Censo: `LAYER_VOCABULARY` deja de penalizar consumir una capa; global 43 % →
**45 %**, `calendar` 75 % → **84 %**.
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-21 — **Revisión adversarial de F2-A (§7.7) + nace R-5.4.** Molde
Sidebar sobre worktree con solo el CSS revertido a la base: 34 hipótesis ·
26 refutadas · 8 reales, todas las de código arregladas con diff de computed
VACÍO (command 8.091 · carousel 2.842 valores). Retiradas: el re-point
muerto de `--command-radius` en el Dialog (su comentario mentía: el input
nunca lo leyó) y el trío `item-gap*` de carousel (el `gap` INLINE de soma
gana siempre — canal de valor, no knob). Tokenizado: el `2.25` vertical del
indicador activo. El instrumento viejo cargaba 22 falsos negativos de 26
«no effect» (clic-foco · override solo en `[data-{c}]` · pseudos) y la
no-determinación de `feed` era la demo cargando — cuatro causas arregladas.
**R-5.4**: `theming-sentinel.ts` + ledger de adjudicaciones, los ocho
fix(table): el striped cuenta filas de DATOS — la banda vuelve a existir Firmado por el autor tras la revisión adversarial: el hallazgo 4 (679dfdb29) midió que `:nth-of-type(even)` cuenta los <tr> hermanos, y con un RowDetail intercalado tras cada fila las filas de datos quedan todas impares — la demo publicada no bandeaba NADA (diez tr inmunes al token, medido uno a uno). El selector pasa a `:nth-child(even of [data-table-row])`, con dos decisiones de peso dentro: - **conserva el (0,6,0) del viejo**: el pseudo más el argumento `of` pesan exactamente lo que pesaba `[data-table-row]:nth-of-type()`, así que el hover —posterior en el fichero— sigue ganando su empate y la banda NO mata el hover. Subirlo de peso habría importado a table el defecto §12.4 de tree-grid. - **`:where(:not([data-selected]))` a peso CERO**: la fila seleccionada siempre estuvo prometida al acento («Overrides hover + striped», dice la regla de al lado a (0,3,0)) pero la banda vieja, de haber casado, se la habría comido por especificidad. El :where excluye sin pesar. Medido (sonda antes/después, 13.195 valores en 7 estados): **12 diffs exactos** — 2 filas pares × 6 estados, backgroundColor 0.9911 → 0.9551 — y nada más se movió. Interacciones verificadas en vivo: selected par conserva el acento (0.9588 con croma) · hover sobre par pinta su tinte (0.931). El ledger de R-5.4 pierde la excepción de `striped-row-bg` (era «genuinely unreachable, pending signature»): el guard adjudica ahora table 42/45 con 3 excepciones, exit 0, sin STALE. eidos-lint 0 invalid · audit --only table PASS · censo table 86 % intacto · vitest eidos 434/435 (el rojo conocido) · rtl 0 · docs 0 · check 0 en tocados · prettier limpio en lo tocado (el wrap de la regla del hover ya estaba desviado en HEAD y no se toca) Incidencia operativa, reparada y anotada: al retirar el worktree de medición de la revisión, `git worktree remove --force` recursó A TRAVÉS de la junction de node_modules y borró parte del real (node_modules/.bin incluido) antes de fallar; `npm install` lo reconstruyó y la suite volvió a 434/435. El árbol git nunca se tocó. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
componentes en verde con 46 excepciones medidas. El `striped` inerte se
firmó y ejecutó el mismo día: `:nth-child(even of [data-table-row])` +
`:where(:not([data-selected]))` al mismo (0,6,0) — 12 diffs exactos, hover
y selected verificados, la excepción del ledger retirada (table 42/45).
Pendiente de firma: el `item` en fila Y celda, y el gap de soma.
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4 Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba más dura que la del autor), sonda x2 por componente para fijar el suelo de ruido. 34 hipótesis · 26 refutadas · 8 reales. Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs; command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 · listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano» de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación; eidos-lint 0 invalid. Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command 8.091 y carousel 2.842 valores, 0 diffs): 1. command: el re-point de `--command-radius` dentro del Dialog era una declaración muerta con comentario falso — la misma regla pone el radio a 0 y el input lee `--command-input-radius`, nunca este token (medido: `--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px` movía la paleta inline y no la del dialog). Retirado; comentarios y README reescritos con lo medido. 2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo mientras la horizontal leía `active-indicator-scale` — el token alcanzaba media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora lo leen las dos. 3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap` INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es la prueba. Canal de valor de soma, como --gp-current-gradient. Censo carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto (5.190 · 2.102). 4. table: `striped-row-bg` no alcanza NADA en la demo publicada — `:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel → PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`); adjudicado en el ledger mientras tanto. El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas medidas, y la cuarta era de la sonda: - el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el token de reposo leía muerto (era el «falso negativo sin causa» de §13); - el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de `{c}-root` no lo recibían; - `::before`/`::after` eran invisibles (el aro de buffering, el spinner); - la no-determinación de feed no era una animación sin localizar: era la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma `commit-settle` anima box-shadow sobre el nodo medido. Con la espera de asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas. Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y que carousel convirtió en patrón — un estilo inline de otra capa es invisible para todo análisis estático): `npm run theming:sentinel -- <c> <url>` (scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en todos los nodos del componente, pseudos, pase de hover, settle) + ledger scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 · gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46 excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró; y dos muertos nuevos los produjeron los propios arreglos (el blur apaga focus-input-border; el settle desmonta el spinner de feed) — adjudicados con su porqué. Doctrina en recipe-contract §4. También: tres cifras de especificidad de next-features §12 recontadas (la banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0); y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el velo en la celda), el comentario de listbox que mandaba pisar el privado `--_listbox-max-height` apunta ahora al público, y quedan registrados en §13 los dos `header-z` con literal '2' y el spinner de media-player a 720ms mientras el gemelo de feed lleva tokens. Guards: censo global 43 % intacto · audit --only command+carousel PASS · eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs 0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en HEAD y no se tocan). Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el arquetipo en fila+celda, y que soma lea el gap de un token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Detalle en [`CONTINUE-theming.md`](./CONTINUE-theming.md) §«Registro».
docs(theming): el handoff, reescrito para que otra sesión arranque sin arqueología La sesión llega llena. El handoff había crecido por acumulación —cada bloque añadía su sección— y para saber qué hacer había que leerlo entero y deducirlo. Ahora la AGENDA va arriba y el registro histórico abajo, separado por su propio encabezado. Arriba: qué comprobar en los primeros cinco minutos (las dos cifras que deben cuadrar, el dev server antes de medir, leer el veredicto §5); tres opciones con recomendación —revisión adversarial de F2-A, el diseño de `calendar-surface`, o seguir la cola por alcance, con la cola ya recalculada—; el protocolo en diez pasos; y **lo que este bloque enseñó**, que es lo que evita repetir el día: el veredicto orienta pero la medición decide (tres de ocho tenían un error de lectura) · un token que no mueve nada miente, y lo que procede es retirar la declaración muerta · un alias puro se borra, no se renombra · lo que la capa posee el consumidor no lo acuña · y la sonda miente de tres maneras distintas, así que ante un diff inesperado se corre DOS VECES sobre el mismo código antes de sospechar del cambio. El plan §8 gana la entrada de la sesión con las cifras y los commits, y su cabecera de estado pasa a 43 %. Estado que hereda la sesión siguiente: alcance global 43 % (era 33 % al abrir el eje), 15 componentes con contrato, 49 sin él, 7 al 100 %, gramática con 0 desviadas y guard en `error`. 29 commits, sin pushear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-20/21 — **Las 14 firmas, el vocabulario y el bloque F2-A.** El día se
abrió presentando D-TH.6 y acabó con las catorce decisiones pendientes
firmadas en bloque, el vocabulario normalizado con guard, y ocho componentes
tokenizados. Detalle en [`CONTINUE-theming.md`](./CONTINUE-theming.md)
§«Registro de la sesión».
**El vocabulario** (`f09e04fab` acta · `bd916ef3f` codemod · `93017975c`
segunda pasada · `7781d6e4c` guard + doctrina · `476da2dd2` muro de tipos):
307 claves al idioma firmado —tinta `fg`, modificador interactivo delante—
con censo de alcance IDÉNTICO y 0 diffs de computed. R-5.3 entra en `error`
directo; el tipo de `defineRecipes` impide la regresión sin ejecutar nada.
La medición corrigió el inventario heredado: 370 «desviadas» eran 301, porque
47 claves `{rol}-{slot-de-rol}` son canónicas por construcción y un codemod
sobre ellas habría roto `button` entero.
**El bloque F2-A** (pieza 0 `bd032534b` + ocho componentes): global
**37 % → 43 %**, sin contrato **56 → 49**, al 100 % **6 → 7**. `command` 98 %
· `table` 86 % · `media-player` 88 % · `tree-grid` 95 % · `gradient-picker`
88 % · `listbox` 68 % (techo deliberado: su ritmo es de `list-surface`) ·
`carousel` 77 % · `feed` 94 %. Protocolo §7 entero en los ocho; diff de
computed vacío en todos.
**Lo que el bloque destapó y NO se arregló aquí** está en
[`next-features.md`](../next-features.md) §12 y §13: el contrato de cascada
del velo de estado (131 declaraciones-atajo en 45 componentes, la prop
`hoverable` que no suprime nada, un empate de especificidad resuelto por orden
de carga) y los huecos de instrumento y demo. La regla que el eje adoptó: si
una incidencia mueve píxel o toca morfo, se mide, se anota y se sigue.
**Tres veredictos de ocho tenían un error de lectura** que sólo apareció
midiendo, y esa es la lección de método del bloque: el veredicto orienta, la
medición decide.
Además: la demo enseña ahora el contrato de tokens (`90aa6dc5f`,
`TokensPanel` leyendo `getRecipeTokens()` en vivo) — hasta ese día ninguna
demo mostraba la superficie que este eje construye.
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-19 — Censo medido (§1) con `scripts/theming-census.ts` (añadido en
este commit como instrumento, sin guard). Plan entregado. **Nada firmado,
nada construido.** Precedente ejecutado hoy que fija la forma:
`navigation-menu` (`ae9e277fa`: tokens del trigger por talla, default
idéntico; `a82794845`: el hover es el del sistema; `822b78cf2`: el `<li>`
sin arquetipo) — y aun así el componente queda en 44 %, prueba de que hace
falta el guard.
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-20 — **Informe + primeros siete componentes** (`2591ec2f0` el informe
y el instrumento; `3bfe0d413` la revisión de las fichas 11-20). El censo pasa
a `--report`: `docs/audit/theming/` con un README de conjunto y **170 fichas**
(162 recetas + 8 sin receta), cada una con análisis, propuesta derivada de la
doctrina y un bloque de **veredicto** que se conserva al regenerar.
| Componente | Censo antes → después | Commit | Excepciones / lo que queda fuera |
| --------------------------- | --------------------: | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `gradient-builder` (piloto) | 0 % → 80 % | `3cdb5b5f0` | foco (sistema) + 3 literales de layout; damero tapado por el `background` inline del wrapper (anotado) |
| `combobox` | 0 % → 76 % | `cef2d32f1` | 3 hovers bespoke (esperan firma) + 6 literales; `content-font-family/-line-height` inertes bajo `[data-depth='overlay']` |
| `natural-time-picker` | 0 % → 61 % | `e774ca7e8` | cielos = excepción de tono fijo ya firmada; panel presta `--popover-*` por composición |
| `date-range-picker` | 0 % → 42 % | `07778b597` | mitad calendario (§B) + mitad campo (§5.3-3) |
| `time-range-picker` | 0 % → 19 % | `68eb8af26` | préstamos de Field/Slider/Toggle |
| `time-picker` | 0 % → 18 % | `e4fcdd62e` | préstamos de Field |
| `proof-of-human` | 0 % → 14 % | `ae91573c3` | escena = `FIXED_TONE_COMPONENTS`; hooks de clase (eje aparte) |
**Global: 33 % → 37 %** (1.621 → 1.841 públicos; 62 → 56 sin contrato).
Protocolo §7 aplicado entero en los siete: diff de computed **vacío** en todos
(6.612 · 1.566 · 6.438 · 493 · 725 · 580 · 406 valores, 7-8 estados cada uno),
centinela por token, `component:audit` PASS, `eidos-lint` 0 invalid,
`rtl:check` 0, `docs:check` 0, suite eidos con el único rojo conocido
(`skin-media-player`).
Cuatro defectos del método que la verificación destapó y que el handoff
recoge: **clave duplicada** en `recipes/base.ts` descarta el bloque en
silencio · dejar vivos los bloques `[data-size]` del CSS hace que el diff dé
0 por la ruta vieja · la sonda debe congelar `transition` pero **no**
`animation` (Presence no monta) · un token RESUELTO no se mueve desde `:root`
por diseño.
Prefijos abreviados o ajenos corregidos de paso (el censo los contaba como
`global` y se cruzarían al anidar): `--gb-*`, `--_ntp-*`, `--_time-field-*` en
los dos time-pickers, `--_date-field-*` y `--_calendar-font-size` en el
date-range-picker.
docs(theming): el plan del eje theme-reach — medir qué alcanza un tema, y corregir componente a componente El autor, al ver que el radio del trigger del nav vivía en un privado clavado a --radius-default: «¿los componentes son themables? si no lo son, es un error como framework». Lo es. La doctrina ya exige que cada receta declare sus knobs en recipes/base.ts (recipe-contract §1, theming §6), pero R-1…R-4 sólo comprueban «sin literales», no «alcanzable por un tema»: una receta con todo en var(--radius-md) y privados pasa component:audit en PASS y sólo se puede temar moviendo el sistema entero. Medido, no opinado — scripts/theming-census.ts (instrumento, NO guard): de 5.205 knobs de apariencia en 162 recetas, 1.621 (33 %) pasan por un token público del componente; 1.880 atan directo a un primitivo global, 856 a privados, 614 son literales; 62 componentes no tienen una sola entrada en el contrato; 59 tienen alcance < 20 %; 6 al 100 %. navigation-menu, tras todo el día de hoy, 44 %. El plan (PLAN-theming.md): §1 el censo por familia y por componente (tabla regenerable, no editable) · §2 los cuatro ejes de «personalizable» (tokens · talla · color/variante · estados) · §3 la regla R-5 «alcance de tema» para component-audit, con su válvula de excepción · §4 ocho decisiones D-TH para el autor (R-5 dura · perímetro de knob · WIP · orden · DEFAULT IDÉNTICO · normalizar nombres de slot, que hoy incumplen tabs y el propio nav · cuándo gradúa a error · panel de temas) · §5 fases F0–F4 con gates · §6 deuda transversal · §7 el PROTOCOLO DE VERIFICACIÓN por componente, porque «lo ejecutará Opus, y Opus falla mucho»: cinco preguntas, medir ANTES (computed por parte/estado/talla, sonda headless desde la raíz, fuera de callbacks de MutationObserver), editar sólo la forma firmada, medir DESPUÉS (diff de computed = 0, prueba de CENTINELA por cada token nuevo, desde el píxel hacia arriba, registro de animationstart/end), guards por fichero, un componente = un commit con sus artefactos, y revisión adversarial por bloque. Cada paso tiene un artefacto; sin artefacto no está hecho. El instrumento se probó por mutación antes de creerle, y falló la primera vez: no contaba una regla de una sola línea (`[x] { padding-inline: 8px; }`), así que meter un literal no movía la cifra. Regex endurecido; ahora 6→7→8 con una y dos mutaciones. Las cifras del plan son las del instrumento final, y el handoff (CONTINUE-theming.md) obliga a reproducirlas antes de seguir. Nada firmado, nada construido. Gates del commit: docs:check 0/642 · prettier del script limpio · tsc del script sin errores · el plan cita ficheros que existen (los once, comprobados). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## 9. Tabla completa por componente (2026-08-19 — regenerar con el script, no editar)
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Componente | Familia | Alcance | Knobs | Público | Privado | Global | Literal | Sistema | Contrato | size |
| --------------------- | ------------------------------- | ------: | ----: | ------: | ------: | -----: | ------: | ------: | -------: | :--: |
| `range-calendar` | Tiempo (chronos · fecha/hora) | 0% | 98 | 0 | 7 | 84 | 4 | 3 | 0 | y |
| `combobox` | Campos | 0% | 96 | 0 | 15 | 67 | 6 | 8 | 1 | y |
| `proof-of-human` | Controles · marcas | 0% | 95 | 0 | 18 | 36 | 37 | 4 | 0 | y |
| `time-range-picker` | Tiempo (chronos · fecha/hora) | 0% | 88 | 0 | 6 | 61 | 17 | 4 | 0 | y |
| `natural-time-picker` | Tiempo (chronos · fecha/hora) | 0% | 78 | 0 | 0 | 69 | 7 | 2 | 0 | y |
| `gradient-builder` | Campos | 0% | 62 | 0 | 8 | 46 | 3 | 5 | 0 | y |
| `month-grid` | Tiempo (chronos · fecha/hora) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| `year-grid` | Tiempo (chronos · fecha/hora) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y |
| `time-picker` | Tiempo (chronos · fecha/hora) | 0% | 59 | 0 | 3 | 42 | 12 | 2 | 0 | y |
| `date-range-picker` | Tiempo (chronos · fecha/hora) | 0% | 55 | 0 | 0 | 49 | 3 | 3 | 0 | y |
| `command` | Menús · listas · navegación | 0% | 52 | 0 | 9 | 35 | 6 | 2 | 0 | y |
| `table` | Menús · listas · navegación | 0% | 46 | 0 | 23 | 14 | 5 | 4 | 0 | y |
| `media-player` | Media · chat | 0% | 45 | 0 | 0 | 37 | 8 | 0 | 2 | y |
| `tree-grid` | Menús · listas · navegación | 0% | 42 | 0 | 29 | 8 | 2 | 3 | 0 | y |
| `field-langs` | Campos | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – |
| `gradient-picker` | Campos | 0% | 38 | 0 | 7 | 23 | 5 | 3 | 0 | y |
| `listbox` | Campos | 0% | 38 | 0 | 9 | 23 | 3 | 3 | 0 | – |
| `carousel` | Superficies · overlays · estado | 0% | 36 | 0 | 14 | 12 | 5 | 5 | 0 | y |
| `heading` | Texto · tipografía | 0% | 36 | 0 | 36 | 0 | 0 | 0 | 0 | – |
| `feed` | Menús · listas · navegación | 0% | 33 | 0 | 13 | 16 | 3 | 1 | 0 | y |
| `grid-list` | Menús · listas · navegación | 0% | 32 | 0 | 8 | 17 | 2 | 5 | 0 | y |
| `textarea` | Campos | 0% | 28 | 0 | 16 | 5 | 4 | 3 | 0 | y |
| `tree-view` | Menús · listas · navegación | 0% | 28 | 0 | 8 | 14 | 4 | 2 | 0 | y |
| `code-block` | Texto · tipografía | 0% | 26 | 0 | 2 | 24 | 0 | 0 | 0 | – |
| `card-group` | Superficies · overlays · estado | 0% | 24 | 0 | 0 | 18 | 4 | 2 | 0 | y |
| `text` | Texto · tipografía | 0% | 24 | 0 | 24 | 0 | 0 | 0 | 0 | – |
| `spinner` | Superficies · overlays · estado | 0% | 23 | 0 | 15 | 4 | 4 | 0 | 0 | y |
| `picker-shell` | Tiempo (chronos · fecha/hora) | 0% | 21 | 0 | 4 | 17 | 0 | 0 | 0 | y |
| `link-preview` | Superficies · overlays · estado | 0% | 17 | 0 | 4 | 9 | 1 | 3 | 0 | y |
| `drag-drop` | Controles · marcas | 0% | 16 | 0 | 3 | 6 | 0 | 7 | 1 | – |
| `kbd` | Controles · marcas | 0% | 14 | 0 | 1 | 9 | 4 | 0 | 0 | – |
| `link` | Controles · marcas | 0% | 14 | 0 | 4 | 2 | 5 | 3 | 0 | – |
| `code` | Texto · tipografía | 0% | 13 | 0 | 6 | 3 | 4 | 0 | 0 | – |
| `virtual-list` | Menús · listas · navegación | 0% | 13 | 0 | 2 | 6 | 4 | 1 | 0 | y |
| `virtual-grid` | Menús · listas · navegación | 0% | 12 | 0 | 2 | 6 | 3 | 1 | 0 | y |
| `skeleton` | Superficies · overlays · estado | 0% | 11 | 0 | 9 | 1 | 1 | 0 | 0 | y |
| `skin-media-player` | Media · chat | 0% | 11 | 0 | 0 | 2 | 7 | 2 | 0 | y |
| `scroll-frames` | Superficies · overlays · estado | 0% | 9 | 0 | 1 | 4 | 4 | 0 | 0 | – |
| `announce` | Superficies · overlays · estado | 0% | 8 | 0 | 0 | 8 | 0 | 0 | 0 | – |
| `clipboard` | Controles · marcas | 0% | 8 | 0 | 2 | 5 | 1 | 0 | 0 | – |
| `label` | Campos | 0% | 8 | 0 | 6 | 2 | 0 | 0 | 0 | – |
| `menu-dial` | Menús · listas · navegación | 0% | 8 | 0 | 3 | 1 | 4 | 0 | 0 | y |
| `display` | Texto · tipografía | 0% | 6 | 0 | 6 | 0 | 0 | 0 | 0 | – |
| `mark` | Controles · marcas | 0% | 5 | 0 | 2 | 1 | 2 | 0 | 0 | – |
| `aspect-ratio` | Controles · marcas | 0% | 3 | 0 | 0 | 1 | 2 | 0 | 0 | – |
| `date-picker` | Tiempo (chronos · fecha/hora) | 0% | 2 | 0 | 0 | 0 | 2 | 0 | 0 | – |
| `sticky` | Superficies · overlays · estado | 0% | 2 | 0 | 1 | 0 | 1 | 0 | 1 | – |
| `text-blur` | Texto · tipografía | 0% | 2 | 0 | 0 | 0 | 2 | 0 | 0 | – |
| `cascade` | Menús · listas · navegación | 0% | 1 | 0 | 0 | 0 | 1 | 0 | 0 | – |
| `motion` | Superficies · overlays · estado | 0% | 1 | 0 | 0 | 0 | 1 | 0 | 0 | – |
| `section` | Layout (primitivos) | 0% | 1 | 0 | 0 | 0 | 1 | 0 | 4 | – |
| `chronos` | Tiempo (chronos · fecha/hora) | 1% | 245 | 2 | 22 | 202 | 12 | 7 | 9 | y |
| `prose` | Texto · tipografía | 1% | 74 | 1 | 0 | 43 | 30 | 0 | 2 | y |
| `nav-tree` | Menús · listas · navegación | 4% | 25 | 1 | 0 | 17 | 6 | 1 | 1 | – |
| `s-text` | Texto · tipografía | 7% | 30 | 2 | 28 | 0 | 0 | 0 | 3 | – |
| `chart` | Controles · marcas | 9% | 92 | 8 | 0 | 66 | 14 | 4 | 17 | – |
| `result` | Superficies · overlays · estado | 9% | 22 | 2 | 1 | 15 | 4 | 0 | 8 | – |
| `mockup` | Superficies · overlays · estado | 14% | 29 | 4 | 7 | 13 | 5 | 0 | 0 | – |
| `menubar` | Menús · listas · navegación | 15% | 27 | 4 | 6 | 16 | 0 | 1 | 5 | y |
| `anchor-nav` | Menús · listas · navegación | 20% | 11 | 2 | 0 | 8 | 0 | 1 | 2 | – |
| `banner` | Superficies · overlays · estado | 20% | 10 | 2 | 6 | 1 | 1 | 0 | 42 | y |
| `button` | Controles · marcas | 21% | 30 | 6 | 16 | 0 | 7 | 1 | 105 | y |
| `palabras` | WIP (tracks paralelos) | 22% | 471 | 103 | 1 | 272 | 90 | 5 | 0 | – |
| `toggle` | Controles · marcas | 22% | 28 | 6 | 17 | 1 | 3 | 1 | 119 | y |
| `date-range-field` | Tiempo (chronos · fecha/hora) | 25% | 9 | 2 | 0 | 4 | 2 | 1 | 3 | – |
| `time-range-field` | Tiempo (chronos · fecha/hora) | 25% | 9 | 2 | 0 | 4 | 2 | 1 | 3 | – |
| `surface` | Superficies · overlays · estado | 25% | 4 | 1 | 3 | 0 | 0 | 0 | 25 | – |
| `callout` | Superficies · overlays · estado | 27% | 15 | 4 | 3 | 7 | 1 | 0 | 28 | – |
| `meter` | Superficies · overlays · estado | 29% | 14 | 4 | 6 | 2 | 2 | 0 | 28 | y |
| `knob` | Controles · marcas | 29% | 27 | 7 | 4 | 9 | 4 | 3 | 0 | y |
| `onion-menu` | Menús · listas · navegación | 29% | 27 | 7 | 0 | 8 | 9 | 3 | 12 | – |
| `empty-state` | Superficies · overlays · estado | 29% | 17 | 5 | 7 | 5 | 0 | 0 | 6 | y |
| `search-field` | Campos | 31% | 14 | 4 | 5 | 0 | 4 | 1 | 17 | y |
| `password-field` | Campos | 31% | 36 | 11 | 5 | 9 | 10 | 1 | 23 | y |
| `aura` | Superficies · overlays · estado | 31% | 35 | 11 | 4 | 14 | 6 | 0 | 24 | y |
| `toggle-group` | Controles · marcas | 33% | 3 | 1 | 0 | 1 | 1 | 0 | 1 | – |
| `background` | Superficies · overlays · estado | 35% | 26 | 8 | 6 | 6 | 3 | 3 | 31 | – |
| `avatar` | Controles · marcas | 36% | 36 | 13 | 17 | 0 | 6 | 0 | 0 | y |
| `audio-player` | Media · chat | 37% | 19 | 7 | 0 | 8 | 4 | 0 | 5 | – |
| `tooltip` | Superficies · overlays · estado | 38% | 22 | 8 | 9 | 3 | 1 | 1 | 20 | y |
| `button-group` | Controles · marcas | 40% | 5 | 2 | 0 | 0 | 3 | 0 | 1 | – |
| `badge` | Controles · marcas | 41% | 22 | 9 | 10 | 1 | 2 | 0 | 65 | y |
| `popover` | Superficies · overlays · estado | 42% | 51 | 20 | 6 | 22 | 0 | 3 | 36 | y |
| `qr-code` | Controles · marcas | 43% | 7 | 3 | 0 | 0 | 4 | 0 | 10 | – |
| `navigation-menu` | Menús · listas · navegación | 44% | 38 | 15 | 1 | 13 | 5 | 4 | 26 | y |
| `waveform` | Media · chat | 45% | 11 | 5 | 1 | 1 | 4 | 0 | 10 | y |
| `color-picker` | Campos | 46% | 82 | 36 | 10 | 22 | 11 | 3 | 54 | y |
| `checkbox` | Controles · marcas | 48% | 34 | 16 | 12 | 2 | 3 | 1 | 45 | y |
| `progress` | Superficies · overlays · estado | 49% | 35 | 17 | 9 | 3 | 6 | 0 | 41 | y |
| `tag-group` | Controles · marcas | 49% | 46 | 21 | 21 | 0 | 1 | 3 | 91 | y |
| `color-swatch` | Controles · marcas | 50% | 10 | 5 | 2 | 3 | 0 | 0 | 12 | y |
| `text-scramble` | Texto · tipografía | 50% | 4 | 2 | 0 | 0 | 2 | 0 | 0 | – |
| `split-button` | Controles · marcas | 50% | 2 | 1 | 0 | 1 | 0 | 0 | 1 | y |
| `timeline` | Superficies · overlays · estado | 51% | 49 | 25 | 21 | 0 | 3 | 0 | 45 | y |
| `field` | Campos | 51% | 73 | 35 | 15 | 7 | 11 | 5 | 60 | y |
| `context-menu` | Menús · listas · navegación | 53% | 33 | 16 | 0 | 11 | 3 | 3 | 18 | – |
| `rating-group` | Controles · marcas | 53% | 16 | 8 | 4 | 1 | 2 | 1 | 25 | y |
| `metrics` | Controles · marcas | 54% | 50 | 27 | 10 | 9 | 4 | 0 | 68 | y |
| `sidebar` | Menús · listas · navegación | 55% | 45 | 23 | 0 | 14 | 5 | 3 | 50 | – |
| `drawer` | Superficies · overlays · estado | 55% | 62 | 33 | 9 | 13 | 5 | 2 | 44 | y |
| `color-field` | Campos | 56% | 16 | 9 | 0 | 2 | 5 | 0 | 22 | y |
| `image` | Controles · marcas | 57% | 35 | 20 | 2 | 1 | 12 | 0 | 22 | y |
| `dropdown-menu` | Menús · listas · navegación | 57% | 30 | 16 | 0 | 9 | 3 | 2 | 19 | – |
| `switch` | Controles · marcas | 57% | 22 | 12 | 8 | 1 | 0 | 1 | 52 | y |
| `s-text-virtual-list` | Menús · listas · navegación | 58% | 12 | 7 | 0 | 3 | 2 | 0 | 9 | – |
| `form` | Campos | 59% | 52 | 30 | 14 | 5 | 2 | 1 | 54 | y |
| `float-panel` | Superficies · overlays · estado | 59% | 53 | 29 | 3 | 12 | 5 | 4 | 28 | – |
| `slider` | Controles · marcas | 59% | 33 | 19 | 12 | 0 | 1 | 1 | 39 | y |
| `radio-cards` | Controles · marcas | 60% | 72 | 42 | 0 | 24 | 4 | 2 | 39 | y |
| `card` | Superficies · overlays · estado | 60% | 36 | 21 | 14 | 0 | 0 | 1 | 97 | y |
| `cropper` | Controles · marcas | 60% | 27 | 15 | 0 | 3 | 7 | 2 | 15 | – |
| `emoji-picker` | Campos | 61% | 32 | 17 | 0 | 8 | 3 | 4 | 17 | – |
| `pin-input` | Campos | 61% | 19 | 11 | 0 | 6 | 1 | 1 | 20 | y |
| `editable` | Campos | 63% | 44 | 25 | 12 | 1 | 2 | 4 | 56 | y |
| `text-focus` | Texto · tipografía | 63% | 8 | 5 | 0 | 1 | 2 | 0 | 0 | – |
| `spin-field` | Campos | 64% | 27 | 16 | 0 | 8 | 1 | 2 | 20 | – |
| `file-upload` | Campos | 64% | 61 | 38 | 16 | 1 | 4 | 2 | 98 | y |
| `tags-input` | Campos | 67% | 46 | 28 | 13 | 1 | 0 | 4 | 90 | y |
| `toolbar` | Menús · listas · navegación | 67% | 35 | 22 | 10 | 0 | 1 | 2 | 54 | y |
| `image-adjustments` | Controles · marcas | 67% | 18 | 10 | 0 | 4 | 1 | 3 | 12 | – |
| `text-gradient` | Texto · tipografía | 67% | 9 | 6 | 0 | 0 | 3 | 0 | 0 | – |
| `separator` | SIN FAMILIA | 67% | 6 | 4 | 0 | 0 | 2 | 0 | 3 | – |
| `select` | Campos | 69% | 72 | 45 | 14 | 3 | 3 | 7 | 101 | y |
| `dialog` | Superficies · overlays · estado | 70% | 33 | 23 | 5 | 4 | 1 | 0 | 37 | y |
| `image-picker` | Campos | 71% | 20 | 12 | 0 | 1 | 4 | 3 | 13 | – |
| `pagination` | Menús · listas · navegación | 71% | 26 | 17 | 7 | 0 | 0 | 2 | 34 | y |
| `stepper` | Menús · listas · navegación | 71% | 60 | 40 | 15 | 0 | 1 | 4 | 69 | y |
| `skip-link` | Superficies · overlays · estado | 71% | 8 | 5 | 0 | 0 | 2 | 1 | 8 | – |
| `date-field` | Tiempo (chronos · fecha/hora) | 73% | 11 | 8 | 3 | 0 | 0 | 0 | 21 | y |
| `time-field` | Tiempo (chronos · fecha/hora) | 73% | 11 | 8 | 3 | 0 | 0 | 0 | 21 | y |
| `calendar` | Tiempo (chronos · fecha/hora) | 75% | 71 | 51 | 13 | 0 | 4 | 3 | 76 | y |
| `accordion` | Superficies · overlays · estado | 77% | 65 | 49 | 10 | 0 | 5 | 1 | 76 | y |
| `radio-group` | Controles · marcas | 77% | 75 | 56 | 10 | 7 | 0 | 2 | 61 | y |
| `chat-log` | Media · chat | 77% | 42 | 30 | 0 | 4 | 5 | 3 | 31 | – |
| `chat-composer` | Media · chat | 79% | 44 | 31 | 0 | 3 | 5 | 5 | 31 | – |
| `chat-message` | Media · chat | 82% | 84 | 63 | 2 | 6 | 6 | 7 | 75 | y |
| `fab` | Controles · marcas | 82% | 17 | 14 | 0 | 2 | 1 | 0 | 10 | – |
| `toast` | Superficies · overlays · estado | 83% | 42 | 35 | 6 | 0 | 1 | 0 | 85 | – |
| `text-circular` | Texto · tipografía | 83% | 6 | 5 | 0 | 1 | 0 | 0 | 0 | – |
| `tabs` | Menús · listas · navegación | 88% | 76 | 66 | 6 | 2 | 1 | 1 | 79 | y |
| `chat-typing` | Media · chat | 89% | 9 | 8 | 0 | 1 | 0 | 0 | 7 | – |
| `breadcrumb` | Menús · listas · navegación | 90% | 23 | 18 | 2 | 0 | 0 | 3 | 28 | y |
| `collapsible` | Superficies · overlays · estado | 90% | 12 | 9 | 0 | 1 | 0 | 2 | 11 | – |
| `scroll-area` | Superficies · overlays · estado | 94% | 17 | 15 | 0 | 0 | 1 | 1 | 13 | – |
| `splitter` | Superficies · overlays · estado | 100% | 19 | 18 | 0 | 0 | 0 | 1 | 16 | – |
| `chat-list` | Media · chat | 100% | 18 | 18 | 0 | 0 | 0 | 0 | 18 | – |
| `box` | Layout (primitivos) | 100% | 9 | 9 | 0 | 0 | 0 | 0 | 43 | – |
| `barcode` | Controles · marcas | 100% | 5 | 5 | 0 | 0 | 0 | 0 | 7 | – |
| `flex` | Layout (primitivos) | 100% | 2 | 2 | 0 | 0 | 0 | 0 | 7 | – |
| `grid` | Layout (primitivos) | 100% | 2 | 2 | 0 | 0 | 0 | 0 | 13 | – |
| `alert-dialog` | Superficies · overlays · estado | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `auto-grid` | Layout (primitivos) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `container` | Layout (primitivos) | — | 0 | 0 | 0 | 0 | 0 | 0 | 1 | – |
| `css-field` | Campos | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `float` | Superficies · overlays · estado | — | 0 | 0 | 0 | 0 | 0 | 0 | 1 | – |
| `group` | Layout (primitivos) | — | 0 | 0 | 0 | 0 | 0 | 0 | 1 | – |
| `highlight` | Controles · marcas | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `icon` | Controles · marcas | — | 0 | 0 | 0 | 0 | 0 | 0 | 2 | – |
| `mask-field` | Campos | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `number-field` | Campos | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `stack` | Layout (primitivos) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |
| `wrap` | Layout (primitivos) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – |

Powered by TurnKey Linux.