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

63 KiB

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

Estado (act. 2026-08-20): EN EJECUCIÓN — 7 componentes corregidos, 20 fichas revisadas; las D-TH de §4 SIGUEN SIN FIRMAR. Lo ejecutado es lo que NO depende de ellas; el registro con cifras y commits está en §8, y el punto de entrada diario es CONTINUE-theming.md. Las decisiones de §4 se presentan 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).

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. SIN FIRMAR
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í. SIN FIRMAR
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). SIN FIRMAR
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. SIN FIRMAR
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). SIN FIRMAR
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). SIN FIRMAR
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. SIN FIRMAR
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. SIN FIRMAR

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) un test scripts/theming-census.test.ts o bajo src/uix/eidos/ que fije 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.

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 — por cada token NUEVO del componente, poner en el root del componente un valor imposible de confundir (9999px, rgb(1, 2, 3), 0.123) y leer el computed del nodo que lo consume: tiene que seguirlo. Si un token no mueve nada, es un token que miente (fantasma o nombre equivocado). Se automatiza en la misma sonda (el.style.setProperty('--{c}-…', sentinel) → getComputedStyle).
  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-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.