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

2104 lines
161 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`)
> **Estado (act. 2026-08-23): EN EJECUCIÓN — alcance global 65 %, 25
> componentes al 100 %, 24 sin contrato, 12 por debajo del 20 %. Vocabulario
> CERRADO (codemod + R-5.3 + muro de tipos), bloques F2-A y F2-B CERRADOS y la
> cola avanzando: dieciséis componentes el 2026-08-23. 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).
**6.bis — CUENTA LOS NODOS ANTES DE CREER NADA.** La sonda imprime cuántos mide;
el guard, cuántos tokens mueven. Un recuento de 0–2 nodos, o un 0/N del guard, NO
es un resultado: es el instrumento ciego. Las siete causas medidas hasta el
2026-08-23, con el interruptor que las arregla (todos en `COMPONENT_OVERRIDES`
del guard y en `EXTRA_NODES`/`DEMO_VARIANTS` de la sonda):
| síntoma | causa | interruptor |
| --- | --- | --- |
| 0 nodos, ruta 404 | el componente **no tiene demo** (picker-shell, mockup, surface, audio-player) | `urls: ['/ruta/donde/sí/vive']` |
| 0–2 nodos con la página llena | sus partes **no llevan `data-{c}-*`**: hooks de clase (onion-menu, prose) o partes de OTRO chasis (audio-player) | `extraNodes` / `EXTRA_NODES` |
| la superficie no está en el DOM | abre por **hover**, no por clic (tooltip, link-preview) | `openBy: 'hover'` |
| la superficie se CIERRA sola | nace abierta y el guard la pulsa (onion-menu) | `openMarker` |
| media docena de tokens muertos por variante | el panel/skin sólo existe tras un **chip de la demo** | `openWith` + `DEMO_VARIANTS` |
| todos los tonos muertos | la paleta resuelve por `data-color` **o `data-intent`**, y por VARIANTE | barrido de tono (automático) + `sweepAttr` |
| `TimeoutError` antes de medir | la página **nunca alcanza `networkidle`** (image reintenta un `src` roto) | caída a `load` (automática) |
**Y tres lecturas que mienten aunque el nodo esté bien**: una propiedad EN
TRANSICIÓN devuelve el valor viejo (congela antes de leer, salvo que el token SEA
la transición); un nodo ANIMADO devuelve el valor de la animación (congela
también `animation`, a mano); y `::placeholder` sobre un input sin `placeholder`
devuelve el estilo del ELEMENTO.
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.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.
**Nota 2026-08-23 (revisión `REVIEW-theming-2026-08-23.md`, D3)**: los pasos 6 y
13 —las capturas— **no se hicieron en ninguno de los 33 componentes** de los
bloques del 22 y el 23. No se recuperan retroactivamente: para esos, el artefacto
es el diff de computed en vacío más el centinela. **Se exigen desde el siguiente
componente**, y el paso 12 (píxel hacia arriba) es OBLIGATORIO en cuanto el
commit toque una regla `:hover` — que es donde el 2026-08-19 costó cuatro
commits de correcció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
### 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(chat-log): temable — 77 % → 100 %, y el instrumento daba DIECISÉIS falsos muertos de 31 Protocolo PLAN-theming §7 sobre `chat-log`. Censo 77 % → **100 %**, contrato 31 → **35** claves, centinela **34/35** con 1 adjudicada. El default NO se mueve: diff de computed VACÍO (1.920 valores · 7 estados · 9 nodos), capturas 2x antes/después idénticas. ## Lo caro no fue acuñar: fue VER El guard leía **15/31** antes de tocar una línea de código, y las dieciséis "muertas" estaban las dieciséis VIVAS. Tres cegueras, ninguna del componente: 1. **El viewport que scrollea es del componente COMPUESTO.** El log pinta su `padding-inline` / `padding-block` sobre `[data-virtual-list-viewport]`, un nodo sin un solo `data-chat-log-*`: fuera del filtro de la sonda y del guard. Cuarta forma del mismo punto ciego (prose · navigation-menu · waveform). 2. **La demo no montaba TRES de las seis partes.** Separador de día y divisor de no-leídos no existían en el escenario, y la fila de evento vivía en el índice 0 de una lista virtualizada anclada al FINAL — fuera de la ventana. Once claves sin nodo que pintar. La demo los monta ya cerca del final (instrumento: lo que la demo no monta, nadie lo mide). 3. **La pastilla del separador es un `> span` sin atributo** — seis claves más. Con las tres arregladas, **31/31 antes de acuñar nada**. ## Lo que entra (4 claves, la COSTURA de los cuatro globales) `divider-padding-block` · `event-radius` · `pill-gap` · `to-latest-radius`. El valor sigue siendo el del sistema; el knob pasa a ser del componente. Los nombres salen del CATÁLOGO, no del generador: la ficha proponía `divider-unread-padding-block` y `pill-new-gap` —nombra por el atributo de la parte— cuando el contrato lleva `divider-fg` / `-line` / `-font-size` / `-gap` y `pill-bg` / `-fg` / `-radius`. La familia manda. ## Lo que SALE: una regla que no ha pintado nunca `[data-chat-log-to-latest] svg { block-size: 1em; inline-size: 1em }` no puede ganar: el `Icon` compuesto emite `style="width: var(--icon-size-sm); height: ..."` EN LÍNEA, y un estilo en línea gana a todo selector. El glifo mide 16 px (paso `sm` del icono), no los 14 px que `1em` daría sobre la fuente heredada del botón. **Retirada: 0 diffs**, que es la prueba de que estaba muerta. Es la clase `carousel.item-gap` / `drag-drop.preview-z`, con una vuelta más: aquí el inline lo escribe un COMPONENTE COMPUESTO, no soma. ## Identidades firmadas (3, fuera del ratio) `block-size: 100%` del shell y del viewport (el log ocupa la caja que la app le da) e `inline-size: fit-content` de la pill (la dimensiona su contenido). ## Adjudicada (1) `event-radius` — sólo pinta bajo `:focus-visible` y el guard hace blur tras abrir. Medido enfocando con TECLADO la fila real, con `transition` congelada: 0 px en reposo, 4 px enfocada, 1234 px con el centinela. ## Guards censo `--only chat-log` 100 % (global 67 %, no baja) · `component-audit` PASS · `eidos-lint` 0 invalid · `vitest run src/uix/eidos` sin rojos nuevos (los dos vivos son ajenos: `skin-media-player` y `radio-group.row-min-height-touch`) · `rtl:check` 0 · `docs:check` 0 · `check` sin un solo error del componente · prettier limpio sobre el contenido normalizado a LF. README con su sección «Talla y tema», demo con pestaña `Tokens` verificada en navegador, ficha con su veredicto §5 y registro en PLAN §8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`chat-log` 77 % → 100 %** (COMMIT), contrato 31 → **35**
claves, centinela **34/35** con 1 adjudicada. **El instrumento daba 15/31
antes de tocar el código, y las dieciséis «muertas» estaban VIVAS las
dieciséis.** Tres cegueras, ninguna del componente: (1) el viewport que
scrollea es del VirtualList compuesto (`data-virtual-list-viewport`, cero
`data-chat-log-*`), donde el log pinta sus dos paddings — la cuarta forma del
punto ciego de prose / navigation-menu / waveform; (2) la demo no montaba
TRES de las seis partes (separador de día y divisor de no-leídos no existían;
la fila de evento vivía en el índice 0 de una lista virtualizada anclada al
final, o sea fuera de la ventana) — once claves sin nodo; (3) la pastilla del
separador es un `> span` sin atributo, seis claves más. Con las tres
arregladas: **31/31 antes de acuñar nada**. Lo acuñado es la COSTURA de los
cuatro globales: `divider-padding-block`, `event-radius`, `pill-gap`,
`to-latest-radius` — con los nombres del CATÁLOGO (`divider-*` / `pill-*`),
no los del generador (`divider-unread-*` / `pill-new-*`), que nombra por el
atributo de la parte. **Y una regla RETIRADA por muerta**:
`[data-chat-log-to-latest] svg { block-size: 1em }` nunca ha pintado porque
el `Icon` compuesto escribe `style="width: var(--icon-size-sm)"` EN LÍNEA —
el glifo mide 16 px, no los 14 px de `1em`; retirarla da 0 diffs, la prueba.
Es la clase `carousel.item-gap`, pero el inline lo pone un COMPONENTE
COMPUESTO, no soma. Tres identidades firmadas (`block-size: 100%` ×2,
`inline-size: fit-content`). Adjudicada: `event-radius`, que sólo pinta bajo
`:focus-visible` (medido con teclado sobre la fila real: 0 → 4 → 1234 px).
Diff de computed VACÍO (1.920 valores · 7 estados · 9 nodos).
uix(editable): temable — 75 % → 82 %, y el guard CERRABA la superficie que medía Protocolo PLAN-theming §7 sobre `editable`. Censo 75 % → **82 %**, contrato 56 claves, centinela **42/56** con 14 adjudicadas. El default NO se mueve. ## Lo que costó medirlo: dos superficies y una apertura frágil al revés - **Preview e input comparten celda de rejilla y se excluyen** (`display: none`), igual que los triggers (Edit en reposo, Save/Cancel editando): una pasada deja fuera la mitad de la receta. Todo se midió DOS veces. - **Salir del modo edición es lo que hace un `blur`** —da igual lo que diga `submitMode`, esa opción sólo decide si el valor se compromete— y el guard blurea justo después de abrir. Abriendo con clic en el trigger, la superficie ya estaba cerrada cuando se medía: `submit-fg` leía muerto y mueve perfectamente abierta (`rgb(255,255,255)` → `rgb(1,2,3)`). Se abre por FOCO, con la demo en `activationMode=focus`, que es la apertura que el guard no deshace. Misma ley que `navigation-menu`, con el gesto invertido. ## Lo que entra al contrato (1 clave) `disabled-fg` — la tinta apagada de TODO el componente: preview, input y los tres triggers comparten UNA regla, así que el nombre desnudo del catálogo es el que toca (`label` y `select` ya lo llevan con este mismo valor). Era el único knob que iba a un primitivo a pelo, y encima con un respaldo escrito a mano —`var(--color-content-disabled, var(--color-content-muted))`— que estaba MUERTO: el token sí está declarado, así que el respaldo no podía pintar nunca. Retirado con él. ## Lo que sale (1 clave huérfana) `control-border`: ninguna regla lo consumía. El borde en reposo del control es `transparent` por diseño y el del input es el acento (`--_editable-palette-border`), así que esa clave pública prometía un knob que no existe — la deuda INVERSA del eje. Diff 0, y el centinela lo confirmaba muerto. ## Dos literales de identidad firmados El `fit-content` del preview (es tan ancho como el texto que enseña; la ficha proponía acuñarlo como `preview-width`, que sería acuñar la definición de la parte) y el `100 %` del input (llena la celda que comparte con el preview). ## Lo que se queda privado (el techo honesto es 82 %) Los SIETE knobs por privado son el puente de paleta THM-2 (`--_editable-palette-{solid,border,solid-hover}`), que la capa compartida alimenta por instancia desde `[data-color]`: un público encima dejaría que un tema lo fijara y matara el `color=` de cada instancia. Otros cuatro son sistema transversal (anillo de foco y velo de hover), ya fuera del ratio. ## Las catorce adjudicaciones, en dos razones — y una tercera que casi se cuela - El par `transition-*` **ES** la transición que el guard congela; medido sin congelar: `0.12s → 4.321s` y `cubic-bezier(0.4, 0, 0.2, 1) → steps(3)`. - Las doce claves de tono son la cascada de paleta, por PATRÓN y con el mecanismo a la vista: el nodo lleva `--palette-solid` de la capa por instancia, el bloque genérico `[data-editable][data-color]` —emitido EL ÚLTIMO, misma especificidad— lo lee, y el respaldo que nombra la clave del componente no llega nunca. Quítale `data-color` a la misma instancia y `--editable-primary-solid` vuelve a pintar (`rgb(1, 2, 3)`). - ⚠ **`risk-border` NO cae en ese patrón**: las tres reglas de inválido la leen DIRECTAMENTE, así que su silencio tiene otra causa —la demo es válida— y entrada propia: forzando `data-invalid` alcanza en el input Y en el control. Una excepción por patrón que engulle una clave con vida propia es una adjudicación falsa. ## Artefactos - Sonda: **0 diffs** en las dos caras — modo preview (1.696 valores, 7 estados) y modo edición (2.528 valores, 8 estados). - Captura 2× del reposo idéntica al byte. La de edición NO es comparable por bytes: la traza de la demo imprime un TIMESTAMP, así que dos corridas del mismo código difieren (comprobado antes de creerme el diff). Revisada a ojo. - `component-audit --only editable`: **PASS** · `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED, 0 errores en `editable`. - README «Talla y tema» + pestaña `Tokens` (56 filas, verificada en el navegador con `networkidle` y clic real). ## Defecto anotado, NO arreglado El tinte inválido ES la clave de tono (`--editable-risk-border`): dos knobs con una sola llave — un tema que quiera otro color de error mueve también las instancias `color="risk"`. El arreglo es una clave de estado por parte (`invalid-input-border` / `invalid-control-border`, la forma que `switch` acuñó el mismo día) y es contrato: se lista. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`editable` 75 % → 82 %** (COMMIT), contrato 56 claves,
centinela **42/56** con 14 adjudicadas. **Su apertura es frágil AL REVÉS**:
salir del modo edición es lo que hace un `blur` —da igual lo que diga
`submitMode`, esa opción sólo decide si el valor se compromete— y el guard
blurea justo después de abrir, así que abrir con clic medía una superficie ya
cerrada (`submit-fg` leía muerto y mueve perfectamente abierta). Se abre por
FOCO, con la demo en `activationMode=focus`. Además tiene **DOS superficies
excluyentes** (preview/input comparten celda; Edit contra Save+Cancel), así
que todo se midió dos veces: diff 0 en modo preview (1.696 valores) y en modo
edición (2.528). Entra **`disabled-fg`** —la tinta apagada de TODO el
componente, una sola regla; era el único knob a primitivo crudo y traía un
respaldo `, var(--color-content-muted)` MUERTO— y sale **`control-border`**,
clave pública que no consumía ninguna regla (deuda inversa). Dos literales de
identidad firmados. Los 7 privados son el puente de paleta THM-2: **82 % es su
techo**. ⚠ **`risk-border` NO cae en el patrón de paleta**: las tres reglas de
inválido la leen directamente, así que su silencio tiene otra causa y su
propia adjudicación —forzado `data-invalid` alcanza en input y control—; una
excepción por patrón que engulle una clave con vida propia es una adjudicación
falsa. **Defecto anotado**: el tinte inválido ES la clave de tono, o sea dos
knobs con una sola llave; el arreglo es `invalid-{parte}-border` (la forma que
`switch` acuñó el mismo día) y es contrato.
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela **83/88** con 5 adjudicadas. El default NO se mueve. ## Lo primero: el instrumento no veía el contrato La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el `return {`, un tabulador más adentro. Consecuencia medida: - el censo la leía como «sin entrada en base.ts» — 84 claves invisibles; - el centinela **moría** con `no recipe block for avatar`: el componente no se podía medir, y su gate no significaba nada. Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo lo demás de este commit habría sido humo. ## Lo que entra al contrato (6 claves) `group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`. Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por `--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 · −16,8 · −22,4 · −33,6 px → 37 px, uno a uno). ## Lo que se retira (2 declaraciones muertas, diff 0 las dos) - `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta declaraba su default `99`. **No lo leía nadie** — el tope se aplica con `data-has-max` + `:nth-child(n + M)` porque una variable no entra en `:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos sitios; el `+3` del grupo sigue exactamente donde estaba. - el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara `--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue computando `rgb(255, 255, 255)`. ## Seis literales firmados (salen del ratio) Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo centrado por la caja flex. ## Lo que se queda privado (el techo real es 90 %) `--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian de fuente con la variante (solid · soft · outline) y su valor sale del forward de paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`. Un público encima dejaría que un tema los fijara y matara el `color=` de cada avatar. ## Artefactos - Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576 valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072). - Capturas 2× antes/después de las dos demos: **idénticas al byte**. - Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y `font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza — el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas). - El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de `data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en su propia ruta (21 muertos → 5). - `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED sin un solo error en `avatar`. - README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador con `networkidle` y clic real). ## Lo que NO se arregla aquí (anotado) - **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que renombrarlas cambia el contrato público de tres canales: se lista, el audit sigue en NEEDS-WORK por esto y sólo por esto. - **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token en `:root` y también sobre cada nodo, y para una propiedad personalizada que la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo. - **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE las seis excepciones de `metrics`, así que se hace con la re-verificación del ledger entero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`avatar` 75 % → 90 %** (COMMIT), contrato 84 → 88 claves,
centinela **83/88** con 5 adjudicadas. **Su contrato existía y NINGÚN
instrumento lo veía**: es la única entrada de `base.ts` construida por una
IIFE (un helper local genera sus 24 ámbitos compuestos), así que su mapa vive
en el `return {`, un tabulador más adentro — el censo la daba «sin entrada» y
**el centinela moría** (`no recipe block for avatar`). Los dos lectores leen
ya la IIFE. Lo demás: **`group-overlap-{xs..xxl}`**, porque la clave única
`--avatar-group-overlap` la RE-DECLARABA la receta en seis bloques
`[data-size]` —sentada en el elemento, ganaba al `:root` de un tema: medido,
no movía nada desde el asiento del tema, y **el centinela lo daba por vivo
porque escribe el token también sobre el nodo** (falso positivo nuevo, §13)—;
**`group-max` RETIRADO** de la receta y del envoltorio (el tope lo aplica
`data-has-max` + `:nth-child(n + M)`, y la variable inline no la leía nadie);
el respaldo `, white` de `--_avatar-badge-fg`, muerto contra su propio token;
y **seis literales de identidad firmados**. Los 3 privados que quedan son el
CONMUTADOR de variante sobre el forward de paleta THM-2: 90 % es su techo.
Artefactos: diff de computed 0 en tres bases (576 + 384 + 3.072 valores),
capturas 2× idénticas al byte, `rtl:check` 0, `docs:check` 0, `check` sin un
solo error en `avatar`. **Queda R-5.3 preexistente**: cuatro claves
`*-color-custom*` donde la gramática exige `fg` — renombrarlas cambia tres
escotillas públicas, así que se lista.
docs(theming): la revisión adversarial §7.7 de los 26 commits — 5 hipótesis, 5 refutadas Cierra el punto P5 del plan de la revisión: lo ejecutado desde el 22 (33 componentes, dos bloques) por fin tiene su pasada escéptica, con medición propia y no leída de los commits. LA BASE, ANTES DE COMPARAR NADA Worktree en cd6a9f4e3 con dev server propio, node_modules por junction y `server: { fs: { allow: [...] } }` — sin eso Vite responde 403 a /@fs/… , la página NO hidrata y se compara contra una base muerta (la trampa que el handoff registra). Comprobado que los componentes MONTAN e hidratan antes de medir. H1 · «el default es idéntico» — 33 de 33 31 con 0 diffs, ~48.000 valores computados en 7-8 estados por componente. `surface` daba 0 valores por una URL mía equivocada: en su ruta real (/temas/gradientes) 0 diffs sobre 896. Y los dos con diffs son RUIDO DEL INSTRUMENTO, probado corriendo dos veces el MISMO código: `image` reproduce su único diff y `audio-player` da ONCE (más que la comparación base↔HEAD). Las dos propiedades son estado vivo — la opacity de un <img> con animación de entrada (la sonda congela transition, nunca animation) y el ancho del rango buffered de un audio. En §13, con la recomendación de excluirlas del snapshot. H2 · «el velo cae donde debe» — verde en los cuatro que tocaron :hover Píxel hacia arriba: dropdown-menu y context-menu pintan el velo en su item (radio 4px, el nodo CON forma) y el list-surface de debajo queda limpio; menubar pinta el abierto en su propio <button> sin derramar al contenedor, y la fila de su panel en el item; anchor-nav no tiene velo en ninguna capa, que es lo correcto — su hover es sólo tinta. Medido sobre un enlace NO activo: el primero que casa el selector lleva data-active y pinta otra cosa. H3 · «no hay doble animación» — no aplica Ningún commit de los 26 toca animation, @keyframes, data-event ni motion-* en components/. No hay sello movido que pueda duplicarse. H4 · «cada token alcanza» — rehecho, y destapó float-panel El barrido del centinela sobre los 59 con ledger: 0 STALE, 0 errores, un solo rojo (float-panel), ya arreglado en 233ef989b. H5 · «el contrato no tiene duplicados ni huérfanos» — limpio 144 bloques sin duplicados, sin claves repetidas dentro de un bloque, y el test oficial de huérfanos en verde. ⚠ Y aquí mintió mi propio instrumento: un guard estricto de un solo uso reportó 185 huérfanos —los pasos de escala por talla y todos los tonos— porque excluía `generated/`, que es exactamente donde el TSC emite el nombre resuelto y el forward de paleta lee los tonos. Un guard de huérfanos que no mira el CSS GENERADO ve huérfano todo lo que el generador resuelve. En §13. VEREDICTO: 0 hallazgos nuevos. Los reales de esta revisión salieron del análisis ESTÁTICO (E1-E5) y ya estaban arreglados antes de esta pasada. El handoff abre ahora con las cuatro lecciones que cambian cómo se trabaja: correr el guard sobre el ledger entero · el nombre de un token no es el de la propiedad · si un paso del protocolo no está en el commit, no se hizo · y comprobar que la base MONTA antes de comparar contra ella. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **REVISIÓN DE LOS 26 COMMITS SIN REVISAR (§7.7 incluida)**:
`REVIEW-theming-2026-08-23.md`. 33 componentes, los de los bloques del 22 y el
23. **5 errores reales, 5 desviaciones de protocolo, y la §7.7 con 5 hipótesis
y 5 refutaciones.** Todo arreglado el mismo día:
- **E5 — `float-panel` tenía DOCE rojos sin adjudicar y el guard fallaba en
él** (`233ef989b`). Su commit del 22 los dejó escritos como «lo rojo que
queda» y el handoff dio por cerrado el bloque viejo: la frase era FALSA.
Seis son el plano `overlay` (§12.9), tres estados que la demo no monta, uno
el puente de paleta y dos la ley del congelado. **Sólo se ve corriendo el
guard sobre el ledger ENTERO, no sobre lo que tocas.**
- **E1 — `badge` acuñó la clave con el nombre de la PROPIEDAD**
(`min-block-size-{k}`) donde la ranura es `height` (`bfe7727b0`). 24
componentes dimensionan por `…-height-{k}`; era el único. **R-5.3 no guarda
el vocabulario dimensional** — §13.
- **E2 — una adjudicación mal clasificada**: `background.parallax-travel` no
era «estructural», es que **el guard no fotografía `translate`** — §13.
- **D1 — la sesión de la mañana se saltó los pasos 8 y 9 en los 22
componentes**: ni README «Talla y tema» ni pestaña `Tokens` (`e522e04e6`,
tabla DERIVADA del contrato y del ledger, 21 pestañas verificadas en el
navegador).
- **§7.7**: base en worktree con `server.fs.allow` (sin él no hidrata y se
compara contra una base muerta), **31 de 33 con 0 diffs sobre ~48.000
valores**; los dos con diffs (`image`, `audio-player`) son ruido
reproducible del instrumento —dos corridas del MISMO código dan 1 y 11—;
píxel-arriba verde en los cuatro que tocaron `:hover`; sin dobles
animaciones (ningún commit toca `animation`); contrato sin duplicados ni
huérfanos.
uix(file-upload): temable — 76 % → 83 %, y una clave suya NO PINTABA en ninguno de sus tres botones Una clave acuñada, UNA RETIRADA con su declaración muerta, cuatro literales firmados y cuarenta y cuatro adjudicaciones medidas. El default no se mueve: diff de computed 0 sobre 11.448 valores en 17 estados y capturas 2x idénticas byte a byte. Censo: 76 % → 83 % (público 45, global 1 → 0, literal 4 → 0, excepción 0 → 4; knobs 61 → 56). Contrato 98 claves (una entra, otra sale). Global no baja. QUÉ ENTRA (1 clave) invalid-dropzone-border (var(--color-risk-border)). Era el único knob de apariencia que iba a un primitivo global a pelo. Lleva la PARTE en el nombre y no el `invalid-border` desnudo que el catálogo repite seis veces (checkbox, listbox, grid-list, radio-cards…): aquí hay TRES partes con borde —dropzone, botón e ítem— y el nombre desnudo no diría cuál tiñe. La forma modificador + parte + ranura ya está en el catálogo (invalid-input-border, invalid-control-border). Su estado no lo alcanza esta ruta: isInvalid sale de un Field envolvente y la demo no monta ninguno. Forzado: oklch(0.8059 0.1123 59.96) -> rgb(1,2,3). QUÉ SALE (1 clave, y su declaración) button-bg NO PINTABA. La regla compartida de los tres botones declaraba `background: var(--file-upload-button-bg)` y los tres la pisan después: el trigger con el sólido de la paleta, ItemRemove y ClearTrigger con transparent. Medido sobre los tres nodos, uno a uno: el centinela no mueve NADA en ninguno. Con UNA clave la respuesta de F2-A es retirar, no adjudicar — y retirarla da diff 0, que es la prueba de que estaba muerta. La clave sale con ella: un público sin consumidor es un huérfano. CUATRO LITERALES FIRMADOS (fuera del ratio) Los tres 100% de identidad —el campo ocupa el ancho que le dan, la miniatura llena la caja en la que se recorta, la barra ocupa la fila del grid— más el block-size de la miniatura. Cada uno con su anotación en la declaración (válvula de recipe-contract §3). CUARENTA CLAVES DE TONO NO PINTAN 8 tonos x 5 ranuras (solid, solid-hover, track, border, text), 40 de sus 98. Es la cascada de paleta: el forward emite AL FINAL un bloque genérico [data-file-upload][data-color] que resuelve desde el --palette-* global, misma especificidad (0,2,0), gana el último. Medido para los ocho tonos sobre las dos superficies que las consumen —el trigger (solid) y el dropzone en hover (track)—: ninguna se mueve, mientras --palette-solid sobre el nodo repinta. Adjudicadas POR PATRÓN; incidencia de fondo en next-features §13. FUERA POR DOCTRINA (los 9 privados) Ocho son el puente de paleta THM-2 y el noveno es EL CANAL DE VALOR (--_file-upload-progress-value, que el envoltorio escribe inline por fila). Lo que el componente escribe inline no es tema, y un público sobre el puente dejaría que un tema fijara el tono y matara el color= de cada instancia. Su techo honesto NO es el 100 %. VERIFICACIÓN Sonda estándar antes/después 0 diffs (4.608 valores · 8 estados · 19 nodos: la demo arranca con dos ficheros sembrados, así que monta la superficie entera) + barrido de los estados que la ruta no arranca (3 variantes x 3 tallas, invalid, disabled, dragging) 0 diffs sobre 6.840 valores · capturas 2x idénticas byte a byte · R-5.4 54/98, exit 0, sin STALE · censo --only 83 % · component:audit PASS · eidos-lint 29 morfo-backed / 0 invalid · vitest src/uix/eidos 35/36 (el rojo conocido es skin-media-player) · rtl:check 0 · docs:check 0 · prettier limpio · npm run check sin errores en ficheros del componente · pestaña Tokens verificada en el navegador (98 filas, 0 errores de consola). ⚠ Truco de medición nuevo: el «antes» del barrido de estados se reconstruye INYECTANDO el CSS de HEAD en la página (gana por orden a igual especificidad), sin tocar un árbol que otras tres sesiones están usando. Con muta-prueba: 19 diffs al alterar el control, 1 si sólo se altera el borde inválido. Índice construido a mano desde HEAD sobre los tres ficheros compartidos (recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts). Fuera del commit, como en el de `form`, docs/audit/theming/README.md — es un agregado global que mezcla trabajo en vuelo de otras sesiones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`file-upload` 76 % → 83 %** (COMMIT), 1 clave acuñada y **1
RETIRADA**. `invalid-dropzone-border` (con la PARTE en el nombre: tres partes
con borde) era el único knob a un primitivo a pelo. **`button-bg` no pintaba**:
la regla compartida de los tres botones la declaraba y los tres la pisan
después (el trigger con el sólido de la paleta, `ItemRemove` y `ClearTrigger`
con `transparent`) — medido sobre los tres nodos, cero movimiento, así que se
retira la declaración Y la clave, y el diff 0 lo prueba. **Cuatro literales de
identidad firmados** (el ancho del campo, los dos de la miniatura, el de la
barra). **CUARENTA claves de tono** (8 × 5 ranuras) adjudicadas por patrón: la
cascada de paleta, medida sobre el trigger y el dropzone en hover para los
ocho tonos. Los 9 privados son 8 puentes THM-2 + **el canal de valor**
(`--_file-upload-progress-value`, inline por fila), así que **su techo honesto
no es el 100 %**. R-5.4 54/98, exit 0. Diff 0 sobre **11.448 valores en 17
estados** y capturas 2× idénticas byte a byte. ⚠ **Truco de medición nuevo**:
el barrido de estados que la demo no arranca (variante × talla × invalid ×
disabled × dragging) reconstruye el «antes» **inyectando el CSS de HEAD en la
página** —gana por orden a igual especificidad—, sin tocar el árbol que otras
tres sesiones están usando; con su muta-prueba (19 diffs al alterar el control,
1 al alterar sólo el borde inválido).
uix(switch): temable — 76 % → 81 %, y su tono NEUTRAL cae con los otros once Una clave nueva (51 → 52) y veinticinco adjudicaciones medidas: trece propias y doce por patrón. El default no se mueve: diff de computed 0 sobre 1.344 valores en 36 estados y capturas 2x idénticas. Censo: 76 % → 81 % (público 16 → 17, global 1 → 0; 22 knobs, 4 privados y 1 sistema fuera). Global 67 % (no baja). QUÉ ENTRA (1 clave) invalid-track-border (var(--color-threat-element)). Era el ÚNICO knob de apariencia que iba a un primitivo global a pelo. El nombre NO es el `invalid-border` que el catálogo repite seis veces (checkbox, listbox, grid-list, radio-cards, date-range-field…): esta receta tiene DOS partes con borde y las nombra — su hermana de estado es `disabled-track-border` y el pulgar lleva `thumb-border`, así que un nombre desnudo no diría cuál de las dos tiñe. La forma modificador + parte + ranura ya está en el catálogo (`invalid-input-border`, `invalid-control-border`). DOCE CLAVES DE TONO NO PINTAN — Y AQUÍ CAE TAMBIÉN «NEUTRAL» Es la cascada de paleta ya medida en button / badge / callout: el forward emite al FINAL un bloque genérico [data-switch][data-color] que resuelve desde el --palette-* global, misma especificidad (0,2,0), gana el último. Lo propio de este componente es que el fallback no salva ni al tono por defecto: soma estampa data-color SIEMPRE (es el color RESUELTO, neutral por defecto), así que el bloque genérico casa en toda instancia. Medido en checked: --switch-neutral-solid no mueve nada, --palette-solid sobre el nodo repinta (oklch(0.5556 …) -> rgb(4,5,6)). En button el neutral sobrevive; aquí no. Adjudicadas POR PATRÓN, con esa diferencia escrita. LAS OTRAS TRECE SON ESTADO O INSTRUMENTO Cinco disabled-* y el invalid-track-border nuevo sólo existen bajo su atributo, que ningún trigger alcanza (forzados: alcanzan, con su par medido). Los cinco size-*-thumb-translate son DOS huecos a la vez: pintan sobre `transform`, que el guard no fotografía, y valen 0px hasta que el interruptor está encendido (forzado checked: 12/14/16/18/20px -> 1234px). El par de transición ES la transición que el guard congela (sin congelar: 0,12s -> 11,5s en las cuatro propiedades). FUERA POR DOCTRINA (los 4 privados que quedan) --_switch-track-bg y --_switch-track-border-hover son CONMUTADORES: dos fuentes cada uno (la clave pública del estado apagado, el forward de paleta del encendido), y aplanarlos obligaría a duplicar cada regla por color. Las dos lecturas directas de --_switch-palette-solid* (el fondo del track en hover, la tinta del check dentro del pulgar) son el puente THM-2: un público encima dejaría que un tema fijara el tono y matara el color= de cada instancia. El techo honesto de este componente NO es el 100 %. DEFECTO ANOTADO, NO ARREGLADO El tinte inválido es --color-threat-element, el paso `element` de la escala: oklch(0.9555 0.0207 13.86), casi blanco sobre superficie clara. Y en cuanto entra el puntero, la regla de hover (0,4,0) lo tapa con el borde neutro fuerte. Es el MISMO valor en checkbox, radio-group y toggle para el mismo estado, así que es una decisión de escala del sistema (element contra border), no de esta receta: cambiarlo mueve píxel en cuatro componentes y D-TH.5 lo deja fuera. VERIFICACIÓN Sonda estándar antes/después 0 diffs (416 valores · 7 estados) + sonda de estados propia 0 diffs (928 valores · 29 estados: checked, invalid, readonly, disabled, RTL y las cinco tallas, cada uno con hover y foco) · capturas 2x antes/después idénticas · R-5.4 27/52, exit 0, sin STALE · censo --only 81 % · component:audit PASS · eidos-lint 13 morfo-backed / 0 invalid · vitest src/uix/eidos 35/36 (el rojo conocido es skin-media-player) · rtl:check 0 · docs:check 0 · npm run check sin errores en ficheros del componente · pestaña Tokens verificada en el navegador (52 filas, 0 errores de consola). Índice construido a mano desde HEAD sobre los cuatro ficheros compartidos (recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts): hay tres sesiones más en vuelo sobre ellos. Fuera del commit, como en el de `form`, docs/audit/theming/README.md — es un agregado global que mezcla ese trabajo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`switch` 76 % → 81 %** (COMMIT), 1 clave. `invalid-track-border`
era el ÚNICO knob que iba a un primitivo a pelo (`--color-threat-element`).
**El nombre NO es el `invalid-border` del catálogo** (6 recetas lo usan):
esta tiene DOS partes con borde y las nombra —`disabled-track-border` es su
hermana de estado, `thumb-border` es la del pulgar—, así que el nombre desnudo
no diría cuál tiñe; la forma modificador+parte+ranura ya existe
(`invalid-input-border`, `invalid-control-border`). **Las DOCE claves de tono
no pintan y aquí cae también `neutral`**: la cascada de paleta ya conocida,
más que **soma estampa `data-color` SIEMPRE** (color resuelto, `neutral` por
defecto), así que el bloque genérico casa en toda instancia y el fallback que
salva al tono por defecto en otras recetas tampoco llega — medido en checked
(`--switch-neutral-solid` no mueve nada, `--palette-solid` repinta).
Adjudicadas por PATRÓN. Otras trece adjudicaciones son estado que la demo no
monta (5 `disabled-*`, `invalid-track-border`) y dos huecos del instrumento
(los 5 `size-*-thumb-translate` pintan sobre `transform`, que el guard no lee,
y valen 0 hasta checked; el par de transición ES la transición que congela).
Los 4 `private` que quedan son 2 CONMUTADORES + 2 lecturas del puente THM-2:
**su techo honesto no es el 100 %**. R-5.4 27/52, exit 0. Diff 0 sobre
**1.344 valores en 36 estados** (416 de la sonda estándar + 928 de una sonda
de estados propia: checked / invalid / readonly / disabled / RTL / 5 tallas,
cada uno con hover y foco) y capturas 2× idénticas. ⚠ **Defecto anotado, no
arreglado**: el tinte inválido es el paso `element` de la escala
(`oklch(0.9555 …)`, casi blanco) y el `:hover` (0,4,0) lo tapa — mismo valor
en checkbox / radio-group / toggle, así que es escala del sistema y mueve
píxel en cuatro sitios.
uix(progress): temable — 69 % → 87 %, y el instrumento medía UNA de sesenta caras Dos claves acuñadas, seis literales FIRMADOS y cuatro knobs adjudicados por escrito. El default no se mueve: diff de computed 0 sobre 60 configuraciones y capturas 2x idénticas byte a byte. Censo: 69 % → 87 % (35 knobs → 30; público 24 → 26, global 3 → 2, literal 6 → 0, excepción firmada 0 → 6). Contrato 41 → 43 claves. Global 67 % (no baja). QUÉ ENTRA (2 claves) row-gap (--space-2) + column-gap (--space-3). Era el ÚNICO knob de apariencia que iba a un primitivo a pelo, y se parte en dos porque el atajo llevaba DOS valores distintos: el hueco de fila separa la línea de texto de la barra, el de columna separa el rótulo del valor. Un gap único habría nombrado un knob para dos. El catálogo tiene las dos palabras (row-gap x10, column-gap x4) y el molde exacto es chart.legend-row-gap / -column-gap. QUÉ SE FIRMA EN VEZ DE ACUÑARSE (6 literales, fuera del ratio) Cuatro 100 % y dos max-content: la geometría de la barra, no una decisión de tema — llena la fila que le dan, o se encoge a su propio grosor / diámetro. Cada uno con su anotación en la declaración (válvula de recipe-contract §3). QUÉ SE RECHAZA DE LA PROPUESTA §4 (dos de sus tres filas) indicator-radius — el --radius-full del anillo. Su GEMELO meter ya dejó el veredicto escrito EN EL CSS: es lo que hace circular a shape='circular', y un token dejaría que un tema des-redondease una forma que el consumidor pidió por su nombre. Identidad, no knob; se copia el comentario del gemelo. Son los dos únicos global que quedan, y el censo no puede sacarlos del ratio: la válvula de la anotación sólo reclasifica la clase literal, nunca un global (mismo techo que meter). width: max-content — la §4 fundía en UN nombre dos nodos distintos (la raíz vertical y la raíz circular) y los nombraba por el primer selector. Es el literal de identidad de arriba. EL TECHO HONESTO ES 87 %, NO 100 % Los dos knobs private que quedan son el CANAL DE VALOR: --_progress-value-pct lo escribe soma en el estilo inline del provider en cada render (0-100 saturado) y la receta lo lee para el ancho del relleno lineal y el alto del vertical. Un público encima mentiría — un tema no puede fijar el progreso de una tarea. Misma clase que el z-index inline de drag-drop. GUARD R-5.4: 40/43, TRES ADJUDICADOS CON SU MEDIDA indicator-bg-loaded — sólo pinta bajo [data-state='loaded'], y la corrida mantiene indeterminate encendido para alcanzar los tokens del barrido; forzado sobre el nodo real: oklch(0.6406 0.1329 157.68) -> rgb(1,2,3). transition-duration / -ease — los congela el guard por diseño; pasada sin congelar: 0.18s -> 11.5s y cubic-bezier(0.4, 0, 0.2, 1) -> steps(7). EL INSTRUMENTO MEDÍA UNA DE SESENTA CARAS La sonda estándar ve 4 nodos — que son TODAS las partes del morfo — pero una sola configuración: lineal, horizontal, loading, md. Medio contrato (el anillo, el eje vertical, el barrido indeterminado, la tinta de completado) queda fuera del diff, y el gate pasa en verde sin haberlo mirado. El guard lo dijo antes: 22 de 41 en la página por defecto. Con sweepAttr data-shape — el interruptor que su gemelo meter YA tenía — sube a 34; con el interruptor indeterminate de la demo, a 37; con el chip vertical, a 38. Cada control se comprobó por separado, antes y después, porque montar más puede medir menos: aquí ninguno tapó nada. Y una trampa NUEVA, medida aquí: una sonda que CACHEA los nodos y luego mide en bucle da 8.352 falsos diffs si el HMR reemplaza el subárbol a mitad de corrida. getComputedStyle sobre un nodo DESACOPLADO devuelve la cadena vacía en TODAS las propiedades, y el diff lee cada vacío como un cambio. Se arregla re-consultando los nodos en cada instantánea, y se detecta haciendo fallar la sonda ante un computed vacío. Pariente de hmr-stale-tab-phantom-findings. ARTEFACTOS Sonda estándar: 4 nodos, 800 valores x 7 estados -> 0 diffs. Sonda de variantes (2 formas x 2 orientaciones x 3 estados x 5 tallas, elemento y ::before): 17.280 valores -> 0 diffs. Capturas 2x antes/después, lineal y circular: idénticas byte a byte. Censo --only progress 87 % · component:audit PASS 1/1 · eidos-lint 0 invalid (13 morfo-backed, 12 eidos-only) · vitest src/uix/eidos 439/440 (el único rojo es el conocido skin-media-player en lint.test.ts) · rtl:check 0 · docs:check 0 · npm run check sin errores atribuibles a progress · prettier limpio en lo tocado (progress.css estaba limpio en HEAD y sigue limpio; los dos ficheros del centinela ya venían sucios de antes y mis líneas no añaden nada). README de eidos con su sección «Talla y tema» y pestaña Tokens en la demo, verificada en el navegador: 43 filas resueltas en vivo, 0 errores de consola. NOTA DE ÁRBOL COMPARTIDO Otra sesión trabaja sobre form en los mismos ficheros (base.ts, generated/base.css, los dos del centinela y el índice de fichas). Lo indexado se construyó a mano desde HEAD + sólo mis hunks, así que el commit no arrastra su trabajo en curso; docs/audit/theming/README.md lleva sólo el delta de progress y conserva la fila de form tal como está en HEAD. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`progress` 69 % → 87 %** (COMMIT), 2 claves. `row-gap` +
`column-gap`: el `gap` en atajo tenía DOS valores distintos, así que es un knob
por eje (molde `chart.legend-row-gap`). **Seis literales de identidad
firmados** (cuatro `100%`, dos `max-content`: llena la fila que le dan o se
encoge a su propio grosor). **Rechazadas dos de las tres filas de la §4**: el
`--radius-full` del anillo es lo que HACE circular a `shape='circular'` — su
gemelo `meter` ya lo dejó escrito en el CSS, y se copia el comentario. Los dos
`private` que quedan son el CANAL DE VALOR (`--_progress-value-pct`, inline de
soma), así que **su techo honesto es 87 %**. R-5.4 40/43 con `sweepAttr:
data-shape` + los controles `indeterminate` y `vertical` de la demo (22/41 sin
ellos); tres adjudicados. Diff 0 sobre **60 configuraciones** (2 formas × 2
orientaciones × 3 estados × 5 tallas, 17.280 valores) y capturas 2×
**idénticas byte a byte**. ⚠ **una sonda que CACHEA los nodos da 8.352 falsos
diffs si el HMR reemplaza el subárbol**: `getComputedStyle` de un nodo
desacoplado devuelve la cadena vacía en TODAS las propiedades.
- 2026-08-23 — **`cropper` 60 % → 94 %** (`f03ee5af4`), 2 claves. El cromo del
botón de zoom (radio + `font-size`, costura). **Siete literales de IDENTIDAD**
firmados: cuatro `100%`, dos `50%` (lo que HACE redonda una selección y un
tirador) y un `line-height: 1`. El 6 % restante es el `:hover` neutro de ese
botón — no se acuña (§38 / R-4.3 lo deprecan) y no se puede migrar aquí
porque `[data-cropper-zoom-btn]` es hook EIDOS-ONLY sin arquetipo: cola de la
firma 3. De paso, hueco de composición: ese botón debería componer
`IconButton`. R-5.4 17/17.
- 2026-08-23 — **`badge` 59 % → 75 %** (`271fcd4df`), 6 claves. La ALTURA del
chip vivía en un privado con cuatro fuentes por talla leyendo
`--control-height-*` a pelo: es una ESCALA y sube al TSC. Más `dot-radius`, el
par que a `dot-size` le faltaba. **Y la suite corrigió el valor al entrar en el
contrato** (el bundle, no el primitivo): `recipe-css-contract` no se aplicaba a
la declaración mientras vivía en el CSS. Las 40 claves de tono no mueven nada
por la CASCADA DE PALETA — entran como excepción POR PATRÓN. Su sonda medía 2
nodos (dot / icon / remove son opt-in). `component:audit` NEEDS-WORK
PREEXISTENTE (R-1.5, sin foco).
- 2026-08-23 — **`s-text-virtual-list` 58 % → 70 %** (`1f785b6f2`), CERO claves.
Su techo es ése: el triple tipográfico lo escribe el componente INLINE en cada
render desde props resueltas, así que un público mentiría — y hay una razón
dura, el canvas que mide el texto lee `getComputedStyle` del contenedor y la
fuente pintada tiene que ser la MISMA que la medida. R-5.4 9/9 a la primera.
- 2026-08-23 — **`background` 57 % → 95 %** (`9bdc5a5d5`), 6 claves — la misma
costura dos veces (el mesh leía `--gradient-aurora`, los cinco pasos de
escarcha leían `--blur-*`). El grano del ruido NO se acuña: la textura ES la
técnica y re-escapar su data-URI cambiaría el valor serializado. Nace
`prepareWith` en el guard (3/37 → 18/37) **y la lección al revés: montar MÁS
capas midió MENOS** (17 → 9), porque el foco puntual y el paralaje repintan la
capa donde se miden los patrones. 19 adjudicaciones medidas.
- 2026-08-23 — **`color-field` 56 % → 93 %** (`3c6742b9c`), 4 claves (el divisor
de canal y el ancho fijo por caracteres). **13 tokens preexistentes medidos uno
a uno** — nunca había pasado por R-5.4. Las dos de transición costaron dos
diagnósticos falsos: el conocido (el guard congela) y uno mío, sospechar del
`prefers-reduced-motion` cuando la causa era medir el PRIMER segmento, que es
un `literal` con `transition: none` propio.
- 2026-08-23 — **`waveform` 55 % → 86 %** (`11b92ded6`), 1 clave: `playhead-bg`,
la única línea del re-tinte del Slider embebido que pasaba un primitivo CRUDO.
El censo no la veía (una custom property reenviada a un compuesto no está en
`KNOB_PROPS`). El 14 % restante es préstamo con dueño: apagarse es decisión del
Slider. Tercera cara del punto ciego del instrumento: las partes de un
componente COMPUESTO.
- 2026-08-23 — **`navigation-menu` 44 % → 100 %** (`85988785d`), bloque B1.
Contrato 26 → 44 claves: **19 nuevas y UNA retirada**. Diff 0 sobre 2.752
valores en 8 estados; R-5.4 43/44 con una adjudicación.
**Los nombres los dio el catálogo**: los gemelos cerrados el mismo día
(`dropdown-menu`, `context-menu`) para `content-{min-width,padding,radius,fg}`
y `chevron-{size,stroke}`; `accordion` para `trigger-font-family` y
`disabled-trigger-fg`. Las CUATRO recetas que declaran un chevron lo
dimensionan con un literal — un glifo no es un paso de la escala de espacio —,
y al revés el subrayado: su `2px` ES `--border-width-medium`.
**La §4 generada falla de CINCO maneras, una nueva**: llamaba `trigger-width`
/ `trigger-height` al CHEVRON (colisionando con la altura del control, que ya
existe), fundía dos nodos en `content-radius`, cruzaba `content-padding-*`
(era de la FILA) y llamaba `content-width` a un `min-inline-size` — y abría
exigiendo consumir la capa `menu-indicator`, que es **homonimia**: esa capa es
el check de los CheckboxItem, no el subrayado de esta barra.
**Las filas del panel no son `item-` ni `link-`**: aquí `item` es el `<li>` de
la barra y el `Link` de barra viste el cromo del trigger, así que esos nombres
prometerían un nodo que no alcanzan. `content-link-*` nombra el nodo por donde
vive. **El privado era una ESCALA** (tres fuentes por talla), y sube al TSC.
**`indicator-x` era declaración muerta** — soma escribe el rect inline y la
receta lee sólo `-w`/`-h`; retirada, 0 diffs.
**El instrumento, dos cegueras y una trampa** (§13): sonda y centinela no
veían el panel ni sus filas (los `<a>` desnudos del consumidor: 10 nodos y
ninguno era el panel — 9 de 19 knobs fuera del diff); **el guard cerraba el
panel que medía** (abre por `pointerenter`, y el aparcado del puntero en (0,0)
dispara el cierre) dando falso negativo Y falso positivo a la vez; y `color`
está EN TRANSICIÓN en esta parte, así que forzar `data-disabled` y leer en el
acto devuelve el valor viejo — la trampa del cuentagotas, en otro componente.
uix(audio-player): temable — 37 % → 56 %, y el instrumento medía CERO nodos 3 claves nuevas (5 → 8) y un literal anotado. El 44 % restante es PRÉSTAMO CON DUEÑO, no deuda: ocho filas que leen `--media-player-*` porque este componente ES la piel de audio de ese chasis, y una ficha nunca propone duplicar un token prestado con prefijo propio (§5.3-3). Acuñado lo único que ESTE reproductor añade sobre el chasis: el peso del título y la insignia LIVE en versalitas espaciadas (peso + tracking). Los dos `600` pasan a `var(--font-weight-semibold)`: el mismo valor, con nombre. Y EL INSTRUMENTO ESTABA CIEGO DEL TODO: MEDÍA 0 NODOS. Tres razones encadenadas: 1. `/uix/components/audio-player` es un 404 — el CUARTO canónico sin ruta propia (picker-shell, mockup, surface y éste). Se mide en la demo del chasis. 2. Sus partes no llevan `data-audio-player-*`: son `data-media-player-*`. Con `extraNodes`, la sonda pasa de 0 a 48 NODOS. 3. Sólo existe tras el chip `media: audio` de esa demo: sonda (`DEMO_VARIANTS`) y guard (`openWith` + `openMarker`) lo pulsan ahora, y el guard barre además las cuatro variantes (card/row/bar/inline). Con eso, 0/8 → 6/8. Las dos que quedan son la insignia LIVE, que sólo existe mientras el proveedor refleja un directo: montada con `[data-live]`, alcanza (600 → 333 y 1,28px → 7px). Gates: sonda con 48 nodos (antes 0) · centinela 6/8 con las dos adjudicadas · censo 56 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · equivalencia comprobada en el computado (título 600, tracking 1,28px). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`audio-player` 37 % → 56 %** (3 claves) **y el instrumento
medía CERO nodos**. El 44 % restante es préstamo con dueño (ocho filas
`--media-player-*`): es la piel de audio de ese chasis y se tema en su ficha.
Acuñado lo único que añade sobre el chasis: el peso del título y la insignia
LIVE (peso + tracking); los dos `600` pasan a `var(--font-weight-semibold)`.
**Tres cegueras encadenadas**: `/uix/components/audio-player` es un **404** —el
CUARTO canónico sin ruta—; sus partes son `data-media-player-*`, no
`data-audio-player-*` (con `extraNodes`, la sonda pasa de **0 a 48 nodos**); y
sólo existe tras el chip `media: audio` de la demo del chasis (sonda y guard lo
pulsan ahora, y el guard barre las cuatro variantes). Con eso, 0/8 → 6/8.
uix(theming): cola pequeña — seis componentes, y una receta que TAPABA sus claves `section` (cuatro claves resucitadas), `sticky` 0 → 100 %, `button-group` 40 → 100 %, `qr-code` 43 → 100 %, `toggle-group` 33 → 50 % (su techo) y `split-button` adjudicado en 50 % (el suyo). Cero claves nuevas. Global 65 %, 25 componentes al 100 %, y los de alcance <20 % bajan de 14 a 12. EL HALLAZGO ESTÁ EN `section`: LA RECETA TAPABA SUS PROPIAS CLAVES PÚBLICAS. Declaraba los cuatro pasos de relleno en el host (`[data-box][data-section] { --section-padding-block-sm: … }`), y esa declaración gana a la que el contrato emite en `:root`: un tema que escribiera `--section-padding-block-lg` perdía contra la receta. Borrado el bloque —mismos valores, una sola fuente—, computado idéntico (48px antes y después), y las cuatro claves alcanzan. Lo demás es IDENTIDAD: once `100 %` entre las seis recetas y el centinela de 2px del sticky —el hilo invisible que el observador vigila—, todos con su razón escrita. Más un respaldo muerto retirado en `toggle-group`, sobre un token PRESTADO de `toggle`: el velo de un grupo deshabilitado ES el del toggle, y se tema en su ficha (§5.3-3). Y UNA CLASE DE ADJUDICACIÓN NUEVA, QUE SE REPETIRÁ: UN PASO POR INSTANCIA. `section` y `qr-code` mapean su prop de talla a `var(--{c}-{eje}-{k})` y la escriben INLINE, así que sólo el paso ACTIVO alcanza; los otros leen muertos hasta que la prop cambia. No es deuda ni ceguera del instrumento: es cómo funciona una escala resuelta por prop. Gates: los seis guards en verde · censo 65 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **COLA PEQUEÑA: seis componentes, cero claves nuevas.** `section`
(— · cuatro claves RESUCITADAS), `sticky` 0 → **100 %**, `button-group` 40 →
**100 %**, `qr-code` 43 → **100 %**, `toggle-group` 33 → **50 %** (su techo) y
`split-button` ADJUDICADO en 50 % (su techo). Global 65 %; **25 componentes al
100 %** y los de alcance <20 % bajan de 14 a 12.
**El hallazgo está en `section`: la receta TAPABA sus propias claves públicas.**
Declaraba los cuatro pasos de relleno en el host
(`[data-box][data-section] { --section-padding-block-sm: … }`), y esa
declaración gana a la que el contrato emite en `:root`: un tema que escribiera
`--section-padding-block-lg` perdía contra la receta. Borrado el bloque —mismos
valores, una sola fuente—, computado idéntico (48px antes y después), y las
cuatro claves alcanzan.
Lo demás es **identidad**: once `100 %` entre las seis recetas y el centinela
de 2px del sticky —el hilo invisible que el observador vigila—, todos con su
razón escrita. Más un respaldo muerto retirado en `toggle-group`, sobre un
token PRESTADO de `toggle` (el velo de un grupo deshabilitado es el del
toggle, y se tema en su ficha).
**Y una clase de adjudicación nueva, que se repetirá: UN PASO POR INSTANCIA.**
`section` y `qr-code` mapean su prop de talla a `var(--{c}-{eje}-{k})` y la
escriben INLINE, así que sólo el paso ACTIVO alcanza; los otros cuatro leen
muertos hasta que la prop cambia. No es deuda ni ceguera del instrumento: es
cómo funciona una escala resuelta por prop.
Gates: seis guards en verde · censo · audit PASS · suite con el rojo conocido
ajeno · rtl 0 · docs 0.
uix(tooltip): temable — 52 % → 75 %, y el plano ANULA SUS TRES VARIANTES 3 claves nuevas y 4 literales anotados. Subiría más, pero medir destapó que cinco de sus claves no pintan: el plano `overlay` le gana la superficie ENTERA. Acuñados el desenfoque del velo `ghost` y las dos tintas de la flecha `outline`, que se pintan por selector HERMANO desde el contenido. ⚠⚠ EL PLANO NO SÓLO SE QUEDA LA TIPOGRAFÍA: SE QUEDA EL FONDO, EL BORDE Y LA SOMBRA. `[data-depth='overlay']` declara las tres con la misma especificidad que `[data-tooltip-content]` y más tarde en la cascada. Medido: escribir `--tooltip-bg`, `--tooltip-border` o `--tooltip-shadow` no mueve NADA, mientras `--depth-overlay-surface` / `-border` / `-shadow` repintan el panel. Con `font-family` y `line-height`, son cinco públicos que no alcanzan. Y LA CONSECUENCIA VISUAL ES PEOR QUE LA CONTABLE: LAS TRES VARIANTES SON LA MISMA. Medidas `solid`, `outline` y `ghost` con el panel abierto: fondo, borde y sombra IDÉNTICOS en las tres. `outline` es indistinguible de `solid`; `ghost` sólo se diferencia por un `backdrop-filter` que no se ve porque la superficie que tiene delante es opaca. La máquina de variantes existe, alimenta privados, y el plano la anula entera. → §13. Por eso no se acuñó `ghost-bg`: lo intenté, el centinela lo delató y se retiró antes de commitear. Las cinco que ya estaban quedan ADJUDICADAS, no retiradas: son la FUENTE de esa máquina de variantes, y borrarlas dejaría inválidos los privados sin arreglar nada. La corrección de fondo es la precedencia del plano — la misma firma §12.9 que arrastran menubar, dropdown-menu y combobox. `content-z` sí está vivo, pero en otro nodo: aterriza en el ENVOLTORIO flotante (90 → 4321), que no lleva ningún `data-tooltip-*` y queda fuera del conjunto medido. Instrumento: un tooltip abre por HOVER, no por clic (`openBy: 'hover'`, como link-preview) — con eso pasó de 0/24 a 14/24. El resto se midió con el interruptor `open` de la demo: un panel abierto por puntero exige el ratón quieto sobre el disparador toda la pasada, y no es medida fiable. Gates: sonda 0 diffs en 256 valores × 8 estados · centinela 15/23 con las ocho adjudicadas · censo 75 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`tooltip` 52 % → 75 %** (3 claves, 4 literales anotados) **y el
plano `overlay` ANULA SUS TRES VARIANTES**. Global 65 %.
Acuñados el desenfoque del velo `ghost` y las dos tintas de la flecha
`outline`. **No se acuñó `ghost-bg`**: lo intenté, el centinela lo delató y se
retiró antes de commitear.
**⚠⚠ El plano no sólo se queda la tipografía: se queda el fondo, el borde y la
sombra.** Medido: `--tooltip-bg`/`-border`/`-shadow` no mueven nada,
`--depth-overlay-*` repinta. Y la consecuencia visual es peor que la contable:
`solid`, `outline` y `ghost` computan lo MISMO — `outline` es indistinguible
de `solid`. → §13. Las cinco claves quedan adjudicadas, no retiradas: son la
fuente de la máquina de variantes.
Instrumento: el tooltip abre por HOVER (`openBy: 'hover'`, como link-preview) —
0/24 → 14/24—, y el resto se midió con el interruptor `open` de la demo, porque
un panel abierto por puntero exige el ratón quieto toda la pasada.
- 2026-08-23 — **`time-range-field` 29 % → 100 %** (4 claves), gemelo exacto de
`date-range-field` **defecto incluido**: la misma clave `row-gap` alimentando
el `column-gap`. Global **64 % → 65 %**.
Que los dos gemelos tuvieran el MISMO nombre equivocado dice algo: el error no
se coló al escribir, se copió al duplicar. **Donde hay un par, el defecto viene
en par** — mirar al gemelo es parte de la pasada.
uix(date-range-field): temable — 29 % → 100 %, y una clave que giraba OTRA cosa 4 claves nuevas (3 → 7), una RENOMBRADA porque mentía y un literal anotado. Centinela 7/7. `row-gap` ALIMENTABA EL `column-gap`. La clave pública se llamaba `row-gap` y lo que movía era el hueco ENTRE los dos campos y su separador; el hueco de fila de verdad —etiqueta ↔ campos— leía `--space-1-5` a pelo. Ahora hay dos, cada una con el nombre de lo que alimenta, y NINGÚN computado se mueve: la nueva `column-gap` hereda el valor que tenía `row-gap` (`--space-3`) y `row-gap` recupera el suyo (`--space-1-5`). Un nombre equivocado no es un detalle: es una perilla que gira otra cosa. La etiqueta se acuña aquí a propósito y CON FECHA DE CADUCIDAD. La receta ya lo explica: la raíz del rango NO es un `[data-field]`, así que no puede heredar la etiqueta de `field.css` —sus tokens viven en ese ámbito— y repetía los valores de la fundación a pelo. Los mismos valores, ahora nombrados (`label-fg`, `label-font-size` leyendo el bundle, `label-font-weight`). Cuando el mandato de composición de Field (§5.3-3) convierta esa raíz en un campo, las tres desaparecen y la etiqueta hereda: queda escrito en el contrato para que quien lo ejecute las borre sin dudar. Gates: sonda 0 diffs en 992 valores × 8 estados · centinela 7/7 · censo 100 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`date-range-field` 29 % → 100 %** (4 claves, una RENOMBRADA
porque mentía). Centinela 7/7.
**`row-gap` alimentaba el `column-gap`**: la clave pública movía el hueco entre
los dos campos, y el de fila —etiqueta ↔ campos— leía `--space-1-5` a pelo.
Dos claves ahora, cada una con el nombre de lo que alimenta, y **ningún
computado se mueve**. Un nombre equivocado es una perilla que gira otra cosa.
**La etiqueta se acuña con fecha de caducidad**: la raíz del rango NO es un
`[data-field]` —lo dice la propia receta— así que no puede heredar la etiqueta
de field.css y repetía los valores de la fundación. Nombrados aquí, y escrito
en el contrato que el mandato §5.3-3 los borra cuando la raíz sea un campo.
- 2026-08-23 — **`surface` ADJUDICADO: el 25 % es su techo**, cero claves nuevas.
Es la cara de PINTURA de Box y lo que pinta no es suyo: `bg` y `fg` salen del
puente de paleta (THM-2) y el acabado es canal de valor del generador (§39).
La rama `inherit` del `fg` suave es intencionada y está escrita en la receta.
**Pero no tiene ruta**: `/uix/components/surface` es un 404 y el guard medía
una página VACÍA — 0/25, incluido un `radius` vivísimo. Apuntado a
`/temas/gradientes` + `/blocks/cta`: 1/25, y el que mueve es el radio. Ya son
TRES los canónicos sin demo (picker-shell, mockup, surface) → §13.
Sus 24 claves de tono son la supersesión de paleta de hoy: patrón compartido.
uix(banner): temable — 30 % → 100 %, y el guard pasa de 4/42 a 53/53 11 claves nuevas (42 → 53). Su contrato ya era grande —40 claves de intención— pero su CROMO estaba a pelo: la densidad por talla, la interlínea y el trazo del anillo de `outline`, que era la última fila que impedía al privado del borde derivar de un público. Dos ausencias firmadas: los `transparent` de `outline` y `ghost` y los tres `border-width: 0`. La variante ES la ausencia de cromo — mismo criterio que button, y por la misma razón: diez claves de `transparent` no son un contrato, son ruido. EL INSTRUMENTO NO VEÍA 38 DE SUS 42 CLAVES, Y NINGUNA ESTABA MUERTA. Dos cegueras a la vez: 1. Su paleta resuelve por `data-intent`, NO por `data-color`. El barrido de tono del guard —añadido esta mañana para button— estampaba sólo `data-color`. Ahora estampa los DOS nombres: un componente selecciona por uno y el otro queda inerte. 2. Y luego por VARIANTE: las ranuras `solid`/`contrast` sólo pintan en `solid`, la de borde sólo en `outline`, y la demo monta una de cada. Con `sweepAttr` sobre `data-variant`, el guard las recorre. Con las dos, 4/42 → 53/53. Es la diferencia entre «38 tokens que mienten» y «38 tokens que nadie había mirado con el atributo puesto»: exactamente lo que R-5.4 existe para distinguir. Gates: sonda 0 diffs en 192 valores × 7 estados (la demo monta UNA franja) · centinela 53/53 sin una sola adjudicación · censo 100 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura de las cuatro variantes con cuatro intenciones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`banner` 30 % → 100 %** (11 claves) **y el guard pasa de 4/42 a
53/53**: sus 38 «muertos» estaban vivos, sólo que nadie los había mirado con
el atributo puesto. Global 64 %.
Cosida la densidad por talla, la interlínea y el trazo del anillo de
`outline` —la última fila que impedía al privado del borde derivar—. Dos
ausencias firmadas: los `transparent` de `outline`/`ghost` y los
`border-width: 0`, con el criterio de button.
**Dos cegueras del instrumento, arregladas**: (1) su paleta resuelve por
`data-intent`, no por `data-color`, y el barrido de tono estampaba sólo el
segundo — ahora estampa los dos nombres; (2) y luego por VARIANTE, así que el
guard recorre `data-variant` con `sweepAttr`. Es la diferencia entre «38
tokens que mienten» y «38 que nadie había mirado con el atributo puesto».
Gates: sonda 0 diffs en 192 valores × 7 estados · centinela 53/53 · censo
100 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0 ·
captura de cuatro variantes × cuatro intenciones.
uix(anchor-nav): temable — 20 % → 100 %, y el censo aprende los bordes POR LADO 11 claves nuevas (2 → 13). Tenía dos knobs y todo lo demás a pelo: la escala del índice (que ahora lee el bundle, no `--font-size-sm` suelto), el relleno y el radio del enlace, sus tres tintas —reposo, hover y activo—, el peso del activo y LAS DOS TINTAS DEL RAÍL, que es lo que da nombre al componente. EL RAÍL ACTIVO ESTABA INVISIBLE PARA EL CENSO. Su color vive en un `border-inline-start-color`, y `KNOB_PROPS` sólo conocía `border-color` y los shorthands: los ocho longhands por lado (`border-{top,bottom,left,right, block-start,block-end,inline-start,inline-end}-{color,width}`) no eran knobs para la medida. El centinela los mira desde el 2026-08-22 —los añadió cuando prose y nav-tree leyeron muertos—; el censo, no. Corregido: +30 FILAS EN EL CATÁLOGO (5.101 → 5.131 knobs), y esta ficha baja de 20 % a 18 % antes de coserse, que es lo honesto: el censo promete sobre-reportar, nunca infra-reportar. La tinta de reposo del raíl estaba escondida de otra manera: dentro del shorthand `border-inline-start: var(--anchor-nav-rail-width) solid var(--color-border-subtle)`, que el censo clasifica por el PRIMER token que casa —el trazo, ya público— y da la fila por alcanzable. No hizo falta partir el shorthand: basta con nombrar el color dentro. Un susto útil: una coma que faltaba en el contrato rompió el build de tokens, y lo que lo delató fue la SONDA midiendo 0 nodos — la página no renderizaba. El recuento de nodos es el canario, tal como dice el handoff. Gates: sonda 0 diffs en 1.760 valores × 7 estados (8 nodos) · centinela 11/12 con el radio del foco adjudicado (el guard desenfoca a propósito) · censo 100 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura del raíl. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`anchor-nav` 20 % → 100 %** (11 claves) **y el censo aprende los
bordes POR LADO**. Global 64 %.
Tenía dos knobs y todo lo demás a pelo: la escala (que ahora lee el bundle),
el relleno y el radio del enlace, sus tres tintas, el peso del activo y las dos
tintas del RAÍL, que es lo que da nombre al componente.
**El raíl activo era INVISIBLE para la medida**: su color vive en un
`border-inline-start-color` y `KNOB_PROPS` sólo conocía `border-color` y los
shorthands. El centinela mira esos longhands desde el 2026-08-22 (los añadió
cuando prose y nav-tree leyeron muertos); el censo no. Añadidos los ocho por
lado, color y grosor: **+30 filas en el catálogo** (5.101 → 5.131) y esta ficha
baja a 18 % antes de coserse — el censo promete sobre-reportar, NUNCA
infra-reportar.
La tinta de reposo del raíl estaba escondida de otra manera: dentro de un
shorthand que el censo clasifica por el PRIMER token que casa (el trazo, ya
público). No hizo falta partirlo: basta nombrar el color dentro.
**Un susto útil**: una coma que faltaba en el contrato rompió el build de
tokens y lo delató la SONDA midiendo 0 nodos — la página no renderizaba. El
recuento de nodos es el canario.
Gates: sonda 0 diffs en 1.760 valores × 7 estados · centinela 11/12 · censo
100 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0.
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de paleta del placeholder de color, que no es deuda. Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón. Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar todas las recetas que brillan. LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su estado de error y el navegador reintenta, así que sonda y guard morían con un `TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load` si la espera de red se agota — la ociosidad era una comodidad, no una garantía. UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO: `placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano. Emulando la preferencia alcanza, y vale para cualquier token detrás de una media query de preferencia. Y una lectura fantasma cazada por repetición: el primer diff acusó `opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la medida: 0 diffs. Un comentario CSS no puede mover un computado. Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`image` 60 % → 96 %** (1 clave, 12 literales anotados) **y dos
instrumentos que morían en esta página**. Global 64 %.
Su deuda era casi toda IDENTIDAD: once `100 %` (la imagen, el fallback, el
error y sus dos `<img>` llenan su caja) y un `opacity: 1` (una imagen cargada
es opaca). Lo acuñado es la barra del esqueleto, que leía `--gradient-shimmer`
a pelo.
**La página NO alcanza `networkidle` NUNCA** —monta un `src` roto a propósito
para su estado de error— y sonda y guard morían con `TimeoutError` antes de
medir un nodo. Las dos caen ahora al evento `load` si la espera se agota.
**Un token bajo `@media (prefers-reduced-motion: reduce)` lee muerto**:
`placeholder-base` sólo existe ahí. Emulando la preferencia, alcanza — y vale
para cualquier token detrás de una media query de preferencia.
**Lectura fantasma cazada por repetición**: el primer diff acusó
`opacity: 0.925793 → 1` en la imagen cargada; era el fade EN VUELO, como el rim
animado de `onion-menu`. Repetida la medida, 0 diffs.
Gates: sonda 0 diffs en 544 valores × 7 estados (repetida) · centinela 20/23 ·
censo 96 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0.
uix(checkbox): temable — 59 % → 72 %, y un HUÉRFANO que el test no puede ver 3 claves nuevas, 3 literales anotados y un token del contrato CABLEADO. El 28 % que queda es el puente de paleta (`--_checkbox-palette-*`, THM-2): ocho filas que no son deuda. EL HALLAZGO: `indicator-fg` ERA UN HUÉRFANO QUE EL TEST DE HUÉRFANOS NO PUEDE VER. La clave llevaba en el contrato desde siempre, pero la receta pintaba la tinta de la casilla marcada leyendo `--color-content-on-solid` DIRECTAMENTE, sin nombrarla nunca — y el test busca el NOMBRE en los ficheros del componente, así que un token que nadie menciona pasa por vivo. Cableado (`--_checkbox-palette-text: var(--checkbox-indicator-fg)`), computado idéntico, y el propio guard lo confirmó marcando su adjudicación como STALE: ahora mueve. Es la clase inversa del `--popover-bg` que vivía de un COMENTARIO. Acuñado el estado INVALID, que gana a todas las variantes y leía el primitivo: el borde y el DOBLE ANILLO DE FOCO (hueco + anillo), que era una sombra con dos colores cableados. La geometría (2px/4px) se queda dentro de la declaración: es la forma del anillo, no una perilla. Tres literales pasan a excepción por identidad: el glifo llena su caja (`100 %` × 2) y la etiqueta de grupo es su texto (`fit-content`). Los 26 rojos del guard, medidos — y dos son trampas de ALCANCE del instrumento: - El glifo SÓLO existe dentro de una casilla MARCADA, y la primera de la demo está vacía: medir el trazo por talla sobre «el primer indicador del documento» mide otro nodo. Con el path dentro de la misma casilla, los cinco pasos alcanzan. - La etiqueta de grupo lleva su PROPIO `[data-disabled]`: no lo hereda del grupo, así que forzar el grupo no pinta nada. El resto es lo esperable (invalid, disabled, los cuatro de transición que el guard congela) y los ocho de TONO, que son la supersesión de la cascada de paleta medida hoy en `button`: adjudicados con el patrón compartido. Gates: sonda 0 diffs en 2.688 valores × 7 estados (13 nodos) · centinela 22/47 con todo lo demás adjudicado · censo 72 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura de marcada, invalid, disabled, xl y tono affirm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`checkbox` 59 % → 72 %** (3 claves, 3 literales anotados) **y un
HUÉRFANO cableado**. El 28 % restante es el puente de paleta (8 filas, THM-2).
**`indicator-fg` era un huérfano que el test de huérfanos no puede ver**: la
clave estaba en el contrato, pero la receta pintaba la tinta de la casilla
marcada leyendo `--color-content-on-solid` DIRECTAMENTE, sin nombrarla — y el
test busca el NOMBRE en los ficheros del componente. Cableada al privado de
paleta (computado idéntico), el propio guard lo confirmó marcando su
adjudicación como STALE: ahora mueve. Misma clase que el `--popover-bg` que
vivía de un COMENTARIO, al revés.
Acuñado el estado INVALID: el borde y el doble anillo de foco (hueco +
anillo), que era una sombra con dos colores cableados; la geometría 2px/4px se
queda dentro de la declaración, que es la forma del anillo.
**Dos trampas de ALCANCE del instrumento**, anotadas: el glifo sólo existe
dentro de una casilla MARCADA (medir «el primer indicador del documento» mide
otro nodo) y la etiqueta de grupo lleva su PROPIO `[data-disabled]`, no lo
hereda.
Gates: sonda 0 diffs en 2.688 valores × 7 estados · centinela 22/47 con todo
adjudicado · censo 72 % · audit PASS · suite con el rojo conocido ajeno · rtl 0
· docs 0 · captura de cinco estados.
uix(dropdown-menu): temable — 57 % → 96 %, y la tipografía del gemelo se RETIRA 9 claves nuevas (20 → 28) y 18 respaldos muertos retirados. Y una corrección a la baja, a propósito: `context-menu` 90 % → 87 %. Es el GEMELO de `context-menu`: mismos nombres, misma doctrina, mismas dos limpiezas de paso —el `font-family` era respaldo de SÍ MISMO y el peso del encabezado llevaba un `500` muerto detrás del token que ya vale 500—. Cosidos la tinta del panel y de la fila, su interlínea, el encabezado entero (tinta, peso y tracking) y el galón del submenú, que ahora tiene sus dos knobs: trazo y caja. EL FILO DEL SEPARADOR DEJA DE SER UN `1px` SUELTO y pasa a ser el trazo fino del sistema (`--border-width`, idéntico hoy): un tema que engorde el hairline lo engorda también aquí. Los 18 respaldos: cada `var(--dropdown-menu-x, …)` llevaba detrás un valor que NO puede ocurrir —las 18 claves están en el contrato— y sólo servía para envejecer contra él. Ninguno movía un píxel; dos ya discrepaban del contrato en la FUENTE. ⚠ `content-font-family` NO SE ACUÑA, Y LA DEL GEMELO SE RETIRA. El panel lleva `data-depth='overlay'` y el PLANO declara la familia con la misma especificidad y más tarde en la cascada: gana él. Quinta prueba de §12.9. `context-menu` la tenía acuñada desde esta misma sesión CON una adjudicación escrita, y eso era media doctrina: un público que no alcanza es un token que miente (R-5.4). Las dos recetas vuelven a su fuente literal con la razón encima. Un tercer caso —`combobox`, misma clave, mismo plano— queda medido y registrado en §13; no se toca aquí porque no es el componente en curso, y cuando §12.9 se firme la decisión será la misma para las cuatro. Gates: sonda 0 diffs en 928 valores × 8 estados (22 nodos con el panel abierto) · centinela 27/28 con la deshabilitada adjudicada · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura del panel abierto. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`dropdown-menu` 57 % → 96 %** (9 claves, 18 respaldos muertos
retirados) **y la tipografía del gemelo, RETIRADA**: `context-menu` 90 % →
87 %, a la baja y a propósito. Global 64 %.
Cosidos la tinta del panel y de la fila, su interlínea, el encabezado entero y
el galón del submenú (trazo + caja). **El filo del separador deja de ser un
`1px` suelto** y pasa a `--border-width`. Las dos limpiezas del gemelo se
repiten aquí: `font-family` era respaldo de SÍ MISMO y el peso del encabezado
llevaba un `500` muerto.
**Los 18 respaldos**: cada `var(--dropdown-menu-x, …)` llevaba detrás un valor
que no puede ocurrir —las claves están en el contrato— y sólo servía para
envejecer contra él; dos ya discrepaban en la FUENTE.
**⚠ `content-font-family` no se acuña, y la de `context-menu` se RETIRA.** El
panel lleva `data-depth='overlay'` y el plano gana (quinta prueba de §12.9).
Tenerla acuñada con una adjudicación escrita —como quedó ayer en el gemelo—
era media doctrina: **un público que no alcanza es un token que miente**
(R-5.4). Las dos recetas vuelven a la fuente literal con la razón encima.
`combobox` tiene la misma clave sobre el mismo plano: medido, registrado en
§13, no tocado aquí.
Gates: sonda 0 diffs en 928 valores × 8 estados · centinela 27/28 · censo
96 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0 ·
captura del panel.
uix(empty-state): temable — 29 % → 100 %, y el centinela 26/26 sin adjudicar 20 claves nuevas (6 → 26). Es el primero del bloque que pasa el guard ENTERO sin una sola excepción escrita: no tiene estado, variante ni parte condicional que esconda nada. Su deuda era de una sola clase: LA ESCALA DE TALLA VIVÍA ENTERA EN PRIVADOS que leían el bundle a pelo. Ahora cada paso tiene su público —relleno de bloque y de línea, tamaño del título, del cuerpo y del glifo, en `sm`/`md`/`lg`— y los privados los derivan. Con ellos el resto sale solo: peso, interlínea y tinta del título; interlínea y tinta de la descripción. La caja del glifo se sigue derivando (2×) de su propio tamaño —es lo que la mantiene legible en las tres tallas—; ahora ese tamaño es público, así que un tema mueve las dos cosas con un valor. SIETE RESPALDOS MUERTOS RETIRADOS, y dos MENTÍAN: el `, 1.4` de `--font-line-height-sm` (vale 1.45) y el `, 1.5` de `--font-line-height-md`. Mismo defecto que `callout` hoy: un respaldo escrito a mano envejece contra el token que respalda. El título sube UN paso discreto sobre la talla del componente (`sm` → `md`, `md` → `lg`, `lg` → `xl`): jerarquía sin acoplar el nivel semántico. Vivía en un comentario y ahora está en el contrato, con su razón. Gates: sonda 0 diffs en 1.056 valores × 7 estados · centinela 26/26 · censo 100 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las tres tallas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`empty-state` 29 % → 100 %** (20 claves, 6 → 26) **y el
centinela 26/26 sin una sola adjudicación**. Global 64 %.
Su deuda era de una clase: **la escala de talla vivía entera en privados** que
leían el bundle a pelo. Cada paso tiene ya su público (relleno, título, cuerpo
y glifo en `sm`/`md`/`lg`) y los privados los derivan; con eso el resto —peso,
interlínea y tinta del título, interlínea y tinta de la descripción— sale
solo. La caja del glifo se sigue derivando (2×) de su tamaño, ahora público.
**Siete respaldos muertos retirados**, dos de ellos MENTIROSOS: el `, 1.4` de
`--font-line-height-sm` (vale 1.45) y el `, 1.5` de `--font-line-height-md`.
Mismo defecto que `callout` hoy: **un respaldo escrito a mano envejece contra
el token**.
Gates: sonda 0 diffs en 1.056 valores × 7 estados · centinela 26/26 · censo
100 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0 ·
captura de las tres tallas.
uix(callout): temable — 27 % → 79 %, y la base óptica deja de copiar el sistema 7 claves nuevas (28 → 35) y un literal anotado. El 21 % que falta es el PUENTE DE PALETA (THM-2), que no es deuda: es la vía por la que `data-color` resuelve los 8 roles y las 33 escalas donantes sin una línea de CSS extra. Global 64 %. Cosido lo que la receta consumía a pelo: el hueco de fila, la tipografía del cuerpo (`font-size` + `line-height` — así se lee una admonición), la tinta, el tamaño del icono y el peso del título. Los tres pasos leen el BUNDLE (`--size-md-font-size`, `--size-md-font-line-height`, `--size-md-icon-size`). LA LÍNEA BASE ÓPTICA DEL ICONO DEJA DE REPETIR LAS MÉTRICAS DEL SISTEMA: era `calc(var(--size-md-font-size) * var(--font-line-height-md, 1.5))` y ahora es `calc(var(--callout-font-size) * var(--callout-line-height))` — la misma derivación, sobre los tokens del propio callout, así que un tema que agrande la copia mueve la caja del glifo con ella. Cinco respaldos muertos retirados: `var(--callout-gap, var(--space-3))`, `padding`, `accent-width`, `radius` —las cuatro claves están en el contrato— y el `, 1.5` de `--font-line-height-md`, que además MENTÍA: el token existe y vale 1.45. Los 24 rojos del guard son los TONOS, y no son de este componente: es la supersesión de la cascada de paleta que `button` destapó hoy (el bloque genérico va el último y gana por orden). Adjudicados con el patrón compartido, que ahora nombra su razón una sola vez para los dos componentes. Gates: sonda 0 diffs en 896 valores × 7 estados · centinela 10/34 con los tonos adjudicados · censo 79 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de cuatro tonos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`callout` 27 % → 79 %** (7 claves, un literal anotado). Global
**63 % → 64 %**. El 21 % restante es el PUENTE DE PALETA (THM-2), que no es
deuda.
Cosidos el hueco de fila, la tipografía del cuerpo, la tinta, el tamaño del
icono y el peso del título, todos leyendo el BUNDLE. **La línea base óptica
del icono deja de repetir las métricas del sistema**: la misma derivación,
ahora sobre los tokens del propio callout, así que un tema que agrande la
copia mueve la caja del glifo con ella.
**Cinco respaldos muertos retirados** (`var(--callout-gap, var(--space-3))` y
compañía: las claves están en el contrato) incluido el `, 1.5` de
`--font-line-height-md`, que además mentía — el token existe y vale 1.45.
Los 24 rojos del guard son los TONOS: la supersesión que `button` destapó el
mismo día, adjudicada con el patrón compartido.
Gates: sonda 0 diffs en 896 valores × 7 estados · centinela 10/34 · censo
79 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0 ·
captura de cuatro tonos.
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada, y no por deuda suya. Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO — `[data-rounded='full']` leía el primitivo global a pelo, así que la forma que más se retoca (la píldora) era el único escalón fuera del contrato. Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD (`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`, `outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían sido justo el antipatrón que la doctrina avisa: agrupa por VALOR. Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada, no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían inalcanzables mientras todas las demás ramas del mismo privado leen un público. ⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE. El último bloque del forward, `[data-button][data-color]`, resuelve `--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']` llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la misma especificidad (0,2,0). Medido sobre `data-color='risk'`: `--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras `--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete `primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`. No es de este componente: 49 recetas emiten el mismo forward genérico y el contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no movería un píxel, porque el valor por defecto de `--button-risk-solid` ES `var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es firma. → §13. El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el acabado de degradado que el generador deriva por instancia. Un error de método, anotado porque casi cuela: anclé la clave nueva en la primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se ancla en el BLOQUE del componente, nunca en una línea de clave. Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y a estampar el TONO que la clave nombra; y el fichero de excepciones gana PATRONES, para que una razón arquitectónica se escriba una vez y no 56. Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado · censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las seis variantes + píldora + tono `risk`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`button` 55 % → 91 %** (1 clave, 17 declaraciones anotadas) **y
el hallazgo más gordo del eje: la cascada de paleta ANULA los tonos del
componente — 419 claves en el catálogo.** Global 63 %.
Acuñado lo único que faltaba: **el paso `full` de la escala de radio**
(`[data-rounded='full']` leía el primitivo global a pelo). Anotadas 7
identidades (`fit-content` × 2, el `100 %` de `[data-block]`, los cuatro
`1px` de las cajas sr-only) y **10 ausencias**: los `transparent` de `soft`,
`outline`, `ghost` y `plain` — la variante ES la ausencia de cromo, y diez
claves de `transparent` habrían sido el antipatrón que la doctrina avisa.
Para que la ausencia cuente, **el censo aprende la válvula de recipe-contract
§3 un piso más abajo**: un PRIVADO cuyo valor es un literal CON su razón
escrita es una ausencia firmada, no deuda (`derivesFromPublic` la acepta).
Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un
público. El censo global sube 5 knobs por esto, no por retoque de ratio.
**⚠⚠ EL HALLAZGO.** El último bloque del forward, `[data-button][data-color]`,
resuelve `--button-palette-*` desde el `--palette-*` GLOBAL —que
`[data-color='{tono}']` llena desde `--color-{tono}-*`— y **gana a las reglas
por tono por ORDEN**, con la misma especificidad (0,2,0). Medido sobre
`data-color='risk'`: `--button-risk-solid` no mueve nada, ni en `:root` ni en
el nodo; `--palette-solid` y `--color-risk-solid` en el nodo repintan. Las
siete `primary-*` son el RESPALDO de esa regla y sólo actúan SIN
`data-color`. **49 recetas emiten ese mismo forward y el catálogo tiene 419
claves de tono**; medido igual en `badge` y `callout`. → §13, con el arreglo
propuesto (orden de emisión) y su contrapartida (la jaula del color).
El techo restante son dos filas de canal de valor (`--_button-fill-finish`,
el acabado que el generador deriva por instancia; su dial es del sistema).
**Un error de método que casi cuela**: anclé la clave nueva en la primera
línea `'radius-xl'` del fichero y aterrizó en **`meter`**. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende **`sweepAttr`** (barrer una variante mientras
mide) y a estampar **el tono que la clave nombra**; y el fichero de
excepciones gana **patrones** (`SENTINEL_PATTERN_EXCEPTIONS`), para que una
razón arquitectónica se escriba UNA vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con el resto adjudicado ·
censo 91 % · audit PASS · suite con el rojo conocido ajeno · rtl 0 · docs 0 ·
captura de las seis variantes + píldora + tono `risk`.
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`onion-menu` 29 % → 84 %** (7 claves + 5 literales anotados), y
**una regla del recipe que el navegador DESCARTA**. Global 63 %.
Acuñados el color del anillo del sector —UNO para los dos estados: el propio
CSS dice que el hover usa «el mismo color que el anillo de selección, más
claro», y lo que los separa es la opacidad que ya tenía token—, la tipografía
de la etiqueta, el radio y la tinta del disparador y las dos piezas del glifo
`+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))`
—respaldo de sí mismo, el defecto de context-menu— y dos `var(--radius-full, …)`
con respaldo imposible.
**⚠ `.onion-menu-icon :global(svg)` NO EXISTE**: `:global()` es un envoltorio
de Svelte y esto es un CSS plano, así que el selector es inválido y la regla
entera se descarta. Medido: el glifo computa 14px (el tamaño propio del Icon)
y el `70 %` que el fichero cree pintar no pinta. Acuñé `icon-glyph-size`, el
centinela lo delató y lo RETIRÉ: arreglar el selector mueve píxel. Mismo bug
en `timeline.css:331`; `image-picker.css` ya documentaba la trampa. → §13.
**Tres cegueras del instrumento, dos nuevas y genéricas**:
1. **El guard CERRABA la superficie.** Este menú nace ABIERTO y el clic de
«abrir» lo cerraba — 12 de 20 tokens muertos. Ahora, si hay `openMarker` y
ya casa, no se pulsa nada (8 → 14 de golpe).
2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden
con `extraNodes` (37 nodos en reposo contra 5).
3. **Congelar las transiciones no basta cuando el nodo está ANIMADO**:
`rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto;
congelando también la animación computa 0,45 y sigue al token.
Los cuatro tokens del rim son del look `luminous`, **que la demo del
componente no renderiza** (prop de Svelte que emite otro marcado, no hay
atributo que forzar): medidos en `/active/docs/agnt`.
Gates: sonda **0 diffs** en 7.328 valores × 8 estados · centinela 14/19 con
las cinco adjudicadas · censo 84 % · audit PASS · suite con el rojo conocido
ajeno · rtl 0 · docs 0 · `check` sin errores propios · capturas.
uix(field): temable — 73 % → 98 %, y sus 18 rojos estaban VIVOS 8 claves nuevas (60 → 68) y 8 literales ANOTADOS. Lo que falta es el PUENTE DE PALETA (`_palette-text`, THM-2), que no es deuda. Global 63 %. Field es el componente más COMPUESTO del sistema: lo que otras fichas llaman «préstamo con dueño» se cobra aquí, así que su techo pesa más que el de nadie. Acuñados el DISPARADOR DE CONTROL —el icono que abre el calendario, el reloj o la muestra, y revela la contraseña: `size`, `radius`, `fg` y sus dos estados—, el indicador `optional` (gemelo del `required`, que ya tenía token) y el lateral de la etiqueta flotante, que es lo que CORTA el borde bajo el texto. La caja del disparador se mide en `em` a propósito, así que el token guarda la RAZÓN (`1.75em`) y no un píxel por talla. OCHO LITERALES PASAN A EXCEPCIÓN FIRMADA POR IDENTIDAD, no para inflar el ratio: cuatro `inline-size: 100%` (el campo, el control, el input y el control flotante llenan su fila), dos `line-height: 1` (un glifo suelto no tiene interlínea) y dos `1em` del icono dentro del disparador (el glifo ES el tamaño del texto). LOS 20 ROJOS DEL GUARD ESTABAN VIVOS, uno por uno, y tres enseñan algo: - `disabled-opacity` vive en el CONTROL, no en la raíz: el guard leía el nodo equivocado. - `control-placeholder-fg` no leía porque el input de la demo NO TIENE `placeholder`: sin el atributo no existe la caja `::placeholder`, y `getComputedStyle(nd, '::placeholder')` devuelve entonces el estilo del ELEMENTO. Con un placeholder puesto, alcanza. Es genérico → §13. - `segment-height` es un token de FAMILIA que field posee y los pickers consumen —en las rutas de field no lo lee nadie— y además es RESUELTO (declarado por `[data-field][data-size]`), así que escribirlo en `:root` pierde POR DISEÑO: el tema mueve la coordenada. Medido en `/time-picker`: desde `:root` nada, desde el host del campo 28px → 1234px, y moviendo `--field-control-height-md` 28px → 992px. El resto son estado (invalid, disabled, readonly, foco), variante ghost, orientación horizontal, la franja de segmentos, el texto de error —que sólo se RENDERIZA con el interruptor de la demo: forzar el atributo no hace que Svelte monte el nodo— y los dos de transición, que el guard congela. Instrumento: el disparador de control no lo monta NINGUNA demo de field, así que el guard mide ahora `field` sobre DOS rutas (`/field` + `/date-picker`). Gates: sonda 0 diffs en 2.280 valores × 8 estados · equivalencia del disparador comprobada aparte en `/date-picker` (28px = 1.75em con fuente 16px · 4px = `--radius-sm` · misma tinta) · centinela 48/68 con las 20 adjudicadas · censo 98 % · `--names` 0 · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el rojo conocido ajeno · `check` sin errores propios · capturas de reposo, invalid, disabled, etiqueta flotante y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`field` 73 % → 98 %** (8 claves + 8 literales ANOTADOS) **y sus
18 rojos, medidos**. Global **63 %**. El 2 % que falta es el puente de paleta
(THM-2), que no es deuda.
Es el componente más COMPUESTO del sistema: lo que otras fichas llaman
«préstamo con dueño» se cobra aquí, así que su techo pesa más que el de nadie.
Acuñados el **disparador de control** —el icono que abre el calendario, el
reloj o la muestra, y revela la contraseña: `size`, `radius`, `fg` y sus dos
estados—, el indicador `optional` (gemelo del `required`, que ya tenía token)
y el **lateral de la etiqueta flotante**, que es lo que CORTA el borde bajo el
texto. La caja del disparador se mide en `em` a propósito, así que el token
guarda la RAZÓN (`1.75em`), no un píxel por talla.
**Ocho literales pasan a excepción firmada por IDENTIDAD, no por inflar el
ratio**: cuatro `inline-size: 100%`, dos `line-height: 1` de un glifo suelto y
dos `1em` del icono dentro del disparador (el glifo ES el tamaño del texto).
**Los 20 rojos del guard estaban VIVOS**, y tres enseñan algo:
`disabled-opacity` vive en el CONTROL y el guard leía la raíz;
**`control-placeholder-fg` no leía porque el input de la demo NO TIENE
`placeholder`** —sin atributo no hay caja `::placeholder` y
`getComputedStyle(nd, '::placeholder')` devuelve el estilo del ELEMENTO—; y
`segment-height` es un token de FAMILIA que field posee y los pickers
consumen, además de RESUELTO: desde `:root` no se mueve por diseño (medido en
`/time-picker`: desde el host 28px → 1234px, moviendo la coordenada
`--field-control-height-md` 28px → 992px).
**Instrumento**: el disparador de control no lo monta NINGUNA demo de field,
así que el guard mide ahora `field` sobre DOS rutas (`/field` + `/date-picker`).
Gates: sonda **0 diffs** en 2.280 valores × 8 estados · equivalencia del
disparador comprobada aparte · centinela 48/68 con las 20 adjudicadas · censo
98 % · `--names` 0 · audit PASS · rtl 0 · docs 0 · suite eidos con el rojo
conocido ajeno · `check` sin errores propios · capturas de reposo, invalid,
disabled, etiqueta flotante y disparador.
uix(menubar): temable — 15 % → 88 %, y el plano `overlay` GANA a la receta 30 claves nuevas (5 → 35): la barra (superficie, marco, radio —uno solo, la receta lo desacopla de la talla a propósito y el motor de forma deriva el del disparador de él— y las tres escalas `gap`/`padding`/`height`), el disparador (tinta, tipografía, escala de `font-size` y de `padding-inline`, el par de la entrada ABIERTA y la tinta deshabilitada) y el Panel `role=dialog` (tinta + escala). Global 62 % → 63 %. Los pasos leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`), no el primitivo crudo que el CSS venía consumiendo; y el `calc(var(--space-1) * 0.5)` del paso `sm` es `var(--space-0-5)` exacto. EL HUECO DEL PANEL DEJA DE REPETIR EL VALOR DEL PADDING. Las tres reglas de `--floating-gap` compensan el sangrado del disparador dentro de la barra con lo que era una COPIA del padding; ahora leen `--menubar-padding-{k}`, su fuente — si un tema ensancha el padding, el panel deja de quedar a ras de la barra. No pueden leer el privado `--_menubar-padding`: esas superficies viajan POR PORTAL y un privado declarado en la raíz no cruza esa frontera. ⚠ EL PLANO `overlay` GANA A LA RECETA — cuarta prueba de §12.9 y la primera con MECANISMO. Acuñé `panel-font-family` y `panel-line-height` sobre declaraciones que la receta YA tenía, y el centinela las dio muertas. Medido: `[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y más tarde en la cascada, así que gana; del mismo bloque, `color` y `font-size` —que el plano no declara— sí alcanzan. Retiradas del contrato y devueltas a su fuente literal, con la razón escrita en el CSS. Mientras §12.9 no se firme, un token de tipografía en CUALQUIER superficie `overlay` miente, y son veinte. EL HOVER NO SE ACUÑA: ES DEL EJE navigation-menu. `background: var(--color-surface-overlay)` sobre el disparador mata la capa del sistema —medido: `background-image` computa `none` en hover con `--state-hover` vivo—, por las dos razones que `navigation-menu.css` ya documenta al retirar esta MISMA regla: el shorthand resetea `background-image`, y la regla del arquetipo va en `:where()`, especificidad CERO. Acuñar un `hover-trigger-bg` habría fosilizado la invención. Instrumento: la barra suelta DOS superficies y el Panel —el que lleva sus knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`) y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó `openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`, que aquí no existe, así que volvía a pulsar el disparador antes de cada token y CERRABA el panel — media corrida contra una superficie cerrada. Gates: sonda 0 diffs en 1.470 valores × 8 estados · centinela 34/35 (la deshabilitada, adjudicada) · censo 88 % · `--names` 0 desviadas · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el rojo conocido ajeno (`skin-media-player`) · `check` sin errores propios · capturas de barra, tallas sm/lg, menú abierto y panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **`menubar` 15 % → 88 %** (30 claves, 5 → 35). Global **62 % →
63 %**. Las tres filas que quedan fuera NO son deuda de este eje.
Cosidas la barra (superficie, marco, radio desacoplado de la talla, y las
tres escalas `gap`/`padding`/`height`), el disparador (tinta, tipografía,
escala de `font-size` y de `padding-inline`, el par de la entrada ABIERTA y
la tinta deshabilitada) y el Panel `role=dialog` (tinta + escala). Los pasos
leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`), no el
primitivo crudo que el CSS venía consumiendo, y el `calc(var(--space-1) * 0.5)`
del paso `sm` pasa a `var(--space-0-5)` (mismo computado).
**El hueco del panel deja de repetir el valor del padding**: las tres reglas
de `--floating-gap` compensaban el sangrado del disparador con una COPIA del
padding de la barra; ahora leen `--menubar-padding-{k}`, su fuente. No pueden
leer el privado: esas superficies viajan por PORTAL, y un privado de la raíz
no cruza esa frontera (la ley de ayer, aplicada al derecho).
**⚠ El plano `overlay` GANA a la receta — cuarta prueba de §12.9, y la
primera con MECANISMO.** Acuñé `panel-font-family` y `panel-line-height`; el
centinela las dio muertas. Medido: `[data-depth='overlay']` declara las dos
con la MISMA especificidad (0,1,0) y más tarde en la cascada, así que gana,
mientras `panel-fg` y `panel-font-size-*` —del mismo bloque— sí alcanzan.
Retiradas del contrato, devueltas a su fuente literal con la razón en el CSS.
**Mientras §12.9 no se firme, un token de tipografía en cualquier superficie
`overlay` miente**, y eso vale para los veinte componentes del empate.
**El hover no se acuña: es del eje navigation-menu.** `background:
var(--color-surface-overlay)` sobre el disparador mata la capa del sistema
—medido: `background-image` computa `none` en hover con `--state-hover`
vivo—, por las dos razones que `navigation-menu.css` ya documenta al haber
retirado esa misma regla: el shorthand resetea `background-image`, y la regla
del arquetipo va en `:where()` (especificidad CERO). Acuñar `hover-trigger-bg`
habría fosilizado la invención. → §13.
**Instrumento**: la barra suelta DOS superficies y el Panel —el que lleva sus
knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`)
y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó
`openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`,
que aquí no existe, así que volvía a pulsar el disparador antes de cada token
y CERRABA el panel.
Gates: sonda **0 diffs** en 1.470 valores × 8 estados · centinela 34/35 (la
deshabilitada adjudicada) · censo 88 % · `--names` 0 · audit PASS · rtl 0 ·
docs 0 · suite eidos con el rojo conocido ajeno · `check` sin errores propios
· capturas de barra, tallas, menú y panel.
uix(theming): los dos rojos que quedaban — y el eje `size` del picker MUERE EN EL PORTAL Medidos uno a uno los 18 de `metrics` y los 25 de `color-picker`, los últimos del bloque viejo que el guard R-5.4 arrastraba sin adjudicar. Los dos quedan en verde. Sin cambios de código: esto es medición y registro. `metrics`: los 18 están VIVOS. Ninguno miente; lo que faltaba era el escenario. Seis piden el paso `xxl` (la demo llega a `xl`), dos el icono SIN `data-color` (su defecto sólo pinta cuando no hay color, y todos los de la demo llevan uno), dos las formas `circle`/`square`, cuatro la fila de acciones, uno el gráfico en `end`, uno el borde de la variante `outline`, y dos el sello de sema durante el hold (`data-event-family=signal` + `phase=active`): el destello del valor sólo se mide estampando el sello a mano, porque cuando el guard lee ya se ha ido. `color-picker`: 23 de 25 vivos, y los otros dos son un DEFECTO REAL. EL EJE `size` MUERE EN EL PORTAL, dos veces. `--_color-picker-content-width` se declara TRES veces y no lo consume NADIE: la anchura del panel la fija `--_popover-content-width-override`, cableada al paso `md`. Un picker `sm` y uno `lg` abren el MISMO panel de 344 px, y `content-width-sm`/`-lg` son dos públicos que no mueven nada. Lo mismo con las muestras dentro del panel (`--_color-picker-swatch-size` clavado a `md`): fuera del portal los tres pasos alcanzan (20/24/28 px → 1234 px), dentro sólo el `md`. La causa es una y vale como ley: el portal es una FRONTERA DE CASCADA — un privado declarado en la raíz del componente no la cruza. Misma familia que la capa `list-surface` inalcanzable desde `:root`. El arreglo honesto es una escala por talla en el ámbito del CONTENIDO (que sí lleva su `data-size`); mueve píxel en `sm` y `lg`, así que es firma → §13. Tres cegueras del instrumento, medidas: - El guard CONGELA las transiciones antes de medir —el arreglo que hizo medibles radio-cards y compañía— y `transition-duration`/`transition-ease` SON la transición: sin congelar alcanzan (0.12s → 11.5s). Es genérico: cualquier componente con esos dos públicos los reportará muertos. - Una lectura inmediata de una propiedad EN TRANSICIÓN devuelve el valor VIEJO. Es lo que hacía ilegible el fondo del cuentagotas: ni con `!important` en línea se movía, porque la transición aún no había corrido. - La DEMO pisa el cuentagotas con su regla de ámbito (`.cp-tools-row` le pone `inline-size:auto` y `background`), así que sus dos tokens sólo se miden devolviéndole el consumo del token. Gates: `theming:sentinel metrics` y `color-picker` en verde (54/72 y 46/71, el resto adjudicado por escrito) · `check` sin errores propios · `docs:check` 0/0 · formato sin regresión (los tres ficheros ya venían avisados en HEAD). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-23 — **Los dos rojos que quedaban del bloque viejo, medidos: `metrics`
(18) y `color-picker` (25). Los dos guards en verde; sin cambios de código.**
**`metrics`: los 18 están VIVOS, uno por uno.** Ninguno miente — lo que
faltaba era el escenario: seis piden el paso `xxl` (la demo llega a `xl`),
dos el icono SIN `data-color` (el defecto que sólo pinta cuando no hay
color, y todos los iconos de la demo llevan uno), dos las formas `circle` /
`square` (la demo monta `rounded`), cuatro la fila de acciones (no montada),
uno el gráfico en `end` (la demo los coloca `full`), uno el borde de la
variante `outline`, y **dos el sello de sema durante el hold**
(`data-event-family=signal` + `phase=active`): el destello del valor se mide
estampando el sello a mano, porque cuando el guard lee ya se ha ido.
**`color-picker`: 23 de 25 vivos, y los otros dos son un DEFECTO REAL —
el eje `size` muere en el portal.** `--_color-picker-content-width` se
declara TRES veces y no lo consume NADIE: la anchura del panel la fija
`--_popover-content-width-override` cableada al paso `md`, así que un picker
`sm` y uno `lg` abren el mismo panel de 344 px. Lo mismo con las muestras
dentro del panel (`--_color-picker-swatch-size` clavado a `md`): fuera del
portal los tres pasos alcanzan (20 / 24 / 28 px), dentro sólo el `md`. La
causa es una sola y vale como ley: **el portal es una frontera de cascada —
un privado declarado en la raíz del componente no la cruza.** → §13, porque
arreglarlo mueve píxel en `sm` y `lg`.
Tres cegueras del instrumento, medidas y anotadas: **el guard congela las
transiciones y `transition-duration` / `transition-ease` SON la transición**
(sin congelar: 0.12s → 11.5s) — es genérico, cualquier componente con esos
dos públicos los reportará muertos; **una lectura inmediata de una propiedad
en transición devuelve el valor VIEJO**, que es lo que hacía ilegible el
fondo del cuentagotas; y **la demo pisa el cuentagotas con su propia regla
de ámbito** (`.cp-tools-row` le pone `inline-size:auto` y `background`), así
que sus dos tokens sólo se pueden medir devolviéndole el consumo.
uix(drawer): los 24 rojos medidos uno a uno — y uno era un DEFECTO REAL: el velo no pinta Sin cambios de código: 24 adjudicaciones medidas y dos hallazgos. El guard queda en VERDE. EL VELO DEL CAJÓN NO PINTA. Su regla es background: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent) y **soma escribe `--drawer-overlay-opacity: 1` INLINE** en el nodo del velo — es su progreso de arrastre, un número sin unidad. `color-mix` exige ahí un PORCENTAJE: con `1` la función entera es inválida y el fondo cae a `transparent`. Medido: el velo abierto computa `rgba(0, 0, 0, 0)`; escribiendo `80%` en el propio nodo, pinta. Y VISTO en captura: el fondo tras el cajón sale desenfocado pero **sin atenuar** — el `backdrop-filter` funciona y el tinte no existe. Un cajón modal sin velo. No se arregla aquí porque mueve píxel y toca soma (§7.8). Puede arreglarse de un lado o del otro —que soma escriba un porcentaje, o que la receta no use ese slot como porcentaje—, y la lección general queda escrita en §13: **un valor que soma escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo es silencioso. SEGUNDO HALLAZGO: las tallas son canal de valor en el eje que manda. Soma escribe `width` (o `height`, según el lado) inline en el contenido, así que `content-width-{sm,md,lg}` no puede ganar desde `:root` en un cajón izquierda/derecha; el eje cruzado sí alcanza (`height-sm` forzado: 260px → 976px). Misma clase que el `item-gap` de carousel y el `preview-z` de drag-drop, que se retiraron — pero aquí el token sigue siendo la fuente del valor que soma resuelve, así que retirarlo o no es firma tuya. Los otros 22 son lo esperable, y quedan medidos con su valor: el asa —que la demo no monta y cuyo tamaño INTERCAMBIA ejes según el lado (36px→1234px de largo, 4px→1234px de grosor)—, el anillo de arrastre, el padding `full`, la cabecera y el pie opcionales, y la z de la variante inline. Gates: centinela 29/54 con las 24 adjudicadas, exit 0 · docs:check 0 · captura de la página con el cajón abierto mirada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`drawer`: los 24 rojos que arrastraba, medidos uno a uno — y
uno era un DEFECTO REAL.** El guard queda en verde; sin cambios de código.
**EL VELO DEL CAJÓN NO PINTA.** Su regla es
`color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent)`
y **soma escribe `--drawer-overlay-opacity: 1` INLINE** —su progreso de
arrastre, un número sin unidad—. `color-mix` exige ahí un PORCENTAJE: con
`1` la función entera es inválida y el fondo cae a `transparent`. Medido
(`rgba(0, 0, 0, 0)` en el velo abierto; con `80%` en el nodo, pinta) y VISTO
en captura: el fondo tras el cajón sale desenfocado pero sin atenuar. Mueve
píxel y toca soma, así que va a §13 con su lección: **un valor que soma
escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo
es silencioso.
El segundo hallazgo: **las tallas son canal de valor en el eje que manda** —
soma escribe `width`/`height` inline según el lado, así que
`content-width-*` no gana desde `:root` en un cajón izquierda/derecha (el
eje cruzado sí: `height-sm` 260px → 976px). Misma clase que el `item-gap`
de carousel, pero aquí el token sigue siendo la fuente del valor que soma
resuelve: retirar o no es firma.
Los otros 22 son lo esperable y quedan medidos: el asa (no montada, y su
tamaño INTERCAMBIA ejes según el lado), el anillo de arrastre, el padding
`full`, la cabecera y el pie opcionales, la z de la variante inline.
uix(theming): revisión adversarial del bloque — la pasada de HOVER envenenaba a todos los tokens de reposo Cinco hipótesis, un hallazgo real, cuatro refutadas. Bloque de CATORCE componentes; global del eje 56 % → **62 %**. HALLAZGO REAL — la anomalía de `float-panel` tenía causa, y era del guard. `resize-grip-fg` movía aislado y leía muerto en una corrida completa. Instrumenté el propio guard para probar UNA clave y salió `moved=true`; luego, en la corrida entera, muerto. La causa: **la pasada de HOVER deja el puntero sobre el último nodo que tocó**, y una regla `:hover` gana a la de reposo con la que comparte nodo — el asa re-apunta su color al acento al pasar el ratón. Es decir: **cualquier token de reposo probado DESPUÉS de un token `hover-*` podía leer muerto**. Es exactamente la clase que F2-A arregló con el clic-foco, una pasada más tarde: allí se aparcó el puntero tras ABRIR, aquí faltaba aparcarlo tras HOVERAR. Arreglado. Y entonces el propio guard delató DOS excepciones STALE — `float-panel.resize-grip-fg` y `media-player.track` —, retiradas: dos tokens que el ledger daba por muertos están vivos. Se confirma la regla de F2-A: **un falso negativo siempre tiene causa**; registrarlo como «sin causa» era el paso previo, no el final. REFUTADA «hay tokens huérfanos»: comprobación ESTRICTA —comentarios despojados, que es el hueco por el que `popover.bg` sobrevivió, sobre todo el CSS del árbol— de las **553 claves** de los catorce bloques: ninguna declarada sin que una regla la lea. REFUTADA «el cambio de instrumento rompió algo»: los **27 componentes con ledger re-verificados** tras aparcar el puntero — cero regresiones. Los tres rojos que quedan (`drawer` 24, `metrics` 16, `color-picker` 23) son ANTERIORES al bloque y están anotados con lo que ya se descartó de cada uno. REFUTADA «quedan fantasmas del barrido en lo tocado»: pasa de 9 a **8** — el `--color-content-tertiary` de float-panel murió con su costura. Los ocho restantes son de componentes que este bloque no tocó. CONFIRMADO, y ya estaba escrito: hay gates FINOS. `popover` mide 1 nodo en reposo (7 en abierto), `drawer` 1, `context-menu` 1 + 14 en abierto, `color-picker` 5 + 12. Cada veredicto lo dice, y lo que sostiene esas cifras es el forzado a mano de sus estados, no la sonda. Guards: `--names` sin desviadas · docs:check 0 · censo 62 % (2.877 públicos de 5.137 knobs; `global` 1.145 → 912). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **REVISIÓN ADVERSARIAL DEL BLOQUE (§7.7): 5 hipótesis, 1
hallazgo real, 4 refutadas.** Bloque de CATORCE componentes; global 56 % →
**62 %**.
**HALLAZGO REAL — la anomalía de `float-panel` tenía causa, y era del guard.**
`resize-grip-fg` movía aislado y leía muerto en una corrida completa. La
causa: **la pasada de HOVER deja el puntero sobre el último nodo que tocó**, y
una regla `:hover` gana a la de reposo con la que comparte nodo (el asa
re-apunta su color al acento). Es decir: **cualquier token de reposo probado
después de un token `hover-*` podía leer muerto** — misma clase que el
envenenamiento por clic-foco que F2-A arregló, una pasada más tarde.
Arreglado aparcando el puntero tras la pasada de hover. El propio guard
delató entonces DOS excepciones STALE —`float-panel.resize-grip-fg` y
`media-player.track`—, retiradas: dos tokens que se daban por muertos están
vivos. Se confirma la regla de F2-A: **un falso negativo siempre tiene causa**;
registrarlo como «sin causa» era el paso previo, no el final.
**REFUTADA «hay tokens huérfanos»**: comprobación ESTRICTA (comentarios
despojados —el hueco que dejó vivo el `popover.bg`— sobre todo el CSS del
árbol) de las **553 claves** de los catorce bloques: ninguna declarada sin que
una regla la lea.
**REFUTADA «el cambio de instrumento rompió algo»**: los **27 componentes con
ledger re-verificados** tras aparcar el puntero — cero regresiones. Los tres
rojos que quedan (`drawer` 24, `metrics` 16, `color-picker` 23) son
ANTERIORES al bloque y están anotados.
**REFUTADA «quedan fantasmas del barrido en lo tocado»**: el barrido pasa de
9 a **8** — el `--color-content-tertiary` de float-panel murió con su costura.
Los ocho restantes son de componentes que este bloque no tocó.
**CONFIRMADO, y ya estaba escrito: hay gates FINOS.** `popover` mide 1 nodo en
reposo (7 en abierto), `drawer` 1, `context-menu` 1 + 14 en abierto,
`color-picker` 5 + 12. Cada veredicto lo dice; el forzado a mano de sus
estados es lo que sostiene esas cifras, no la sonda.
uix(password-field): temable — 41 % → 62 %, y el guard queda EN VERDE 7 claves (23 → 30). Censo password-field 41 % → **62 %**; el centinela pasa a verde con **las siete adjudicaciones medidas**, incluidas las tres que este componente arrastraba sin medir. Cosido: el tracking del input enmascarado —los puntos leen mejor algo sueltos—, el chip de bloq-mayús (radio y peso; su peso llevaba un `500` muerto detrás de un token que ya vale 500) y **los cuatro peldaños del medidor de fuerza**. LOS CUATRO PELDAÑOS SON UNA ESCALA DE INTENCIÓN, no cuatro colores sueltos: cada uno lee su rol —pérdida, riesgo, cumplimiento, afirmación— y ahora un tema puede recorrer la escala entera sin tocar los roles del sistema. De paso muere otra referencia con respaldo: el primer peldaño leía `var(--color-loss-solid, var(--color-threat-solid))` y el primitivo SÍ existe, así que el respaldo era ruido. LO QUE NO SE ACUÑA, Y ES COMPOSICIÓN: el `padding-inline` del medidor y el `font-size` de su rótulo leen privados de FIELD (`--_field-control-*`), porque este componente monta un Field. Se teman en la ficha del dueño — mandato de composición, `theming-audit` §5.3-3. Siete adjudicaciones, todas medidas: los cuatro peldaños sólo pintan bajo su `data-strength` y la demo arranca con el campo vacío (fuerza 0, ningún segmento activo); las dos de transición las congela el guard; y la opacidad deshabilitada vive en el disparador de visibilidad, no en la raíz — por eso mi primera medición dio «sin efecto» y hubo que buscar el nodo que la regla señala. Gates: sonda antes/después **0 diffs en 3.060 valores** (14 nodos × 8 estados) · centinela 23/30 con las siete adjudicadas, exit 0 · censo --only 62 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (campo, medidor de cuatro segmentos y rótulo en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`password-field` 41 % → 62 %** (7 claves) **y el guard en
VERDE**: sus siete silencios quedan medidos, incluidos los tres que arrastraba
de antes.
Los cuatro peldaños del medidor de fuerza son una ESCALA DE INTENCIÓN —cada
uno lee su rol: pérdida, riesgo, cumplimiento, afirmación—, así que un tema
puede recorrerla entera sin tocar los roles del sistema. Fuera, por
composición: el padding del medidor y el tamaño de su rótulo leen privados de
FIELD (§5.3-3, se teman en la ficha del dueño).
Gates: sonda 0 diffs en 3.060 valores · censo 62 % · audit PASS · lint 0 ·
suite con el rojo conocido · rtl 0 · docs 0 · captura mirada.
uix(context-menu): temable — 53 % → 90 %, y el instrumento aprende a abrir un menú contextual 11 claves (18 → 29). Censo context-menu 53 % → **90 %** con `global` a CERO. EL INSTRUMENTO NO SABÍA ABRIRLO. Un menú contextual se abre con clic DERECHO y con nada más, así que sonda y guard medían UN nodo: el disparador. Los dos aprenden el modo (`openBy: 'contextmenu'` en `COMPONENT_OVERRIDES`, clic derecho en la sonda) y pasan a ver **14 nodos** en el estado abierto; el guard, de un baremo ciego a **27/29**. Cosido el cromo propio del panel y del disparador: tipografía y tinta del contenido, tinta e interlineado de la fila, el galón del submenú (dos bordes girados 45°, un solo trazo), el encabezado de grupo (tinta, peso y tracking de versalitas) y el contorno DISCONTINUO que marca el disparador abierto — el estilo `dashed` es identidad de la afordancia; el grosor y el color son knobs. Dos limpiezas de paso: el `font-family` leía `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo— y el peso del encabezado llevaba un `500` muerto detrás de un token que ya vale 500. DOS ADJUDICACIONES MEDIDAS, y una es la TERCERA ocurrencia del mismo hallazgo: `content-font-family` no puede ganar porque el plano `overlay` impone la tipografía del panel (§12.9, pendiente de tu firma — ya medido en `popover` y en `float-panel`). La otra es el ítem deshabilitado, que la demo no monta: forzado, 0.55 → 0.123. Gates: sonda antes/después **0 diffs en 600 valores** (8 estados, 14 nodos en el abierto) · centinela 27/29 · censo --only 90 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del panel mirada (encabezado en versalitas, filas, casillas, galón del submenú y separadores en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`context-menu` 53 % → 90 %** (11 claves, `global` a cero), y
**el instrumento aprende a abrir un menú contextual**: se abre con clic
DERECHO y con nada más, así que sonda y guard medían UN nodo. Con
`openBy: 'contextmenu'` y el clic derecho en la sonda: 14 nodos en abierto y
el guard en **27/29**.
Dos limpiezas: el `font-family` era respaldo de sí mismo y el peso del
encabezado llevaba un `500` muerto. Dos adjudicaciones, y una es la TERCERA
ocurrencia de §12.9 (el plano `overlay` impone la tipografía del panel).
Gates: sonda 0 diffs en 600 valores · censo 90 % · audit PASS · lint 0 · suite
con el rojo conocido · rtl 0 · docs 0 · captura del panel mirada.
- 2026-08-22 — **`metrics` 74 % → 82 %** (4 claves + prefijo abreviado fuera,
`global` a cero). **`--_mi-*` violaba theming §6 r5** —segundo caso del día
tras `--cp-*`—: 16 ocurrencias renombradas a `--_metrics-icon-*`. Son el
conmutador del icono por variante y ya derivaban de públicos, así que el censo
los reclasifica solo. Cosido: el ritmo del trend y las TRES formas del icono
(una clave por valor de `data-shape`; el radio es lo único que las distingue).
Dos adjudicaciones forzando las formas que la demo no monta. Sonda 0 diffs en
1.590 valores · censo 82 % · audit PASS · lint 0 · captura mirada.
uix(float-panel): temable — 59 % → 84 %, muere un fantasma y queda una anomalía registrada 12 claves (28 → 40). Censo float-panel 59 % → **84 %** con `global` a CERO. Cosido: las dos VARIANTES que ajustan desde el default elevado (`outline-*`, `ghost-*`), los botones de su barra (`action-*`, `hover-action-*`) y el asa de redimensionado. MUERE UNO DE LOS NUEVE FANTASMAS DEL BARRIDO de esta mañana: el asa leía `var(--color-content-tertiary, var(--color-content-secondary))` y ese primitivo NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad pintaba, con lo que la referencia muerta desaparece sin mover un píxel. ONCE ADJUDICACIONES MEDIDAS: la demo monta la variante por DEFECTO —ni outline ni ghost— y no tiene botón de acción genérico, sólo el control de cierre; se forzó el atributo de variante y se montó el botón donde el CSS lo selecciona, y el radio del asa sólo pinta bajo `:focus-visible`. Las once alcanzan. Y UNA ANOMALÍA REGISTRADA EN VEZ DE ESCONDIDA: `resize-grip-fg` **mueve** bajo una réplica a mano de la secuencia del propio guard —congelar transiciones, blur, aparcar el ratón, escribir en `:root` y en cada nodo con el prefijo, fotografiar los mismos props con `::before`/`::after`/`::placeholder`— (oklch(0.5032 0 0) → rgb(1,2,3)), y aun así el guard lo da por muerto. Descartado que el nodo no lleve el atributo (lo lleva), que la regla necesite un estado (es la regla base), que sea el valor centinela (es el mismo) y que sea HMR (dos corridas). Queda adjudicado CON la medición y marcado como anomalía en §13: el token está vivo, lo que está en duda es la lectura. Es por donde debe empezar la revisión adversarial de este bloque — el precedente de F2-A dice que estos casos sí tienen causa. Lo rojo que queda son 12 tokens anteriores, y seis de ellos —`bg`, `border`, `border-width`, `shadow`, `font-family`, `line-height`— son el mismo conjunto que en `popover`: el PLANO de profundidad gana sobre el panel (§12.9, pendiente de firma). Gates: sonda antes/después **0 diffs en 1.890 valores** (9 nodos × 7 estados) · censo --only 84 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (cabecera, botón de cierre y asa de la esquina en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`float-panel` 59 % → 84 %** (12 claves, `global` a cero).
Las dos variantes que ajustan desde el default elevado, los botones de la
barra y el asa. **Muere uno de los nueve fantasmas del barrido**: el asa leía
`var(--color-content-tertiary, …)` y ese primitivo no existe — el contrato
guarda lo que de verdad pintaba.
Once adjudicaciones medidas (la demo monta la variante por defecto y no tiene
botón de acción genérico) **y una ANOMALÍA registrada**: `resize-grip-fg`
mueve bajo una réplica a mano de la secuencia del propio guard y aun así lo da
por muerto; causa no encontrada, descartadas cuatro. El token está vivo; la
lectura es lo que está en duda → §13, primer sitio por donde empezar la
revisión adversarial de este bloque.
Los 12 rojos que quedan son anteriores, y seis son el mismo conjunto que en
`popover`: el plano de profundidad gana sobre el panel (§12.9).
Gates: sonda 0 diffs en 1.890 valores · censo 84 % · `--names` sin desviadas ·
audit PASS · lint 0 · suite con el rojo conocido · rtl 0 · docs 0 · captura
mirada (cabecera, cierre y asa en su sitio).
uix(nav-tree): temable — 4 % → 96 %, y 18/18 en el guard sin una sola excepción 17 claves nuevas (1 → 18): tenía UNA sola pública (`indent`) y todo lo demás iba a primitivo crudo. Censo nav-tree 4 % → **96 %**; global del eje 61 %. LA FILA ES LINK + TRIGGER A LA VEZ. Las reglas los tratan juntos —mismo padding, misma tinta, mismo radio, mismo hover— así que el knob es de la FILA (`row-*`), no de cada parte: es una pseudo-parte, el molde firmado de las regiones de `waveform`. Sus estados llevan el modificador DELANTE, que es lo que la gramática pide para lo interactivo (`hover-row-fg`, `active-row-fg`, `disabled-row-fg`), y la estela —los ancestros de la página actual— es contextual y va DETRÁS (`trail-row-fg`). El disparador sí tiene lo suyo: el suelo de 24 px del blanco táctil (WCAG 2.5.8) y su padding. Y el galón es una escuadra dibujada con dos bordes (`::after` con `border-inline-end` + `border-block-end` girado 45°): su lado es un knob y el grosor del trazo otro — mismo molde que la esquina de `text-focus`. LA SUITE CAZÓ UN INCUMPLIMIENTO HEREDADO, tercera vez en el eje: el contrato copió `var(--font-size-sm)` verbatim del CSS, y ésa es la coordenada CRUDA que `recipe-css-contract` prohíbe en un token de receta. Corregido al bundle (`var(--size-sm-font-size)`), que computa idéntico. El «valor verbatim» arrastra el defecto del CSS de partida siempre que el CSS ya lo tuviera. DOS CEGUERAS MÁS DEL GUARD, medidas y arregladas: 1. **La fila es un `<div>` pelado sin atributo propio**, así que el `gap` entre link y trigger leía muerto moviendo 4px → 1234px. 2. **No leía el ANCHO de un borde que no fuera el de arriba**: el galón tiñe los de inline-end y block-end. Añadidos los tres lados que faltaban (los COLORES por lado ya se añadieron con `prose`, hoy mismo). Con las dos: **18/18 en vivo, sin una sola excepción**. Los nueve componentes con ledger re-verificados tras tocar las PROPS del guard: sin regresiones ni STALE. Lo que queda fuera: el `100%` de la fila (identidad) y el anillo de foco (sistema). Gates: sonda antes/después **0 diffs en 22.140 valores** (102 nodos × 8 estados) · centinela 18/18 · censo --only 96 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del escenario mirada (raíl, galones, estela y fila activa intactos). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`nav-tree` 4 % → 96 %** (17 claves; tenía UNA). Global 61 %.
**La FILA es link + trigger a la vez** —mismo padding, tinta, radio y hover—,
así que el knob es de la fila (pseudo-parte, molde de `waveform`), con el
modificador DELANTE en lo interactivo (`hover-`/`active-`/`disabled-row-fg`)
y DETRÁS en lo contextual (`trail-row-fg`). El galón es una escuadra de dos
bordes: lado y trazo, como la esquina de `text-focus`.
**La suite cazó un incumplimiento heredado**, tercera vez en el eje: el
contrato copió `var(--font-size-sm)` verbatim del CSS y ésa es la coordenada
CRUDA que `recipe-css-contract` prohíbe. Al bundle, computa idéntico.
**Dos cegueras más del guard**: la fila es un `<div>` pelado sin atributo (su
`gap` leía muerto) y no leía el ANCHO de un borde que no fuera el de arriba.
Con las dos: **18/18 sin una sola excepción**. Los nueve componentes con
ledger re-verificados tras tocar las PROPS: sin regresiones ni STALE.
Gates: sonda **0 diffs en 22.140 valores** (102 nodos × 8 estados) · censo
96 % · `--names` sin desviadas · audit PASS · lint 0 · suite con el rojo
conocido · rtl 0 · docs 0 · captura del escenario mirada.
uix(drawer): temable — 57 % → 72 %, el disparador gemelo del de popover 10 claves (44 → 54). Censo drawer 57 % → **72 %**; global del eje 61 %. Mismo cromo que el disparador de `popover` y con la misma corrección: `min-block-size` lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo crudo que el CSS traía. Aquí no hay estado abierto que teñir, así que el hover lleva una sola clave. LO QUE QUEDA FUERA ES LA FIRMA DE SALIDA, y no se cose aquí: los cuatro `border-{side}: var(--border-width-thick) solid transparent` son la base que las reglas de `data-last-action` tiñen al cerrar. La misma matriz vive en `popover` y `dialog`, ya ha derivado entre las tres, y es mapeo transversal según la tabla morfo→eidos → §13. Los ocho privados son canales de valor (los `*-override` que el wrapper escribe desde sus props, y el par `panel-width/height` de la variante persistente) y los cinco literales, identidad. UNA ADJUDICACIÓN, MEDIDA, Y DE UNA CLASE NUEVA DEL INSTRUMENTO: el guard ABRE el cajón antes de probar, y entonces el velo tapa el disparador — la pasada de hover no puede posarse en él (Playwright se niega a hacer hover sobre un nodo que no recibe puntero). Con el cajón cerrado y hover a mano, `hover-trigger-bg` alcanza (oklch(0.931 0 0) → rgb(1,2,3)). Lo rojo que queda son **24 tokens anteriores** a este commit —el asa de arrastre, el anillo de arrastre, los anchos y altos por talla, los gaps de cabecera y pie—, todos de partes o estados que la demo no monta. Este commit no los empeora. Gates: sonda antes/después **0 diffs en 390 valores** (8 estados) · censo --only 72 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del escenario mirada (disparador con su cromo intacto). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- 2026-08-22 — **`drawer` 57 % → 72 %** (10 claves: el disparador, gemelo del
de popover, con `min-block-size` al bundle). Global 61 %.
Lo que queda fuera es la FIRMA DE SALIDA (los cuatro bordes base que
`data-last-action` tiñe al cerrar) → §13, y ocho privados que son canales de
valor del wrapper.
**Una adjudicación de clase nueva**: el guard ABRE el cajón antes de probar y
el velo tapa el disparador, así que la pasada de hover no puede posarse en él.
Con el cajón cerrado y hover a mano, `hover-trigger-bg` alcanza. Los otros 24
rojos son anteriores (asa, anillo de arrastre, tallas del panel).
Gates: sonda 0 diffs en 390 valores · censo 72 % · `--names` sin desviadas ·
audit PASS · lint 0 · suite con el rojo conocido · rtl 0 · docs 0.
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.