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

3090 lines
233 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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
> 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.
>
> **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:
| 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 |
**Alcance** = `public / (public + private + global + literal)`.
### 1.1 Totales
| 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 |
### 1.2 Por familia (propuesta de familias en D-TH.4 — el autor la firma o la cambia)
| 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 |
### 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:
| 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 |
**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,
border, on-bg, on-fg, on-border, hover-bg, on-hover-bg`). **Hoy no se
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.
- **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).
---
## 4. Decisiones que firma el autor (una por mensaje, con sus cinco preguntas)
| 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). |
---
## 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;
(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
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.
- **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».
### 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.
| 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 | — |
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í
| 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. |
---
## 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.
### 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.
8.bis **ANTES DE RETIRAR UNA CLAVE, MIRA QUIÉN LA LEE.** R-5.4 dice que un
token que no mueve nada es un token que miente, y esta pasada ha retirado
varias con razón (`file-upload.button-bg`, `avatar.group-max`,
`editable.control-border`). Pero **leer muerto en SU demo no es leer muerto
en el catálogo**: `scroll-area.auto-max-block` salía silenciosa en su propia
página y la consumen **cinco recetas ajenas** —`combobox`, `command`,
`gradient-picker`, `select` y `palabras`—; `slider.min-block-size` la fija
`media-player` para su volumen vertical. Retirar cualquiera de las dos
habría roto seis componentes sin que un solo guard se pusiera rojo.
El chequeo cuesta un `grep` y es OBLIGATORIO:
`grep -rl -- '--{c}-{clave}' src/uix/eidos/components/`. Si aparece alguien
más, la clave **no se retira**: se adjudica con esa razón.
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).
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.
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.
### 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`
- `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.
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.
**Excepción FIRMADA 2026-08-24 — completar una demo que no monta su propio
componente ES instrumento, no diseño.** La regla «las demos quedan FUERA de
este eje» sigue en pie para lo que fue escrita: **un hallazgo de demo o de
harness no se registra ni se mide**, y no se rediseña una demo «ya que
estamos». Pero una demo que sólo menciona en prosa partes que la receta VISTE
y el morfo DECLARA no es un testbed: es una página, y deja tokens vivos sin un
nodo donde probarse (`chat-log`, 2026-08-23: tres de sus seis partes sin
montar, **once claves sin nodo que pintar**). Montarlas está permitido con las
CUATRO condiciones, todas verificables:
1. Sólo partes o estados **del propio componente**, declarados en su morfo.
Nada de otro componente, nada que el morfo no nombre.
2. **Contenido mínimo canónico**: el ejemplo más corto que hace existir la
parte. Cero diseño nuevo, cero variantes decorativas.
3. **El diff de computed sobre lo que YA estaba montado sigue siendo CERO** —
se mide igual que el resto: la demo crece, lo que ya se veía no se mueve.
4. **Declarado en el mensaje de commit**, con las claves que pasan de «sin
nodo» a medibles.
Fuera de esas cuatro, la demo no se toca desde este eje. Y lo que se aprenda
mirando la demo sigue **sin** entrar en el ratio ni en el registro: la
excepción abre la mano para MEDIR, no para contabilizar.
---
## 8. Registro
- 2026-08-25 — **FIRMA: las cuatro claves de `avatar` fuera de gramática dicen
ya la verdad** (opción A-c). Las cuatro eran las **únicas 4 desviaciones de
4.558 claves públicas** del catálogo, y la medida demostró que **no son la
misma cosa**: son tres tratos distintos.
**(1) Una se RENOMBRA.** `badge-color-custom-contrast` →
**`badge-fg-custom-contrast`**. Es la única que es tinta de verdad
(`avatar.css` la lleva a `--_avatar-badge-fg` → `color:`) y la única de las
tres `*-custom*` que **alcanza desde `:root`** (medido en la insignia real:
`white` → `rgb(4,5,6)`, restaurado). La derivación no es de gusto: es la
salida literal del clasificador firmado (`classifyName`), que consume R-5.3 y
que ejecutó el codemod de 196 claves. `custom` **no** promociona al frente
porque no está en `UNIVERSAL_MODIFIERS` (D-TH.6: «delante lo interactivo,
detrás lo dimensional y contextual») y **no** es rol/paleta —no está en
`ROLES`, y el morfo lo prueba: `data-color` es un eje enumerado aparte y la
vía custom es una PUERTA distinta, ausente por diseño cuando se pasa un rol—.
El precedente exacto es la propia acta: `scrim-color-on-dark` →
`scrim-fg-over-dark` cambió la ranura **en su sitio** y dejó el calificador
detrás; `bubble-fg-in`/`-out` repiten la forma. El candidato de sonido natural
`badge-custom-fg` se descarta por eso.
**(2) Dos se RETIRAN del contrato.** `ring-color-custom` y
`badge-color-custom` son **canal de valor, no knobs**: la puerta
`data-*-custom` y la escritura inline del token salen de la MISMA expresión
del envoltorio (`avatar.svelte:93`+`:125`, `avatar-badge.svelte:36`+`:48`),
así que viajan juntas y **no existe ni puede existir** instancia en la que un
tema le gane al inline (re-medido: escribir cualquiera de las dos en `:root`
no mueve nada, ni antes ni después). El MECANISMO elegido es el de la casa,
no el de `group-max`: aquel se borró de los dos sitios porque **no lo leía
nadie**, mientras que aquí el CSS sí los lee, así que borrarlos rompería la
función. Se retiran de la RECETA y el canal pasa a privado —
`--_avatar-ring-color-custom` / `--_avatar-badge-color-custom`, escrito por el
envoltorio y leído con `var(…, currentColor)`. Es la forma que ya usan **5 de
5** privadas de envoltorio del parque (`background-gradient-image`,
`carousel-vertical-block-size`, `checkbox-group-columns`,
`file-upload-progress-value`, `gradient-builder-stop-fill`): **ninguna** se
declara en la receta y todas llevan el respaldo en el `var()`. Y es la
adjudicación que `tabs` firmó para su indicador («un público encima sería un
knob que un tema puede fijar»). El respaldo `currentColor` **no** reintroduce
el antipatrón del `, white` que esta misma ficha retiró: aquel duplicaba el
default de un token del contrato y podía envejecer contra él; éste es la única
declaración que queda del default. Verificado sobre la **instancia imposible**
(quitar el inline dejando la puerta): anillo `oklch(0.5032 0 0)` e insignia
`rgb(255,255,255)` **idénticos** antes y después.
**(3) Una queda EXENTA con razón escrita.** `group-carve-color` alimenta
`box-shadow` (`avatar.css`): es el **filo que recorta la silueta** contra el
avatar anterior, geometría y no tinta, y alcanza de verdad — medido en las
**59** instancias de `/uix/components/avatar-group` (`oklch(0.9911 0 0)` →
`rgb(1,2,3)`, restaurado). `group-carve-fg` sería gramática correcta y
semántica peor, así que entra en `NAME_EXEMPT` como cuarta entrada, la primera
que no es un falso amigo sino una medida.
**Cifras.** Contrato público **88 → 86** filas, comprobado EN VIVO: la pestaña
del demo se llama ya «Tokens 86», trae `--avatar-badge-fg-custom-contrast`
con `white` → `white` y **ninguna** privada se ha colado. Censo `--names`:
**4 desviadas en 1 componente → 0 en 0**, es decir **CERO de catálogo** sobre
4.556 claves (conformes 4.084 → 4.085, exentas 13 → 14). Diff de
`generated/base.css`: **un solo hunk**, 3 bajas y 1 alta, exactamente el
renombre y las dos retiradas. Sonda §7.4 antes/después: **0 diffs sobre 576
valores computados**; el píxel de los nodos con puerta, idéntico (anillo
`rgb(245,158,11) 0 0 0 2px`, insignia `rgb(124,58,237)`, tinta `white`).
**0 píxeles**, que es lo que la opción firmaba.
**Dos deudas de INSTRUMENTO que esta firma destapa** (las dos a next-features
§13, ninguna arreglada aquí porque tocan herramienta compartida): el **codemod
de nombres aborta sobre `avatar`** —`blockRange()` busca `nombre: {` y el
bloque es una IIFE, la misma ceguera que `f68bac4a6` corrigió en censo y
centinela—, y el **centinela elige el valor de sonda por el NOMBRE de la
clave**: su prueba de tinta es `fg$` y la cadena lleva `-bg-` pero **no**
`-fg-`, así que toda clave con `fg` MEDIAL recibe `1234px`, inválido para
`color:`. Son **cinco** en el catálogo, y D-TH.6 fabrica esa forma:
`badge-fg-custom-contrast`, `bubble-fg-in`, `bubble-fg-out`,
`scrim-fg-over-dark`, `scrim-fg-over-light`. Reproducido bajo las condiciones
del propio guard (mismo conjunto de 55 nodos, mismo barrido de `data-variant`,
mismo forzado de talla): con `1234px` no mueve en ningún combo, con
`rgb(1,2,3)` mueve en `solid`. Por eso la clave renombrada entra en el ledger
con su razón medida — y por eso el guard queda en **exit 0**, 80/86 con 6
adjudicadas.
- 2026-08-25 — **FIRMA: `tags-input` deja de sellar `aria-selected` como
LITERAL** (opción B-a2). Una línea de morfo:
`{ attr: 'aria-selected', value: v.literal('true') }` →
`v.stateRef('active')`, el estado que soma **ya** deriva de
`highlightedIndex` — soma sólo registra la fuente por parte
(`states: { active: () => this.isHighlighted }` en `TagsInputItemProvider`),
así que `renderProps()` lo resuelve desde el morfo en vez de re-declararlo.
Era el **único `aria-selected` incondicional del catálogo**: 19
declaraciones en 18 morfos, 8 por `v.stateRef`, 9 por `v.propRef` y las dos
de `tree-view` con `condition`. **Sin `severity`**, como los dos precedentes
más cercanos (`listbox:129`, `role=option`; `tabs:96`, el mismo estado
`active`); `tag-group` la lleva pero es INERTE — `compile.ts:583` la guarda y
nadie la lee, `morfo-check.ts:154` y `permutation-check.ts:97` sólo miran la
de los `data-*`. El modo compilado es `aria-boolean`
(`compile.ts:577`/`:702`), que emite SIEMPRE `'true'` o `'false'`: nunca
ausencia.
**Lo que arregla, medido.** (1) **A11y de raíz**: el árbol AX por CDP
entregaba **dos `option` con `selected: true` dentro de dos `listbox` con
`multiselectable: false`** — contradicción en la plataforma, no sólo en el
atributo; ahora da **0 seleccionadas en reposo, exactamente 1 navegando con
el teclado y 0 tras `Escape`**. (2) **Revive `--tags-input-item-fg`**, que no
había pintado nunca: escrito en `:root` sobre la etiqueta real,
`oklch(0.2435 0 0)` → `rgb(1, 2, 3)` (y vuelve al retirarlo). El centinela
pasa de **63/91 con 28 adjudicadas** (2026-08-24) a **71/91 con 20**, exit 0,
**cero sin adjudicar y cero STALE** — 7 de las 8 nuevas vivas son de la firma
B′ y la octava es `item-fg`, cuya entrada del ledger se RETIRA. (3)
**Devuelve la afordancia de hover**, que no existía: con el literal, reposo y
hover de la misma etiqueta eran **byte a byte IDÉNTICOS** (media RGB 212,10 y
212,10, puntero real, 2×); ahora **224,58 en reposo y 212,10 en hover**.
**Diff de sonda: 14 valores de 2.504**, y los catorce son el mismo:
`background-image` de las DOS etiquetas en los siete pases sin puntero
(`rest`, las cinco tallas y `open`), del velo `linear-gradient(…/0.08)` a
`none`. El pase `hover` sale con **0 diferencias** — el velo del hover era y
sigue siendo el mismo, y ésa es justo la razón de que el reposo no se
distinguiera. Ninguna otra propiedad, ningún nodo de más ni de menos.
Reproducido a mano sobre CÓDIGO IDÉNTICO (re-estampando `aria-selected='true'`
en el árbol vivo): los cuatro números vuelven.
**Una atribución CORREGIDA de la ficha anterior**: lo que enmascara la tinta
de TONO de la etiqueta activa **no es `aria-selected`** sino
`[data-archetype='item'][data-highlighted]` **(0,3,0)**, que sigue ganando a
`[data-tags-input-item][data-state='active']` (0,2,0). Re-medido sobre la
etiqueta real navegada por TECLADO: quitar `aria-selected` no cambia nada
(sigue `oklch(0.2435 0 0)`), quitar además `data-highlighted` es lo que deja
pasar el tono (`oklch(0.5168 0.1733 305.88)`). La medida vieja lo atribuía a
`aria-selected` porque forzaba `data-state=active` a mano, sin
`data-highlighted`. El ledger recoge la corrección; **el hueco sigue abierto
en next-features §13**, junto con los otros dos que la firma destapa (el
`apg: grid` que el morfo declara e implementa como listbox/option, y los dos
`role='listbox'` anidados de Provider y Control). Cinco ficheros:
morfo, provider, su test, el README de la receta y el ledger. Gates: `morfo`
entero 222/222, `soma/components/tags-input` 3/3, `eidos` 442/443 (el rojo
ajeno conocido, `lint.test.ts` / `skin-media-player`).
- 2026-08-24 — **FIRMA: el bloque de acción de `form` apuntaba a las partes
EQUIVOCADAS.** `form.css:85-190` metía cuatro nodos en el mismo grupo:
`[data-form-submit]`, `[data-form-reset]` y los dos botones del array de
AutoFields. Los dos primeros COMPONEN el `Button` canónico —llevan
`data-button`, que casa a la misma especificidad (0,1,0) y carga después—, así
que ni una de las 23 claves les llegaba; `docs/guides/component-guide.md:126-146`
ya prohíbe esa regla **nombrando a `Form.Submit`** (consumer pattern rule 5).
Los otros dos, en cambio, son `<button>` PELADOS que no componen nada
(`form-auto-fields.svelte:563-568` y `:584-591`) y este bloque es todo el
cromo que tienen. El grupo pasa a apuntar **sólo a ellos**; `submit`/`reset`
ceden, y con ellos mueren los cuatro forwards `_palette-*` THM-2
(`base.ts`, `parts: ['submit','reset']`) que nadie consumía ya.
**Los números**: ceder cuesta **CERO** — 0 diffs en la sonda compartida (834
valores × 7 pasos, los cuatro nodos de la demo) sobre la medición previa del
verificador, que ya daba 0 sobre **79.296 valores en 16 escenarios** (5 tallas
× 5 variantes × 3 colores × hover × foco × disabled). Contrato **64 públicas
→ 64**, privados **4 → 0**. Centinela `form` **22/64** con 42 adjudicadas,
exit 0, idéntico antes y después: sólo cambia la RAZÓN de las 23. Del generado
desaparecen exactamente las 14 líneas de las cuatro claves (el suelo
`:where([data-form-submit], [data-form-reset])` y su forward); `palette.css`
no se mueve.
**Lo que enseñó**: (1) **una adjudicación por patrón puede ser cierta en el
número y falsa en la razón.** El ledger decía «SUPERSEDED por el `Button`
compuesto» de las 23 y afirmaba que «los dos botones del array llevan también
`data-button`». No lo llevan. Las 23 no estaban muertas: estaban **vivas sobre
las partes equivocadas del grupo** — 23 de 23 alcanzan al montar los botones
del array, en las cinco tallas. Retirarlas, que es lo que la razón vieja
invitaba a hacer, habría dejado dos botones reales en el `<button>` del UA
(89,52×36 → 68,61×21 px, borde `outset`, Arial 13,33). Una razón escrita para
un grupo de selectores hay que comprobarla **selector por selector**. (2) La
cifra correcta era **23**, no 24: tres documentos la habían copiado mal y sólo
el handoff la tenía bien.
- 2026-08-24 — **FIRMA: `file-upload` deja de BARRER DESCENDIENTES.** Su receta
escribía `[data-file-upload] [data-disabled]` (0,2,0) —única ocurrencia de
«combinador descendiente hacia un atributo de estado desnudo» en las **171**
recetas del árbol (el claim decía 162; el censo creció)— y vestía TODO nodo
deshabilitado bajo el provider, huéspedes incluidos. Pasa a **cinco selectores
por parte propia** (`label`, `dropzone`, `trigger`, `item-remove`,
`clear-trigger`), conservando la mitad del provider. Misma especificidad, así
que ninguna otra regla se mueve.
**Por qué se podía retirar entero**: el morfo declara `data-disabled` en 6
partes y soma lo escribe en esos 6 nodos exactos
(`file-upload-provider.svelte.ts:265, 300, 399, 450, 751, 838`), **siempre
junto a su propio `data-file-upload-{parte}`**. El barrido era 100 %
redundante hacia dentro y sólo añadía alcance hacia fuera. Censo por demo:
`/file-upload` 6 propios / 0 ajenos · `/image-picker` 2 / 0 ·
**`/chat-composer` 0 propios / 4 AJENOS** — allí no pintaba ni un nodo suyo, y
`[data-file-upload]` ni siquiera estaba deshabilitado.
**Los números**: `file-upload` diff **CERO ESTRICTO** (4.608 valores × 8
estados) y **0/35** en el estado disabled; `image-picker` **0/15**;
`chat-composer` exactamente **6 de 25** valores, todos recuperación —
`[data-textarea]` opacity 0.4 → 1 y alpha efectiva 0.16 → 0.4,
`[data-textarea-input]` opacity 0.4 → 1 y alpha **0.064 → 0.4**, y el `cursor`
`default` → `auto` del provider del composer y del wrapper del textarea. Nada
extra. **925 de 25.568 px (3,6 %)** en el TEMA POR DEFECTO. Centinela
`file-upload` 61/98 y `chat-composer` **29 → 30/33**, los dos exit 0.
**Lo que enseñó**: (1) **una adjudicación puede ser falsa por no mirar al
huésped ENTERO.** El ledger daba `chat-composer.disabled-opacity` por
invisible «hasta que un tema mueva uno» — cierto para las dos reglas propias
del composer, que empataban a 0.4, y **falso para el textarea**, que no
declara atenuación y recibía el 0.4 dos veces más sobre el legítimo del
provider: la opacidad MULTIPLICA y 0.4³ = 0.064 se ve sin tocar el tema. La
entrada se RETIRA (`theming-sentinel-exceptions.ts`), y esta vez **el guard sí
la cazó** —era clave EXACTA, no patrón—, que es justo el reverso de la ley 1
de B′. (2) **La sonda compartida no tiene paso `disabled`** (rest / size /
open / hover / focus) y su filtro de nodos para `chat-composer` no incluye
`[data-textarea]`: era CIEGA a esta firma, y su cero no es prueba. La
recuperación se midió con instrumento propio; el cero de la sonda vale como
regresión, no como testigo. (3) **Un empate resuelto por orden de emisión no
es una decisión, es una que aún no se ha tomado** — ley hermana de B′, tercera
aplicación de la misma doctrina tras §12.9.
- 2026-08-24 — **`toast` 98 % → 100 %** (`b8eccc1c4`), contrato 85 → **85**
claves —ninguna acuñada—, centinela **51/85** con 34 adjudicadas. Llegaba con
`global`, `private` y `sistema` a CERO y **un** knob fuera de alcance: el
`opacity: 1` de la tarjeta, que se FIRMA como identidad — es el polo EN REPOSO
del par abierto/cerrado, y el que lleva tokens es el otro (`closed-offset`,
`closed-scale`). Tercer literal-identidad del día, tras `scroll-area` y
`slider`. **Lo que enseñó**: (1) **en una tarjeta PORTALADA el plano `overlay`
no se lleva sólo la tipografía — se lleva la TARJETA ENTERA, y esta vez con
coste de PRODUCTO**. El item sella `data-depth='overlay'` y el plano declara
`background`, el **ATAJO `border`** y `box-shadow` a la misma (0,1,0) y más
tarde: 21 claves (12 `{tono}-{surface,border}` + `provider-border-width` +
`accent-width` + `provider-shadow`) no mueven nada, mientras
`--depth-overlay-surface` / `-border` / `-shadow` y `--border-width` lo
repintan todo. **Un toast `risk` y uno `fulfill` visten la misma tarjeta
gris**, y la franja de acento —un `border-inline-start` que el atajo posterior
pisa en los cuatro lados— no se ve NUNCA en reposo. §12.9 sigue sin firmar, así
que se ADJUDICAN con `PLANE_SUPERSEDED` y la incidencia entra en
`next-features` §12; (2) **la misma clave puede mentir en reposo y decir la
verdad en animación** — los seis `{tono}-accent` alcanzan bajo `[data-loading]`,
donde la regla del pulso es (0,2,0) y su `@keyframes` gana a toda declaración
normal (100 fotogramas: oklab L 0.5032..0.8514 sin escribir, 0.0823..0.5032 con
el token en `rgb(1,2,3)`); (3) **poner `openMarker` EMPEORA la medición** —
primera vez: una tarjeta se auto-descarta a los 5 s, así que «¿ya está
abierta?» no tiene respuesta útil y tres corridas sobre código idéntico
discreparon en QUÉ token leía muerto. Sin marcador, el guard cae al de por
defecto (una parte que este componente no tiene) y dispara una tarjeta nueva
antes de CADA token: 51/85 tres veces seguidas. La sonda aprendió lo mismo
(`DEMO_VARIANTS`): medía UN nodo porque un toast no EXISTE hasta que algo lo
lanza. Diff de computed vacío (1.760 valores × 7 estados, 8 nodos), capturas 2×
byte a byte idénticas — por un script propio, porque su viewport es
`position: fixed` y cae fuera del recorte del escenario que usa `__shot`.
- 2026-08-24 — **`tabs` 96 % → 97 % CERRADO** (`300153f9f`), contrato 79 → **79**
claves —ninguna acuñada—, centinela **70/79** con 9 adjudicadas. Sus tres knobs
fuera de alcance se clasificaron uno a uno y los tres quedan fuera POR
DOCTRINA. **La propuesta §4.1 de su ficha es INCORRECTA y no se ejecuta**:
pedía acuñar `indicator-width` / `-height` sobre `var(--indicator-w, 0px)`, y
esas vars son el **canal de VALOR de soma** — la capa `MeasuredIndicator`
escribe el rect del trigger activo en cada movimiento, así que un público
encima sería un knob que un tema puede FIJAR, y fijarlo rompe el
deslizamiento. Es la adjudicación que `navigation-menu` ya lleva escrita para
su propio `indicator-w` (y la razón por la que su `-x` se retiró, muerto). El
tercero, el `width: 100%` de `fitted`, se FIRMA como identidad. **Lo que
enseñó**: (1) **la mitad de un contrato puede vivir en las VARIANTES** — el
escenario arranca en `line` y el guard leía 40/79; barriendo `data-variant` y
añadiendo las dos superficies de URL que el propio demo ofrece
(`perm.orientation=vertical`, `perm.disabled=true`) sube a 70/79 sin adjudicar
una clave de más; (2) **`trigger-bg` muere de una regla que existe para que se
lea la etiqueta** — la demo monta el `Indicator` y
`[data-tabs-list]:has([data-tabs-indicator]) [data-tabs-trigger]` (0,2,0) pone
el fondo transparente; sin indicador, que es la composición por DEFECTO,
alcanza; (3) `scrollable-fade` trae dos razones y cualquiera bastaría: vive
bajo un opt-in y pinta por `mask-image`, que el guard no lee. **Comprobado y
refutado**: su trigger NO compone el `Button` canónico, así que la clase «el
compuesto gana a la receta que lo compone» de `toolbar` / `form` no le aplica.
Diff de computed vacío (2.016 valores × 8 estados, 8 nodos), capturas 2×
idénticas.
- 2026-08-24 — **`slider` 97 % → 100 %** (`4d3ca2fd8`), contrato 39 → **39**
claves —ninguna acuñada—, centinela **32/39** con 7 adjudicadas. Llegaba con
`global` y `private` a CERO (sus cuatro privados son la escala por talla, que
ya deriva de públicos) y **un** knob fuera de alcance: el `inline-size: 100%`
del rail horizontal, que se FIRMA como identidad — un rail ocupa su
contenedor, y su suelo ya es un knob (`min-inline-size`). Mismo precedente que
`separator` el mismo día. **Lo que enseñó**: (1) **con un solo valor, el tick
ACTIVO no existe** — `data-active` se estampa sólo mientras el valor del tick
cae DENTRO de `[min(valores), max(valores)]` (`SliderTickProvider.isActive`),
así que un pulgar en 40 con ticks en 0/50/100 deja los tres inactivos y el
guard lee `active-tick-bg` como un token que miente; la marca activa **no es
un estado del tick, es una relación con el rango**, y con el chip `multiple`
(20–80) alcanza al instante; (2) **el pickup pide el puntero MANTENIDO** —
`active-thumb-scale` / `-shadow` viven en `:active` y el pase estático del
guard aparca el ratón: medidos con un `mouse.down()` real sobre el thumb real
(scale 1.15 → 4.56); (3) **una clave que el centinela lee muerta puede ser la
que otro componente ESCRIBE** — `min-block-size` sólo la lee la orientación
vertical, que el escenario no monta, y es justo la que `media-player` fija
para su volumen vertical. Este contrato lo COMPONEN siete componentes
(`media-player`, `skin-media-player`, `waveform` —su playhead ES este thumb—,
`color-picker`, `gradient-builder`, `image-adjustments`, `time-range-picker`),
así que ninguna clave se retira sin barrerlos. Diff de computed vacío (1.376
valores × 7 estados, 7 nodos), capturas 2× idénticas. ⚠ El único error de
`npm run check` en su fichero de demo es PREEXISTENTE (`1857c7854`).
- 2026-08-24 — **`scroll-area` 94 % → 100 %** (`3282ef978`), contrato 13 → **13**
claves —ninguna acuñada—, centinela **10/13** con 3 adjudicadas. Llegaba con
`global` y `private` a CERO y **un** knob fuera de alcance: el `opacity: 1` de
la barra visible, que se FIRMA como identidad — deshace el `0` del estado
oculto de la regla de encima, o sea el otro extremo de un par (y el `0`
hermano el censo ni lo cuenta, así que acuñarlo habría creado una asimetría).
**Lo que enseñó**: (1) **la clave que el guard leía muerta es la más
consumida del contrato** — `auto-max-block` sólo pinta bajo `[data-autosize]`,
que el escenario no monta, y la escriben `combobox`, `command`,
`gradient-picker`, `select` y `palabras` sobre su propio ScrollArea para
cerrar la altura del panel: retirarla habría roto cinco componentes; (2) **la
excepción de demo de §7.8 no la abría** porque `data-autosize` lo estampa el
ENVOLTORIO de eidos desde una prop y el morfo no lo declara — se adjudica
forzando el atributo sobre la raíz real (none → 1234px, y `none` al quitar el
token); (3) el par `transition-*` es la clase ya conocida — SON la transición
que el guard congela, medidos sin congelar en barra y thumb (0.12s → 11.5s).
`thumb-bg-hover` **se queda con el modificador detrás**: es hover neutro,
excluido del codemod de D-TH.6, y su migración a la capa de estado sigue
bloqueada por morfo (`archetype: 'thumb'` no recibe velo) — el último knob de
la firma 3 sin resolver. Diff de computed vacío (896 valores × 7 estados,
4 nodos), capturas 2× idénticas.
- 2026-08-24 — **`tags-input` 88 % → 90 % CERRADO** (`92180c390`), contrato 90 →
**91** claves, centinela **63/91** con 28 adjudicadas. Sus **cinco** knobs
fuera de alcance se clasificaron uno a uno ANTES de tocar nada: **uno** era la
COSTURA (`[data-invalid]` leía `var(--color-risk-border)` a pelo → entra como
`invalid-control-border`, con el nombre Y el default de
`field.invalid-control-border`, que tinta la misma parte del mismo chasis; NO
el `risk-border` de la paleta, que la cascada `[data-color]` re-enruta por
instancia y se llevaría con él el estado inválido). Los otros **cuatro son el
mismo puente THM-2** (`--_tags-input-palette-{border,track,text}` en el borde
del control enfocado y en los tres slots de la etiqueta activa): no se
acuñan, así que **90 % es su techo y es doctrina, no deuda**. Sus bloques
`[data-size]` del CSS **se quedan**: aquí NO los emite el TSC (claves de talla
planas, sin `scope: 'size:{k}'`), y retirarlos rompería el eje. **Lo que
enseñó**: (1) **su tinta de etiqueta no ha pintado NUNCA** — el morfo sella
`aria-selected='true'` como LITERAL en cada etiqueta, así que la regla de
`archetypes.css` (0,4,0) gana siempre a `[data-tags-input-item]` (0,1,0) y le
fija `color` y el velo de estado EN REPOSO; medido con `aria-selected=false`
sobre el nodo real, donde `item-fg` alcanza al instante. Tercera ocurrencia de
§12.5 y con cara de a11y (un lector anuncia todas las etiquetas como
seleccionadas) — registrado en next-features §12, no se toca desde este eje;
(2) **el guard se poisona a sí mismo abriendo**: clicar el control estampa
`data-focus` y la regla de foco re-tinta el borde a la misma (0,2,0) pero más
tarde, así que `control-border` —vivísimo— leía muerto (la clase de
`command.input-border`, otra vez). Barrer la AUSENCIA (`null`) de
`data-focus` / `data-invalid` / `data-disabled` lo devuelve: **60/91 → 63/91
sin adjudicar una clave de más**. Diff de computed vacío (2.496 valores × 8
estados en el escenario válido + 459 × 5 tallas × 3 variantes + hover en el
INVÁLIDO, con sonda de un solo uso), captura 2× byte a byte idéntica.
- 2026-08-24 — **`fab` 88 % → 100 %** (`5fd2fcdd8`), contrato 10 → **12** claves,
centinela **11/12** con 1 adjudicada. Dos knobs, y los dos del **pill
extendido**: el FAB circular no declara `padding-inline` ni `gap`, pero la
regla `[data-extended]` sí, y leía `var(--space-5)` / `var(--space-2)` a pelo
— la COSTURA de §2-A, con `global` a CERO. **El nombre lleva el cualificador
DETRÁS** (`padding-inline-extended`, `gap-extended`): `extended` es un MODO,
no un estado interactivo, así que cae en la otra mitad de la frase de D-TH.6,
como `sidebar.width-icon`; un `padding-inline` a secas habría prometido a TODO
FAB un padding que sólo tiene el pill. **Lo que enseñó**: (1) **la composición
no le quita nada, y valía la pena medirlo** — el mismo `<button>` lleva
`data-button` y `data-fab`, que es la forma del hallazgo de `gradient-picker`,
pero las reglas del FAB casan (0,3,0) contra los (0,2,0) del Button y ganan
sea cual sea el orden de carga (padding computado 20px = `--space-5`); (2)
**`lift` es la laguna del guard sobre `translate`** —hermana de la de
`transform` en background / rating-group / card— y trae una trampa nueva: **el
centinela se auto-cancela**, porque un lift de 1234px se lleva el nodo de
debajo del cursor (`:hover` cierto en t+50 ms, falso en t+300, `none` en
t+900), así que ni añadiéndole una pasada de hover se mediría; medido a mano
escribiendo el token ANTES de entrar, `translate: 0px -1234px`; (3) el
instrumento: un FAB es **UN nodo**, su segunda superficie es el hueco de icono
del Button compuesto (`EXTRA_NODES`), y su eje de talla es `data-fab-size`,
**no** `data-size` — con el barrido cartesiano `data-fab-size` ×
`data-extended` el guard pasa de **3/12 a 11/12** sin adjudicar nada de más.
Diff de computed vacío (416 valores × 7 estados en el circular + 188 × 4
tallas + hover en el pill), capturas 2× byte a byte idénticas en las dos
formas.
- 2026-08-24 — **`chat-typing` 89 % → 100 %** (`d39147c11`), contrato 7 → **8**
claves, centinela **8/8** con **cero adjudicadas**. Entra **una** clave y es
la COSTURA: `dot-radius` (`var(--radius-full)`), el único knob que leía el
sistema a pelo. El catálogo corrigió el nombre de la §4 generada —proponía
`indicator-radius`, que nombra por el primer selector de la regla
(`[data-chat-typing-indicator] > span`) cuando el nodo que PINTA es el punto y
el contrato ya habla `dot-size` / `dot-gap` / `dot-fg`: el defecto de
`tree-view` en pequeño. **Lo que enseñó**: (1) **el instrumento veía 3 nodos
de 8** por dos causas sumadas —los tres puntos son `> span` DESNUDOS del
indicador (la clase de la pastilla del separador de `chat-log`) y la demo
arranca con NADIE escribiendo, así que `data-state='idle'` esconde a todos los
hijos (presencia = visibilidad, N-7)—; con el selector extra y un typer
encendido por `prepareWith`, el centinela daba **7/7 antes de tocar el
código**; (2) **un componente cuyo loop de sistema anima `opacity` no puede
cerrar el diff en cero leyendo esa propiedad**: 18 diffs, los 18 de `opacity`
y los 18 en los tres `span[data-animation-style=pulse]`, **reproducidos
corriendo la sonda dos veces sobre el MISMO código** (control `after` vs
`after2`) — la sonda congela `transition`, nunca `animation`. Ninguna otra
propiedad se mueve, la clave tocada es `border-radius`, y las capturas 2×
antes/después son idénticas **byte a byte** (1.568 valores × 7 estados × 8
nodos).
- 2026-08-24 — **`chat-message` 84 % → 100 %** (`f882e1b9d`), el mayor de la
familia chat: contrato 75 → **81** claves, centinela **77/81** con 4
adjudicadas. Diff de computed **vacío** (4.192 valores × 8 estados × 16 nodos)
y capturas 2× idénticas **byte a byte**, en reposo y con la barra tapback
abierta. Entran seis, todas la COSTURA de los seis globales: `focus-radius`,
`mentioned-fg`, `reply-fg`, `reaction-fg`, `reaction-inner-gap` y
`quick-react-radius`. **Lo que enseñó**: (1) **el guard leía 45/75 y las
treinta muertas eran ejes que la demo no enciende** — una fila de chat es una
PILA de superficies opt-in (reply, read-by, entrega, mención) y de ejes de
PRESENTACIÓN (`run`, `direction`, `emphasized`) que la demo arranca en su
posición más pobre; con las cuatro superficies por `prepareWith` y los cinco
ejes barridos como PRODUCTO, **71/75 antes de acuñar nada** (12 → 16 nodos en
reposo, 24 con el panel abierto); (2) **un nombre que YA existe con otro
sentido no se reusa**: `reaction-gap` es el hueco ENTRE chips (se pinta en la
fila `reactions`) y el knob nuevo es el ritmo INTERNO del chip — mismo valor,
dos papeles, así que entra como `reaction-inner-gap`; el resto del bloque
`{parte}-gap` de esta receta significa «dentro de la parte», la anómala es la
vieja y renombrarla es churn con riesgo; (3) **tercera regla `> svg` muerta de
la familia**, retirada con 0 diffs: el `Icon` compuesto escribe
`style="width: var(--icon-size-sm)"` EN LÍNEA y el glifo mide 16 px, no los
15,4 px que `1.1em` prometía (la otra mitad del selector ni siquiera tiene
nodo: los chips llevan emoji de TEXTO); (4) **las cuatro adjudicaciones son
cuatro límites distintos del instrumento** — el guard fotografía UNA esquina
(`borderTopLeftRadius`) y la cola de la burbuja es la de ABAJO; congela la
transición que `emphasize-duration` ES; no hay `:focus-visible` porque la fila
sólo es tabulable compuesta DENTRO de un Feed (medida en la demo de
`chat-log`, donde sí lo es); y `transform` sigue fuera de su lista. **Y un
defecto real medido y NO corregido** (mueve píxel, D-TH.5): el chip de añadir
reacción lleva también `data-popover-trigger`, y
`[data-popover-trigger]:not([data-archetype='field-trigger'])` pesa (0,2,0)
contra los (0,1,0) de la receta, así que su cromo entero lo pinta
`popover.css` — se ve en la captura, los dos chips de reacción son píldoras
redondas y el de añadir un cuadrado gris. Clase `gradient-picker` /
`emoji-picker`; los `reaction-*` no mienten (alcanzan sobre los chips
normales), así que no hay nada que retirar: es una decisión de diseño.
- 2026-08-24 — **`separator` 67 % → 100 %** (`acf9c3a23`), contrato **3** claves
intacto (**cero acuñadas**), centinela **3/3** con **cero adjudicadas**. Su
alcance no subía por falta de tokens: subía **firmando lo que nunca fue un
knob**. Los dos que quedaban fuera eran `inline-size: 100%` en el horizontal y
`block-size: 100%` en el vertical — la IDENTIDAD del primitivo: una regla
ocupa su contenedor, y uno que no lo hiciera sería otro componente (su propio
README ya lo decía en sus Gaps, «Horizontal already fills 100%»). Llevan su
`/* literal: … */` y salen del ratio. Acuñar un `--separator-length` habría
sido inventar un eje que nadie pidió. **Lo que enseñó**: (1) **el techo puede
estar en el contrato que YA existe** — `thickness` (el mismo token alimenta
`block-size` en horizontal e `inline-size` en vertical), `fg` y `min-length`
son toda su apariencia, y el `border: 0` / `flex-shrink: 0` no son apariencia
sino puesta a cero y defensa contra el colapso en un padre flex; (2)
**`min-length` leía MUERTO y no mentía** — la demo arranca en el ejemplo
`stacked`, que monta sólo reglas horizontales, y media receta cuelga de
`[data-orientation='vertical']`; se arregla MIDIENDO, con un `sweepAttr` sobre
`data-orientation` que recorre las dos orientaciones **sobre los mismos
nodos**, no cambiando el chip de la demo al ejemplo `toolbar`, que **cambia
una orientación por la otra en vez de sumarlas** («montar MÁS puede medir
MENOS», otra vez). Diff de computed vacío (448 valores × 7 estados),
capturas 2× antes/después.
- 2026-08-24 — **`image-adjustments` 67 % → 100 %** (`e76d2db2b`), contrato
12 → **16** claves, centinela **15/16** con 1 adjudicada. Las cuatro que
entran son **LA COSTURA** (§2-A) y ninguna un diseño nuevo: `head-gap`
(`--space-2`) y `label-line-height` (`--leading-ui`), que la receta leía a
pelo; `hover-reset-fg` (`--color-content-primary`), que **no es un hover
inventado por componente** sino TINTA —el velo del sistema es
`background-image`, y un control sin superficie no puede recibirlo—; y
`focus-reset-radius` (`--radius-sm`), con el precedente exacto
`rating-group.focus-radius`. El catálogo corrigió DOS nombres de la §4
generada: `item-label-line-height` cuando el bloque ya habla `label-*` para
esa parte, y `reset-radius` cuando el radio sólo existe bajo `:focus-visible`.
Su único literal queda FIRMADO (`outline-color: Highlight` bajo
`@media (forced-colors: active)`: la paleta del SO manda ahí). **Lo que
enseñó**: (1) **medir de MÁS midió menos, y por un tope numérico** — el pase
de hover del guard recorre como mucho **30 nodos**, así que ampliar
`extraNodes` de la fila `hue` a las seis filas llevó el conjunto a 32 nodos y
convirtió al reset en el nº 32: `hover-reset-fg` leía muerto por una razón
ajena al token, y acotar el selector a la fila que la receta realmente
re-tinta lo revivió; (2) `hue-track` leía muerto (11/12) y estaba vivísimo —
pinta en el `::before` del `<Slider>` **compuesto**, cuyo nodo lleva
`data-slider*` y no `data-image-adjustments-*` (la clase de `waveform`); (3)
**el canal de VALOR de un panel de ajustes no toca el CSS** — su salida es la
cadena `filter` que soma entrega por `onFilterChange`, así que aquí no había
nada que confundir con un knob. Píxel hacia arriba sobre la regla `:hover`
tocada: el hover mueve `color` y sólo `color` en el propio `<button>`, sin
`background-image` en él ni en un solo ancestro. Diff de computed vacío
(5.440 valores × 7 estados × 26 nodos), capturas 2× antes/después.
- 2026-08-24 — **`toolbar` 91 % → 94 % CERRADO** (`26713f288`), contrato **54**
claves intacto (cero acuñadas), centinela **52/54** con 2 adjudicadas. **No
había nada que acuñar, y ése es el resultado**: el censo daba `global 0` —la
receta ya lee su contrato en cada declaración, con el eje `size` resuelto por
el TSC a través de siete privados que sólo reapuntan claves públicas por
talla— así que el trabajo fue CLASIFICAR lo que quedaba fuera. Sus dos
privados son **conmutadores por variante**, medidos uno a uno en el nodo real
(`--_toolbar-bg` = `var(--toolbar-bg)` en `surface`, `transparent` en
`outline` y `ghost`; `--_toolbar-shadow` igual con `none`): esa segunda rama
es la **identidad de la variante**, no un knob — una barra `ghost` ES la que
no tiene relleno ni sombra, y un público encima dejaría que un tema la
rellenara. Y su único literal (`inline-size: 100%` del separador horizontal,
el mismo trabajo que `align-self: stretch` en el gemelo vertical, medido 112
px sobre 126) queda FIRMADO y sale del ratio. **Lo que enseñó**: (1) **ceder
el cromo al componente compuesto sale GRATIS** — `Toolbar.Button` compone el
`<Button>` canónico y sus nodos llevan `data-button` Y `data-toolbar-button`,
con `[data-button]` casando a la misma especificidad (0,1,0) pero emitiéndose
DESPUÉS; la diferencia con `form`, que conserva 24 declaraciones que Button
anula, es que esta receta **no escribe ni una regla `[data-toolbar-button]`**
(A-112), así que la cascada del compuesto no le mata una sola clave —medido
ANTES de tocar nada: 51/54, y ninguna de las tres silenciosas era del botón;
(2) el interruptor que revive `disabled-opacity` hay que elegirlo MIRANDO la
regla de hover: encender «group disabled» no tapa nada porque el hover del
bar es `[data-toolbar-link]:hover, [data-toolbar-group-item]:hover:not([data-disabled])`
y el Link no pertenece al grupo — comprobado corriendo el guard antes y
después (51/54 → 52/54, el conjunto muerto encoge en uno y no gana ninguno).
Diff de computed vacío (2.464 valores × 7 estados × 11 nodos), capturas 2×
antes/después.
- 2026-08-24 — **`collapsible` 90 % → 100 %** (`1621de93c`), contrato 11 → **12**
claves, centinela **10/12** con 2 adjudicadas. Una sola clave, y es **la
COSTURA** (§2-A): el `gap` del encabezado leía `var(--space-2)` A PELO, así
que el valor era del sistema y el knob no era de nadie. El nombre lo decidió
el **catálogo**, no la propuesta de la ficha: `trigger-gap` ya existía ocho
veces en `base.ts`, cinco con exactamente ese paso. **Lo que enseñó**: (1) **el
componente que ANIMA su apertura no tiene un solo knob de motion** — su firma
es el par de eventos `emerge-expand` / `emerge-collapse` que materializa
`EidosConfig.motion.signatures`, y la receta no declara un `@keyframes`
(recipe-contract §2, R-4.5), de modo que lo único suyo es el par
`transition-*` que ya estaba en el contrato: el aviso «nunca `@keyframes` en
una receta» aquí no tenía nada que prohibir; (2) **una silenciosa se ARREGLA
antes que adjudicarse, pero eligiendo el interruptor** — encender el
`disabled` de la demo para toda la corrida se habría llevado el tinte de hover
por delante (`pointer-events: none` en el provider, «montar más mide menos»),
así que el estado entra como **superficie extra** por la URL
(`?perm.disabled=true`, precedente `radio-group`) en vez de como línea de
ledger. Las dos que sí se adjudican son `transition-duration` /
`transition-ease`, que SON la transición que el guard congela — medidas sin
congelar sobre el encabezado real (`0.18s ×2 → 4.321s ×2`). Diff de computed
vacío (736 valores × 8 estados × 3 nodos, las tres partes del morfo en cerrado
y en abierto), capturas 2× antes/después.
- 2026-08-23 — **`card` 78 % → 78 %** (`f3189a05a`), contrato 97 → **98** claves,
centinela **49/98** con 49 adjudicadas. **Cerrado en su número, y ése es el
resultado**: sus ocho privados son TODOS, transitivamente, el puente THM-2 —
cuatro leen `--_card-palette-*` directo y cuatro leen el conmutador por
VARIANTE (`--_card-bg` / `--_card-fg`), cuyas ramas son ese mismo puente. Un
público encima mataría el `color=` por instancia. Lo único que faltaba era
**la costura**: la rama `outline` del conmutador leía
`var(--color-surface-default)` a pelo → `--card-outline-bg`. **Lo que
enseñó**: (1) el trío `emerge-*` no pinta en la demo porque el montaje CEDE
al sistema de motion (`:not([data-animation-style])`) y el escenario arranca
con un preset — y `emerge-distance` tiene además una segunda causa, que
alimenta `--motion-distance-md` y la animación acaba en `translate 0` con
`fill-mode: both`, así que `transform` en reposo no puede enseñarlo nunca;
(2) **una receta que COMPONE gana a la compuesta**: dentro de un `CardGroup`
el eje `radius` de la card está muerto (`[data-card-group] [data-card]` es
(0,2,0) contra (0,1,0)) — medido, deliberado (anidación concéntrica §30) y
registrado. Diff de computed vacío (1.216 valores × 7 estados + 2.484 × 18
celdas), capturas 2× antes/después.
- 2026-08-23 — **`toggle` 81 % → 100 %** (`edc019515` + `007c2af52`), contrato 119 → **126**
claves, centinela **70/126** con 56 adjudicadas. Tres knobs y una firma: el
**radio** (`radius` + los cinco pasos de `rounded`) leía `--radius-default` y
la escala global A PELO —el agujero de `navigation-menu`, y el único de los
CINCO componentes con escala `rounded` que no tenía la suya (`button`,
`badge`, `image`, `color-swatch` la declaran)—; el **tinte inválido** iba a
`--color-threat-element` a pelo (la costura §2-A); y los tres literales
quedan FIRMADOS (identidad del `block`, técnica `sr-only`). El desacople de
`size` se conserva: son pasos de `rounded`, no de talla. **Lo que enseñó**:
el centinela empezó en 36/126 y llegó a 70 **sin adjudicar nada** — las
claves `{variante}-*` y `{variante}-on-*` son las mismas tres variantes en
dos ESTADOS, y forzar el estado con `prepareWith` cambiaba una mitad por la
otra (36 → 46). El guard aprende a barrer el **producto cartesiano** de
varios ejes, a QUITAR un atributo con `null` (sellar `data-invalid` toda la
corrida hacía que el borde inválido ganase a los otros seis) y a aplicar el
barrido también en el **pase de hover**, que no barría nada — que es
literalmente la razón escrita en el ledger de
`radio-group.hover-segmented-segment-fg`. Los 12 componentes con `sweepAttr`
re-verificados uno a uno: **cero regresiones, cero STALE** (y tres de ellos
dieron exit=1 falso corriendo doce seguidos: a solas, verdes). Diff de
computed vacío (416 valores × 7 estados + 1.150 × 25 celdas de barrido
dirigido), capturas 2× antes/después. **Anotado y FUERA por D-TH.5**: el
`:hover` tapa el borde inválido (mismo peso, declarado antes) — mueve píxel
en los cuatro controles de formulario.
- 2026-08-23 — **`rating-group` 80 % → 100 %** (`f6a618328`), contrato 25 → **29**
claves, centinela **23/29** con 6 adjudicadas. Tres RESUELTAS por talla
(`gap`, `item-size`, `font-size`) jubilan sus tres privados y los cuatro
bloques `[data-size]` que los conmutaban: eran «privado con fuentes por
TALLA», o sea una ESCALA, que sube al contrato. Más `invalid-item-fg` (la
costura: leía `--color-threat-element` a pelo) y los dos `100%` del glifo
FIRMADOS como identidad, que salen del ratio. Diff **0** sobre 5.624 valores.
Lo que enseñó: **el centinela no fotografía `transform`**, así que
`item-hover-offset` —el único knob de la receta que mueve uno— leía muerto
estando vivo (§13).
- 2026-08-23 — **`radio-group` 77 % → 81 %, y su TECHO es ése** (`f5d252443`),
contrato 61 → **69** claves, centinela **46/69** con 23 adjudicadas. Ocho
claves: tres RESUELTAS por talla (`item-size`, `dot-size`, `font-size`) que
retiran **diecisiete bloques `[data-size]`** y jubilan `--_radio-item-size`
(privado con el nombre ABREVIADO, clase `--_mp-*`/`--gp-*`), cuatro de
validación que iban a `--color-threat-*` a pelo (el trío del anillo con el
vocabulario del gemelo `checkbox`) y `segmented-segment-padding-block`. Diff
**0** sobre 25.263 valores en 47 estados. Los diez knobs que quedan son el
puente THM-2 de paleta: **el techo no es el 100 %**.
**`row-min-height-touch` se acuñó y hubo que RETIRARLO**: `--touch-target` no
lo emite ningún generador (constante ergonómica escrita a mano en
`archetypes.css`) y `recipe-css-contract` lo cazó como referencia FANTASMA en
el acto — recipe-contract §2 ya lo lista entre los sistemas transversales que
una receta consume y nunca acuña. **Y salieron TRES declaraciones que no
pintan** (§13, las tres mueven píxel ⇒ firma): la escala del punto no ha
pintado nunca (`<SvgDot>` escribe `width:1em` INLINE — 13,3281px en las cinco
tallas), `[data-radio-group-label][data-disabled]` no casa jamás (soma no
estampa el atributo en la parte `Label`), y el fondo de un radio marcado Y
deshabilitado pierde (0,3,0) contra la regla de variante (0,4,0).
- 2026-08-23 — **`chat-composer` 79 % → 100 %** (`71a097ddc`), contrato 31 → **33**
claves, centinela **29/33** con 4 adjudicadas. **La mitad de su contrato no
la montaba la demo**: 14 muertas de 31, y once eran la barra de contexto
(opt-in tras un chip) más la bandeja de adjuntos (opt-in tras un botón); con
las dos encendidas por `prepareWith`, 29/33 antes de tocar el código. Lo
acuñado son **DOS** claves donde el generador pedía cuatro:
`context-close-size` —el chip de descarte es un control CUADRADO, una clave
para los dos ejes como `send-size` en la misma receta; el ⚠ del generador
fundía la caja (26 px) con su glifo (`1em`)— y `context-close-radius`.
**CUATRO declaraciones retiradas por muertas**: los dos `> svg` (`1em` del
cierre, `1.1em` del envío) no pueden ganar al `style="width:
var(--icon-size-{k})"` que el `Icon` compuesto escribe INLINE — medido 14 px
contra 13,3 y 16 px contra 14,7; retirarlas da 0 diffs. Misma lección que
`chat-log` la misma tarde. **Y una colisión ENTRE RECETAS**:
`--chat-composer-disabled-opacity` no alcanza porque `file-upload` barre
DESCENDIENTES (`[data-file-upload] [data-disabled]`, (0,2,0), el mismo peso
que `[data-chat-composer-send][data-disabled]`) y su receta se emite MÁS
TARDE — y el composer vive dentro de un FileUpload en su composición
DOCUMENTADA. Fuera del envoltorio alcanza (0.4 → 0.123); los dos defaults son
`--opacity-disabled`, así que es invisible hasta que un tema mueva uno. No se
arregla aquí (sería cascadear): adjudicada. Las otras tres son hábitos del
guard (`focus-border` bajo `:focus-within`, y el par que ES la transición que
congela). **El diff no salió limpio del todo y la causa está medida**: 2-4
valores de `[data-chat-composer-attach]` `blockSize` flotando 30 ↔ 36 px, que
flota IGUAL corriendo dos veces el mismo código — una recarga de cada ocho, el
`Button size="sm" variant="ghost"` que la demo compone ahí computa 36 px y
fondo sólido primario con sus `data-size`/`data-variant` puestos: las reglas
de talla y variante de `button` llegan tarde en el dev server. Los 2.204
valores restantes, idénticos.
- 2026-08-24 — **`calendar` 84 % → 89 %** (`509d1144f`), contrato **74 claves sin
tocar**, centinela **45/74 → 68/74** con 6 adjudicadas. **CERO claves
acuñadas: el censo no tenía nada que coser** (`global` ya era 0 desde la capa
`calendar-surface`), así que lo único fuera de alcance eran cuatro literales
de IDENTIDAD (`max-content` ×3 + `100%`), ahora firmados con su razón. **Todo
el trabajo fue MEDIR**, y enseñó la ley del bloque: **una entrada de receta de
FAMILIA no se juzga en la ruta de UN componente** — 23 de sus 29 «muertos»
eran el cromo que `month-grid` y `range-calendar` pintan con sus propios
atributos, invisible al filtro `data-calendar`; y la propia demo pone
SELECTORES de mes/año donde iría el `heading`, así que tres claves más no
tenían nodo aquí. Con `urls` a las tres rutas de la familia + `extraNodes` +
barrido de `variant`, sin tocar a los tres consumidores (siguen a 0 % honesto).
**Hallazgo de píxel**: el tamaño de letra de los selectores de mes / año es NO
DETERMINISTA — `[data-calendar-month-select][data-button]` y
`[data-popover-trigger]:not([data-archetype='field-trigger'])` empatan a
(0,2,0) y gana el módulo que Vite inyecte el último (cuatro cargas:
14/16/14/14 px). Lo destapó la sonda dando **24 diffs corriendo dos veces el
MISMO código**; es la clase de `tree-grid` y va a §13. Sonda 22.656 valores ×
7 estados: **0 diffs fuera de esos dos nodos**; capturas 2× idénticas byte a
byte. Guards: censo `--only` 89 % (global 68 %, no baja) · audit PASS ·
eidos-lint 0 invalid · rtl 0 · docs 0.
- 2026-08-24 — **`select` 83 % → 92 %** (`cc010f79a`), contrato 101 → **103** claves,
centinela **69/103** con 34 adjudicadas. Dos claves de COSTURA:
**`invalid-border`** (las TRES declaraciones del estado inválido leían
`--color-risk-border` a pelo — y son UNA clave, no las dos que proponía la §4:
las filas se agrupan por VALOR) y **`group-gap`** (nombre contado en el
catálogo: `toolbar.group-gap`). Tres literales de identidad firmados. Lo que
queda son los 5 forwards de paleta THM-2: **CERRADO en 92 %**. Lo que enseñó,
todo del PORTAL: (1) el guard tiene que mantener el panel ABIERTO para medir
30 tokens y **el estado abierto tapa los de reposo** —`trigger-border` y
`trigger-indicator-fg` miden bien con el panel cerrado—; (2) tres knobs viven
en nodos SIN `data-select-*` (la `z` en el `[data-floating-wrapper]`, el ritmo
del viewport en el `ScrollArea` compuesto): con `extraNodes`, 65/101 → 69/103;
(3) la cascada de paleta anula sus 24 tonos en su forma MULTI-PARTE (trigger +
content, porque el panel se porta fuera); (4) **⚠ `separator-size` alcanza y
aun así no pinta**: `flex-shrink` 1 en un panel que desborda aplasta su 1px a
**0px** (con `flex-shrink: 0`, 1px → 9px) — defecto de píxel a §13. §12.9
respetado: nada de `font-family` / `line-height` para la superficie `overlay`.
Sonda 2.176 valores × 8 estados = **0 diffs**; capturas 2× (cerrado y abierto)
idénticas byte a byte. Guards: censo 92 % · audit PASS · eidos-lint 0 invalid ·
rtl 0 · docs 0.
- 2026-08-23 — **`chat-log` 77 % → 100 %** (`fd8f01bac`), 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).
- 2026-08-23 — **`editable` 75 % → 82 %** (`2117d7422`), 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.
- 2026-08-23 — **`avatar` 75 % → 90 %** (`f68bac4a6`), 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.
- 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.
- 2026-08-23 — **`file-upload` 76 % → 83 %** (`0c06d42aa`), 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).
- 2026-08-23 — **`switch` 76 % → 81 %** (`bbdc77c07`), 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.
- 2026-08-23 — **`search-field` 71 % → 100 %** (`f24abdb60`), contrato **17
claves sin tocar**, centinela **7/17 → 17/17** sin una sola adjudicación.
**CERO claves acuñadas: su 29 % de deuda eran CUATRO literales que son
IDENTIDAD**, ahora firmados con `/* literal: … */` (la válvula de
recipe-contract §3): el `line-height: 1` del icono —un glifo solo no tiene
interlineado, la misma anotación que su hermana `field.css`—, el `100%`
doble del `::before` —el anillo ES la caja del indicador, que ya mide por
`--_search-field-loading-size`— y el `border-radius: 50%`, que la §4 pedía
acuñar como `loading-indicator-radius`: **sería un knob que SÓLO puede
romper el componente**, porque el arco es un BORDE que gira y con cualquier
otro valor la ruleta deja de leerse como una — la clase 4 de F2-B, «el 50 %
de un círculo», al pie de la letra. Censo 15 knobs → 11 (público 10, literal
4 → 0, excepción firmada 0 → 4; el `--opacity-muted` del indicador bajo
`prefers-reduced-motion` es capa de sistema, fuera del ratio). **Lo que
enseñó**: (1) **diez de sus diecisiete tokens no se habían medido NUNCA** —
la demo no monta dos estados: el indicador de carga sólo existe con
`loading` y son **NUEVE** claves suyas (el commit y el comentario del guard
dicen «ocho»; `base.ts` le da nueve, y 7 del icono + 9 + 1 = 17), y la X
sólo se desvanece con el campo VACÍO, y el escenario arranca con valor
(`clear-hidden-opacity`); con los dos interruptores en `prepareWith` **EL
ORDEN IMPORTA**: el clic en la X va PRIMERO, porque la propia regla que
enciende le quita después el `pointer-events` al botón que la disparó; la
sonda necesita el de `loading` por lo mismo, 4 → **5 nodos**, sin perder
ninguno; (2) **los dos privados NO son deuda** — `--_search-field-icon-size`
y `--_search-field-loading-size` son el conmutador por talla y sus cinco
fuentes son, cada una, una clave pública: **58 de las 162 recetas** (cifra
del commit; el censo confirma las 162, pero ningún guard reproduce el 58)
resuelven así su eje `size` —incluidas `empty-state` y `banner`, cerradas
al 100 % este mes—, y pasarlas al ámbito `size:` del TSC cambiaría la
especificidad de la emisión sin ganar un punto de alcance. ⚠ **Un default
medido, ANOTADO, no cambiado**: las dos escalas del mismo renglón divergen
en los dos últimos peldaños — medido en Chrome con transiciones y animación
congeladas, forzando `data-size` sobre el nodo real, icono → indicador: xs
14→14, sm 16→16, md 18→18, **lg 20→18**, **xl 32→20**; el contrato lo
declara de frente (`loading-indicator-size-lg` apunta a
`--size-md-icon-size` y `-xl` a `--size-lg-icon-size`), así que en `xl` la
ruleta es DOCE píxeles menor que la lupa que tiene al lado. No es deuda de
alcance —las diez claves alcanzan—, es diseño que huele a copia a medias:
mueve píxel, D-TH.5 lo saca de este eje (anotado en el veredicto §5 y en el
README). Diff de computed vacío (1.216 valores · 8 estados · 5 nodos),
capturas 2× antes/después idénticas byte a byte. Guards: audit PASS ·
eidos-lint 0 invalid (9 morfo-backed, 4 eidos-only) · vitest
`src/uix/eidos` 35/36 (el rojo `skin-media-player`, preexistente) · rtl 0 ·
docs 0. README con su sección «Talla y tema» y pestaña Tokens en la demo
(17 claves).
- 2026-08-23 — **`image-picker` 71 % → 100 %** (`1a41312b6`), contrato 13 →
**14** claves, centinela **14/14** con 0 adjudicadas — de 13/13 a 14/14 SIN
una sola adjudicación. **Una sola clave: `hover-button-bg`**, el único knob
que iba a un primitivo global a pelo (`--color-surface-default`), y **no es
el velo del sistema, por eso se acuña**: los dos botones flotan SOBRE la
foto, su relleno de reposo es translúcido (`color-mix(… 88%, transparent)`)
y el hover lo vuelve OPACO — un cambio de RELLENO, no una capa encima.
Medido desde el píxel hacia arriba con las transiciones congeladas: el
cambio cae en el `<button>` (el nodo con forma, radio 6 px, `srgb .988/.88`
→ `oklch(.9911 0 0)`) y NINGÚN ancestro recibe `background-image`, porque el
arquetipo `action` no trae velo — `archetypes.css` sólo vela `trigger`,
`item` y `option`; no se bendice un duplicado, que es lo que paró el
`highlighted` de `listbox`. **El nombre lo decidió el catálogo**: la §4 lo
llamaba `hover-rotate-bg` por el PRIMER selector de la regla y miente dos
veces (pinta rotate **y** remove, y el contrato ya llama `button-*` a ese
par desde que nació); `hover-{parte}-{slot}` es la forma de las 77 claves
`hover-*` de `base.ts` (52 nombres distintos). Los **cuatro literales se
firman, no se acuñan** (válvula de recipe-contract §3): el `100%` doble del
hijo del canvas es identidad (la `<Image>` ES la caja del canvas, ya con
`inset: 0`), el `line-height: 1` es un botón de un solo glifo y el
`Highlight` de `forced-colors` es la paleta del SO, que el UA sustituiría.
Censo 20 knobs → 16 (público 12 → 13, global 1 → 0, literal 4 → 0,
excepción firmada 0 → 4; sistema 3, fuera del ratio). **Lo que enseñó**: el
instrumento medía **UN nodo** — el picker arranca VACÍO y todo lo que la
receta pinta (preview, canvas, toolbar y los dos botones de icono) sólo
existe en el estado `ready`, al que no llega ningún trigger: hay que cargar
un fichero. Con el chip «Load sample image» de la demo la sonda pasa de 1 a
**6 nodos** (`DEMO_VARIANTS` en la sonda, `prepareWith` en el centinela), y
éste necesita además `openMarker` porque su re-apertura por token volvería
a pulsar el chip y remontaría la vista previa a mitad de corrida. **Anotado
y FUERA por D-TH.5**: ese hover neutro es una invención por componente (§38
/ R-4.3 mandan la capa `--state-*`), pero migrarlo cambia el default y no
basta con tocar la receta — el arquetipo del par es `action`, que HOY no
recibe velo, así que o `action` gana velo en `archetypes.css` (mueve píxel
en todo el catálogo) o el par cambia de arquetipo, que es morfo. Diff de
computed vacío (1.344 valores en 7 estados × 6 nodos), capturas 2×
antes/después idénticas.
- 2026-08-23 — **`form` 71 % → 86 %** (`9c3f17fa6`), contrato 54 → **64**
claves, centinela **22/64** con 42 adjudicadas (19 nominales + 23 por un
patrón). Diez claves que iban a los primitivos A PELO: el resumen de errores
(`error-summary-{bg,fg,border}` — es el señalizador de nivel de página, y su
superficie, su tinta y su borde SON el tono risk), el acento de formulario
inválido (`invalid-accent`, un privado con UNA sola fuente, no un
conmutador), las tres superficies del árbol de AutoFields, las dos del
widget y el subrayado a media tinta de los enlaces del resumen
(`link-underline-fg`, el par del `-offset` que ya existía); más un literal
de identidad firmado. **R-5.3 cazó el nombre en el acto**: lo acuñé
`link-underline-color` y la tinta es `fg` (theming §6.7 r7, firmado el 20),
renombrado antes de commitear — el guard de nombres funciona; es el
vocabulario DIMENSIONAL el que no cubre (ver `badge`, el mismo día). **Lo
que enseñó**: ⚠⚠ **23 claves del contrato NO PINTAN, y no es deuda de valor:
es una cascada.** `[data-form-submit]`, `[data-form-reset]` y los dos
botones del array llevan también `data-button` porque COMPONEN el `Button`
canónico, y `[data-button]` casa con la MISMA especificidad (0,1,0) pero
carga DESPUÉS: gana `button.css`. Medido propiedad a propiedad sobre el nodo
real —altura, `padding-inline`, tamaño y peso de letra, interlineado, hueco,
radio, grosor de borde, el par de transición y la opacidad deshabilitada—:
**ninguna se mueve**. Es el defecto que `gradient-picker` documentó (36 de
sus 45 claves) y que `emoji-picker` encontró hoy en su trigger; allí la
respuesta fue RETIRAR las declaraciones muertas, y aquí son 23 claves
PÚBLICAS: decisión del autor, no del que ejecuta. Quedan adjudicadas POR
PATRÓN (`^(action-|transition-|disabled-opacity$)`, una razón arquitectónica
escrita una vez) y anotadas en §13 con la pregunta de fondo, que es la de
§12.9 con el plano overlay: qué manda, el componente compuesto o la receta
que lo compone — si mandan las recetas se sube la especificidad y no se
retira nada; si mandan los compuestos, hay ~62 claves públicas que retirar
en tres componentes. ⚠ el commit, su título y la nota de §13 dicen **24** y
«las 20 + el patrón»: el guard adjudica 19 nominales + 23 por patrón = 42,
que es lo que exige el 22/64 — off-by-one del autor, a corregir en
`next-features.md`. Las demás adjudicaciones son ramas que la demo no monta:
el layout `grid` (forzado alcanza — y su `grid-template-columns` sólo CAMBIA
si el valor altera el número de columnas: con 90px pasa de una columna de
502px a cuatro de 110,5), el resumen de errores VACÍO (sin `<ul>` que
indentar ni `<a>` que subrayar) y toda la rama de ARRAY de AutoFields — el
modo `auto` de la demo monta `group` y `field`, nunca `array`, `item` ni
`widget`. Fuera por doctrina: los tres privados del panel son un CONMUTADOR
(`plain` contra `panel`) y los cuatro `--_form-action-*` leen el forward de
paleta THM-2. Diff de computed vacío (832 valores · 7 estados), capturas 2×
antes/después. Guards: censo `--only` 86 % · audit PASS · `--names` 0
desviadas · eidos-lint 36 morfo-backed / 0 invalid / 0 class-hooks · rtl 0 ·
docs 0 · suite eidos 439/440 (el rojo conocido).
- 2026-08-23 — **`skip-link` 71 % → 100 %** (`ebe3fe6ed`), contrato **8 claves
sin tocar**, centinela **0/8 → 8/8** con **cero adjudicaciones**. **Ninguna
clave nueva**: su 29 % de deuda eran DOS literales que son la TÉCNICA:
`inline-size: 1px` / `block-size: 1px`, la mitad del sr-only canónico —la
otra mitad es el par `clip` / `clip-path`—, ahora firmados con
`/* literal: */`, la válvula de recipe-contract §3: salen del ratio como
ausencia ESCRITA, no como deuda, y el censo baja de 8 knobs a 6. No se
acuñan porque un tema que los mueva no cambia una apariencia, ROMPE el
mecanismo — y a `0 × 0` algunos motores sacan el enlace del árbol de
accesibilidad, que es lo único que este componente no puede permitirse;
misma clase que `text-blur` y `text-scramble`. Los otros 5 knobs ya pasaban
por token público, y las dos ausencias que quedan son doctrina aplicada: la
tipografía se HEREDA (no hay `font-*` declarado; acuñarla fijaría el
default, D-TH.5) y el anillo de foco es del SISTEMA (`--focus-ring-*`,
recipe-contract §2). El `background:` en shorthand no mata ninguna capa de
estado: su parte lleva `archetype: 'provider'`, que no recibe velo. **Lo que
enseñó**: la OCTAVA clase de punto ciego del instrumento — toda su
superficie existe SÓLO bajo `:focus` (sr-only mientras no lo tiene, píldora
cuando sí), así que el guard leía 0 de 8: no lo enfocaba nunca, el `blur`
que hace tras abrir lo habría deshecho igual, y clicarlo tampoco vale porque
su handler manda el foco a la región de destino y la píldora se va por el
camino de entrada. Nace `openBy: 'focus'`, y con él `openingIsFragile` — el
conjunto de aperturas que el blur y el aparcado del puntero NO deben
deshacer, hasta hoy sólo `hover`. Es aditivo: las cuatro condiciones que
decían `openBy !== 'hover'` pasan a consultarlo y ninguna configuración
existente cambia de rama. Re-verificados tres del ledger: `dialog` 37/44 (la
cifra exacta de su commit), `tooltip` 15/23, `context-menu` 27/28 — cero
regresiones. Diff de computed vacío (192 valores en 7 estados, más los 52
del estado ENFOCADO —el único que se ve— medidos aparte con un script
propio: la sonda estándar mide reposo, tallas, abierto y hover, nunca foco),
capturas 2× antes/después idénticas byte a byte.
- 2026-08-23 — **`dialog` 67 % → 89 %** (`bb89174a8`), contrato 37 → **44**
claves, centinela **37/44** con 7 adjudicadas. Las siete son las dos tintas
SEMÁNTICAS que le faltaban. Cuatro para los tonos EVALUATIVOS
(`risk-border`, `risk-title-fg`, `threat-border`, `threat-title-fg`): dialog
no tiene forward de paleta THM-2 y su trato de tono es deliberadamente
estrecho —el borde y la tinta del TÍTULO, nunca la superficie del cuerpo,
para que el texto largo no compita con la señal—, pero iban a
`--color-risk-*` a pelo. Y tres para el tinte de SALIDA (`saved-border`,
`failed-border`, `dismissed-border`): el morfo escribe `data-last-action`
antes de que `data-state` pase a `closed`, así que el borde superior dice
CÓMO se cerró mientras se va; el tercero cubre `cancelled` / `dismissed` /
`dismissed-outside`, que comparten valor — todo cierre sin resultado.
`{tono}-border` es el nombre del catálogo (48 claves) y `failed-border` el
de `proof-of-human`. **El 11 % que queda son los cuatro
`--_dialog-content-*-override`, y no es deuda**: escotillas POR INSTANCIA
que el wrapper escribe INLINE desde las props `width` / `minWidth` /
`maxWidth` / `height` / `minHeight` — un público encima lo pisaría el inline
y mentiría, la clase de `--_background-gradient-image` y del triple de
`s-text-virtual-list`. Más el `width: 100%` de la hoja inferior, firmado
como identidad. ⚠ **Nada de tipografía de superficie, por §12.9**: el
contenido lleva `data-depth='modal'` y un plano declara `font-family` /
`line-height` a la misma especificidad y más tarde en la cascada; las claves
de título y descripción que ya existían son de PARTES internas, no de la
superficie del plano. **Lo que enseñó**: ⚠⚠ **`overlay-opacity` es un FALSO
POSITIVO del guard** — `dialog-overlay.svelte` escribe
`--dialog-overlay-opacity` en el `style` INLINE del velo y el guard escribe
su centinela en el `style` de cada nodo, así que se pisa a sí mismo y ve un
cambio. Medido como lo haría un TEMA (sólo desde `:root`, animaciones
congeladas) el fondo del velo **no se mueve**
(`color(srgb 0.1098 0.098 0.0902 / 0.2518)` antes y después): la clave está
en el contrato y ningún tema la alcanza. Cara opuesta de
`drag-drop.preview-z`, misma familia que el velo de `drawer` que no pinta; a
§13 con el arreglo del guard, y retirar o rescatar la clave es decisión
aparte, porque cambia quién controla la opacidad del velo. Y dos de las
siete adjudicaciones costaron una segunda pasada **por el VALOR del
atributo**: la rejilla de posición usa `top-left` / `top-right` (físicos),
no `top-start`, y el botón de cierre NO lleva `data-position` en esta demo
aunque todas las reglas que leen `close-inset` lo exigen. Diff de computed
vacío (480 valores · 8 estados · 8 nodos con el diálogo abierto), capturas
2× antes/después. Guards: censo `--only` 89 % · audit PASS · eidos-lint 15
morfo-backed / 0 invalid / 0 class-hooks · rtl 0 · docs 0 · suite eidos
439/440 (el rojo conocido).
- 2026-08-23 — **`pin-input` 65 % → 100 %** (`4b9354bba`), contrato 20 → **22**
claves, centinela **16/22** con seis adjudicadas. Su 35 % de deuda eran TRES
defectos, ninguno de diseño: un prefijo ABREVIADO —los privados se llamaban
`--_pin-cell-*` dentro de un componente llamado `pin-input`, así que el
censo no los reconocía como suyos y los cuatro knobs que los leen contaban
como deuda aunque DERIVAN de públicos; cuarto caso de la clase tras
`color-picker`, `metrics` y `time-picker`—, un respaldo A SÍ MISMO
(`font-family` leía `var(--style-label-font-family,
var(--style-label-font-family))`, un no-op con forma de defecto) y la tinta
del dígito a `--color-content-primary` a pelo. Las dos claves nuevas son
esas dos costuras (§2-A): `font-family` y `cell-fg`. **Lo que enseñó**: los
**veintidós respaldos escritos a mano** duplicaban lo que el contrato ya
declara en `:root` —código muerto, y retirarlos dio 0 diffs, que es la
prueba— pero UNO MENTÍA: `disabled-opacity` respaldaba `0.55` cuando
`--opacity-disabled` vale **0.4**. Es la ley de los `, 1.4` contra un
`--font-line-height-sm` de 1.45 que este mes ya salió en dos componentes,
aquí con más deriva: **un respaldo no es una red de seguridad, es una
segunda fuente que nadie actualiza**. Y las seis adjudicaciones enseñaron
DÓNDE vive cada estado: `filled` y `active` son atributos de la CELDA, pero
`invalid` y `readonly` se estampan en el CONTENEDOR y cascadean (soma los
OR-fusiona con el Field que las envuelve), así que forzarlos en la celda no
hace nada. Diff de computed vacío (1.856 valores · 8 estados · 8 nodos),
capturas 2× antes/después.
- 2026-08-23 — **`spin-field` ADJUDICADO: 67 % es su techo** (`2ebeb9b16`),
64 % → 67 % por UN literal firmado, contrato 20 → **20** claves, centinela
**13/20** con siete adjudicadas. **Cero claves nuevas: el techo estaba
escrito en la cabecera de su propio CSS.** Ocho de sus knobs leen los
privados RESUELTOS del Field (`--_field-control-height`,
`--_field-control-padding-inline`, `--_field-control-gap`) porque los
steppers y el scrubber IGUALAN la fila del input a cualquier talla —«never a
parallel `--spin-field-*` chrome that would alias the Field base», dice la
cabecera—, así que acuñar ahí sería el antipatrón que el fichero prohíbe por
su nombre ([S6] no-token-aliases). Préstamo con dueño: se tema en la ficha
de `field`, y el censo lo puntúa como deuda porque no sabe distinguirlo — el
mismo artefacto que `listbox` (68 %) y `radio-cards` (61 %) ya registraron.
Los dos factores del apilado (`* 0.62` el ancho del botón, `* 0.34` el
glifo) viven dentro de un `calc()` sobre ese mismo privado de Field, así que
un token propio no cambiaría quién manda; y el `line-height: 1` de la celda
de glifo es identidad, FIRMADO — lo único que mueve la cifra (16/25 → 16/24
knobs en ratio). **Lo que enseñó**: (1) los tres `active-control-*` no
responden a `:active` NI forzando el pseudo por CDP — su estado es el
`[data-scrubbing]` que soma pone en el SCRUBBER mientras se arrastra, y
forzado el atributo sobre el nodo real los tres alcanzan; (2) es una CAPA,
no un componente: sin wrapper ni ruta propia, el guard lo mide dentro de
`number-field`, declarado ahora en `urls` para que nadie tenga que saberlo;
(3) una corrida de sonda dio 5 «nodos ausentes» que no eran nada —una
recarga que no montó la talla `xl`—: repetida, 0 diffs y 0 ausentes contra
el mismo ANTES, y un «missing node» tampoco se cree a la primera. Las otras
cuatro adjudicadas: `focus-control-bg` (pseudo forzado por CDP),
`control-disabled-opacity` (0.48 → 0.137) y el par de transición medido sin
congelar (0,12s → 11,5s). Diff de computed vacío (2.400 valores · 8 estados
· 5 nodos), `component:audit` sin hallazgos, `docs:check` 0, el ledger en
sus 63 hunks previos, captura del anfitrión `number-field`.
- 2026-08-23 — **`emoji-picker` 61 % → 100 %** (`863c6993c`), contrato 17 →
**21** claves, centinela **17/21** con 5 adjudicadas. Siete acuñadas, **tres
retiradas por muertas** y tres literales de identidad firmados. Los nombres
los dio el catálogo: `open-trigger-fg` (`accordion` lo nombra igual),
`grid-padding-inline` + `grid-padding-block-end` (el eje lógico completo,
como `card-group` / `combobox` / `command`) y las tres del marcador de tono,
la familia `timeline.marker-{size-*,radius,bg}` — se llama `tone-marker-*` y
no `marker-*` porque el pie lleva además un selector de TONO, y `marker` a
secas sería ambiguo dentro del propio componente. Las cinco adjudicaciones,
todas forzadas sobre nodos reales: las tres del marcador (la demo monta CERO
celdas `data-tone-capable`; con el atributo puesto el `::after` alcanza —
4px → 37px, 9999px → 37px, y el fondo), `preview-name-fg` (los hijos de la
vista previa son `<span>` desnudos, sin atributo del componente, fuera del
conjunto medido) y `open-trigger-fg`. **Lo que enseñó**: (1) **el cromo de
reposo del trigger NO es suyo** — el nodo lleva también
`data-popover-trigger`, y
`[data-popover-trigger]:not([data-archetype='field-trigger'])` es
**(0,2,0)** contra el (0,1,0) de esta receta: el radio, la tinta y el tamaño
de letra los pinta `popover.css`. Retiradas las tres declaraciones, **0
diffs**, la prueba de que estaban muertas (`trigger-fg` no llegó a existir:
se acuñó y se midió muerta en el mismo pase). Es el defecto que
`gradient-picker` documentó (36 de sus 45 tokens) y la misma respuesta;
sobreviven dos, `trigger-size` porque popover dimensiona con `height` y no
con el par lógico, y `open-trigger-fg` porque su selector añade
`[data-state='open']` y gana por orden. (2) ⚠ **`open-trigger-fg` OSCILA
entre corridas** — 17/21, 16/21, 16/21 con el mismo código, medido vivo
CUATRO veces (la última replicando paso a paso el `staticPass` del guard):
el panel se cierra en algún punto de la pasada y, durante la animación de
SALIDA, el content sigue en el DOM mientras el trigger ya perdió
`data-state='open'`, que es lo que la regla selecciona, así que `reopen()`
mira el CONTENT, cree que está abierto y no reabre. Un `openMarker` sobre
el trigger NO basta (el clic de reapertura cae en esa misma ventana) y se
retiró; el arreglo real es que `reopen()` espere a la CONDICIÓN en vez de
dormir un número fijo — §13. Su entrada de ledger saldrá **STALE** en las
corridas donde alcanza, y está escrito en la razón para que nadie la borre
por eso. (3) **el instrumento de captura estaba roto desde siempre**:
`__shot.ts` tenía el puerto 5173 CABLEADO, así que cualquier sesión en otro
puerto moría con `ERR_EMPTY_RESPONSE` — probablemente por lo que los pasos
6 y 13 del protocolo se saltaron en 33 componentes sin que nadie lo notara.
Ahora toma la URL como argumento, con un `--open` para los componentes cuya
superficie sólo existe abierta. Diff de computed vacío (6.080 valores · 8
estados · 176 nodos con el panel abierto), capturas 2× antes/después,
estrenando el instrumento arreglado.
- 2026-08-23 — **`progress` 69 % → 87 %** (`2e708adae`), 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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`.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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).
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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».
- 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.
- 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.
- 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.
- 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.
- 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-subtle`, 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`).
- 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).
- 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).
- 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.
**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.
- 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 %**.
- 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
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.
Detalle en [`CONTINUE-theming.md`](./CONTINUE-theming.md) §«Registro».
- 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.
- 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.
- 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.
---
## 9. Tabla completa por componente (2026-08-19 — regenerar con el script, no editar)
| 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.