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

239 KiB

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. 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 §1bis, §3, §5, §6, §7, §12, §16 · docs/canon/tsc.md · docs/canon/recipe-contract.md §1–§5 · docs/theming/motion.md §"When both write animation on ONE node" · docs/guides/component-guide.md §Build contract · src/uix/eidos/components/README.md · el precedente ejecutado y firmado: 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 §«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»)

  1. node --import tsx/esm scripts/theming-census.ts --only {c} → guardar la línea.
  2. 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.
  3. 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)

  1. 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.
  2. 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.
  3. 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)

  1. 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).
  2. 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.
  3. 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").
  4. 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)

  1. node --import tsx/esm scripts/theming-census.ts --only {c} → 100 % o excepción escrita. Y el global NO baja.
  2. 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).
  3. node --import tsx/esm scripts/eidos-lint.ts {c} → 0 invalid.
  4. npx vitest run src/uix/eidos → sin rojos nuevos (comparar por fichero de test, no por cifra: el rojo conocido es skin-media-player).
  5. 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).
  6. 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)

  1. 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.
  2. 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

  1. 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: el sobre de trigger de popover es el SUELO, no el techo (opción b del expediente V4′; doctrina en theming/changelog.md §55). Tercera aplicación de la misma ley —§12.9 el plano de profundidad, §53 la escalera de paleta— y la primera que cae en una receta ESCRITA A MANO en vez del CSS generado. Qué cambia: popover.css se autodescribe como «a baseline button envelope» y emitía ese sobre a (0,2,0), con el :hover a (0,5,0). Un baseline a (0,2,0) le gana a toda receta de huésped a (0,1,0) — eso no es un baseline, es un techo. Las dos reglas pasan a :where([data-popover-trigger]:not([data-archetype='field-trigger'])) y su gemela de :hover. El :not() se queda DENTRO del :where(): anula la contribución de especificidad, nunca el emparejamiento, así que los cinco triggers de picker siguen excluidos igual, ahora a (0,0,0); sacarlo fuera dejaría la regla a (0,1,0), un empate nuevo con toda receta de huésped. Sonda §7.2/§7.4 (76 instancias de trigger en 16 rutas × reposo · hover · :focus-visible · abierto, transiciones y animaciones congeladas, más 167 nodos de referencia medidos en paralelo): 69 instancias no mueven un solo valor de RECETA — los 64 botones desnudos del sitio de docs, gradient-picker, el data-perm-step de la demo y los tres field-trigger excluidos (estos tres, con diff literalmente vacío). De esas 69, 66 recuperan dos valores del SISTEMA que el (0,2,0)/(0,5,0) les tapaba: el velo de hover de archetypes.css y su transition. Es la firma trabajando —«la capa de estado del sistema vuelve a componer en hover»—, no daño colateral, y la sonda de V4′ no podía verlo porque no medía hover ni background-image. 7 identidades mueven, y las 7 hacia lo que su propia receta declara, verificado además por atribución CDP (getMatchedStylesForNode) propiedad a propiedad: chat-message chip → píldora (36→26px, radio 6→9999px, fondo 0.9821→0.9911, padding 12→8, letra 14→12, ancho 42→34), chronos +N more → tinta primaria y sin caja gris al hover (fondo 0.931→transparente, borde 0.7921→transparente), calendar mes y año → ghost (fondo y borde transparentes, tinta primaria, padding 12→16), emoji-picker → su icon-button (fondo→transparente, borde 1→0, padding 12→0), palabras → radio 6→4, tinta content-subtle, padding 12→8, letra 14→12, natural-time-picker → fondo 0.9821→0.9911 y letra 14→16. calendar deja de ser una moneda al aire. El empate era [data-calendar-month-select][data-button] (0,2,0) contra el sobre (0,2,0), las dos declarando font-size; reproducido antes por CDP: 2 de 6 cargas limpias cayeron en 14px y 4 en 16px. Después: 8 cargas en dos órdenes de carga distintos (directa y navegando desde /popover) → el mismo píxel siempre. Es la deuda que next-features.md:1847 declaraba envenenando el instrumento de medida de todo este eje. Centinela: natural-time-picker 44/62 → 48/62 — reviven trigger-padding-inline, trigger-fg, trigger-radius y trigger-font-size, que su README ya registraba como inertes. chat-message 77/81, calendar 68/74, emoji-picker 17/21, gradient-picker 9/25, popover 35/46 y chronos 6/9 sin cambio (comparados antes/después reconstruyendo la cascada vieja en el fichero, nunca por git stash). Ninguna entrada del ledger salió STALE, y ninguna estaba adjudicada por este sobre: los componentes que V4′/V1 citaban habían RETIRADO sus claves en vez de adjudicarlas. Los 3 muertos sin adjudicar de chronos (num-row, timegrid-block-size, disabled-opacity) son previos y ajenos —medidos idénticos con la cascada vieja—, deuda de otro eje. Cierra la trampa latente de chat-message.css:462: el bloque «los chips dentro de la píldora sueltan su cromo» está a (0,2,0) y empataba con el sobre. Medido montando el nodo que ninguna superficie del repo monta: con el sobre viejo reinyectado a (0,2,0) el bloque PIERDE (borde 1px, fondo 0.9821, 36px); con el suelo GANA (borde 0, fondo transparente, la píldora de 26px). La misma decisión resuelve las dos, sin tocar chat-message.css. Guarda por MUTACIÓN en active-eidos-config.test.ts (§55): 6 mutaciones, 6 mordidas — reposo desenvuelto · hover desenvuelto · :not() izado fuera del :where() · suelo vaciado (anti-vacío) · una QUINTA regla de trigger fuera del suelo (cierre estructural) · y las dos reglas de estado bajadas en silencio (la sexta se añadió PORQUE la primera pasada la dejó pasar verde). Censo de la propia guarda: exactamente 4 reglas de trigger en la hoja. Lo que NO se tocó, y por qué: [data-state='open'] (0,3,0) y :focus-visible (0,3,0) no estaban en la firma — coste de dejarlos, medido: el centinela de natural-time-picker se quedaría en 50/62 si bajaran. El pin (0,3,0) de chronos.css:417 sigue en pie (ahora semi-redundante: le gana al velo del sistema). Los dos, en next-features.md §13. Registrado además: palabras dependía del sobre sin figurar en ningún sitio —y no puede figurar en el censo, porque no tiene bloque de receta (theming:sentinel -- palabras muere con «no recipe block»)—, así que la dependencia queda escrita como comentario en palabras-chrome.css, junto a la regla, con los tres ejes que sigue heredando (height, border, background).

  • 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). ⚠ Anotado 2026-08-26 (firma §14): la adjudicación era correcta y el NÚMERO se quedó corto. Aquellas vars se llamaban --indicator-* sin prefijo de componente —públicas de forma, de nadie de hecho—, así que el censo sólo podía puntuarlas global y la ley del espacio cerrado no las veía. Movidas al namespace del anfitrión (--_tabs-indicator-*) leen como el canal que son: tabs 97 % → 100 %, sin acuñar una sola clave. 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 §«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 §«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 §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.