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

98 KiB

PLAN — Theming: que TODO componente sea personalizable por un tema (eje theme-reach)

Estado (act. 2026-08-21): EN EJECUCIÓN — alcance global 43 %, 15 componentes con contrato, 49 sin él. Vocabulario CERRADO (codemod + R-5.3 + muro de tipos) y bloque F2-A CERRADO. Las D-TH.1…8 de §4 FIRMADAS TODAS el 2026-08-20 (más las 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).

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

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.

8. Registro

  • 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-secondary, 9× content-primary, 9× 100%, 6× font-size-sm, 6× font-size-xs, 6× font-weight-medium, 5× surface-default). La inflación de spinner con otra forma: nombrar por parte lo que es un concepto.

    El texto no recibe tokens de chart: toma su ROL DE TAMAÑO de los estilos con nombre (caption para anotaciones, label para el cromo) y su tinta y peso de las ranuras del sistema. Acuñarlos sería tipografía por componente, lo que el autor prohibió el mismo día. Precedente de consumir la capa: code-block, kbd, announce. La receta sólo posee la tinta de lo que no es texto — axis-fg, grid-fg, separator-fg (el pelo de superficie que separa marcas contiguas: cinco partes, una idea), smith-boundary-fg, track-bg — más 17 de geometría de cromo. Identidad fuera de contrato: los nueve 100% y el max-content del tooltip.

    Incumplimiento heredado corregido: el CSS leía la coordenada CRUDA var(--control-height-sm, var(--space-7)) (respaldo muerto); el contrato guarda el bundle var(--size-sm-control-height), 1:1.

    El instrumento estaba ciego de TRES maneras, arregladas antes de medir:

    1. La superficie de chart vive en 16 rutas de demo, una por tipo: /chart monta 58 nodos de los 302 de la página y el resto vive en rutas hermanas. El guard gana urls: un token está VIVO si ALGUNA superficie del componente lo sigue, y cada URL sólo re-prueba lo que sigue pendiente. De paso picker-shell recibe la suya (su ruta propia es un 404, se mide dentro del date-picker: 0/31 → 6/31 sin pasar la URL a mano).
    2. El guard no leía NINGUNA propiedad SVG — sin fill/stroke/ strokeWidth/fillOpacity/r/rx, doce claves de un componente que dibuja en SVG leían muertas estando vivas. Misma clase que el hueco de filter del día anterior.
    3. El valor centinela se elegía por SUBCADENA: slice-stroke-width recibía rgb(1, 2, 3) por contener «stroke» — inválido para una longitud, así que leía muerta. Ahora la dimensión se decide por el final del nombre.

    Gates: sonda 0 diffs en 161.850 valores (16 rutas × 7 estados, 892 nodos en reposo) · centinela 37/39 con dos adjudicaciones medidas sin la congelación (transition-duration 0.12s → 11.5s, -ease → steps(4)) · censo --only 49 % y global 56 % → 57 % (2.638 → 2.675 públicos, <20 % 19 → 18) · --names 39/39 conformes · audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl 0 · docs 0 · check sin errores en los ficheros tocados. Los 28 componentes con ledger re-verificados tras los tres cambios de instrumento: cero regresiones, cero STALE.

    Abre (→ §13): el censo penaliza consumir la capa tipográfica si no eres un primitivo (las doce declaraciones correctas siguen contando global) — la nota de listbox con una segunda ocurrencia; y el número del gauge no tiene estilo con nombre que lo represente (un readout de dato no es un h2).

  • 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.