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/CONTINUE-theming.md

60 KiB

CONTINUE — eje Theming «theme-reach» (handoff, act. 2026-08-22)

Estado: EN EJECUCIÓN — F0 y F1 CERRADAS · F2 en curso, bloque F2-B CERRADO con su revisión adversarial pasada (2026-08-22); la cola sigue abierta y text-gradient ya está dentro (2026-08-22). Alcance global 56 % (33 % al abrir el eje, 45 % tras F2-A, 50 % tras F2-B, y +6 puntos al arreglar la MEDICIÓN, no el código: ver abajo). Sin contrato 24; al 100 % 17; alcance <20 % 19.

LO PRIMERO AL ENTRAR (cinco minutos, en este orden)

  1. Comprueba que el suelo no se ha movido:

    node --import tsx/esm scripts/theming-census.ts        # 162 · 5.188 · 2.638 (56 %) · 24 sin contrato
    node --import tsx/esm scripts/theming-census.ts --names # DESVIADAS 0 — la gramática se mantiene
    

    El segundo cubre componentes que component:audit NO ve (p. ej. picker-shell, eidos-only sin morfo): córrelo siempre, no te fíes del audit para la gramática.

  2. Arranca el dev server ANTES de medir nada: preview_start con la config dev de .claude/launch.json (puerto 5180). Las sondas se ejecutan con node, no con tsx, desde la raíz, y su default es 5173: pásales siempre la URL como tercer argumento.

  3. Lee el veredicto §5 de la ficha (docs/audit/theming/{c}.md). Si está vacío — lo están ~130 de 171 — lo escribes TÚ midiendo, y luego ejecutas: ése fue el flujo de todo F2-B. La §4 generada no se sigue a ciegas; ver «Lo que F2-B enseñó».

QUÉ HACER AHORA

1. F2-B está CERRADO — su revisión adversarial (§7.7) se hizo el 2026-08-22

4 hipótesis · 3 refutadas · 1 hallazgo real. Detalle en PLAN §8. Lo que hay que recordar de ella:

  • drag-drop.preview-z mentió y se retiró: soma escribe su z-index INLINE, y la adjudicación que lo daba por vivo se había medido sobre un nodo creado a mano. Una adjudicación «forzado → alcanza» vale lo que valga el nodo que se forzó — si lo creaste tú, no lleva lo que soma le pone.
  • El orden de carga NO es el mismo en dev que en producción (medido con build + CDP). El empate de §12.9 sigue abierto, ahora con las dos medidas.
  • Un worktree cuyo node_modules es una junction fuera del root no hidrata: Vite responde 403 a @fs/…. La primera pasada comparó contra una base muerta sin que nada lo delatara — añade server: { fs: { allow: [...] } } a su vite.config.ts y comprueba que un componente monta antes de comparar.

2. El censo cuenta distinto desde el 2026-08-22 — léelo antes de comparar cifras

Un privado que DERIVA de un público ya cuenta como alcanzable. Es lo que F0 encargaba («leer --_c-x: var(--c-y) y reclasificar») y sólo se había implementado para el INFORME: la columna «¿deriva de un público?» de la §3 lo decía, pero el contador lo seguía puntuando como deuda. Medido: 240 privados en 59 componentes estaban en ese estado, haciendo exactamente lo que la doctrina prescribe y penalizados por ello.

El cierre es TRANSITIVO (--_a: calc(var(--_b) * .3) alcanza si --_b alcanza) y tiene muta-prueba: apuntar un privado a un primitivo crudo baja la cifra, revertirlo la sube. Global 50 % → 56 %, privados 625 → 341, al 100 % 10 → 14. avatar pasó de 36 % a 75 % y picker-shell de 76 % a 95 % sin tocar una línea de su código.

Consecuencia: cualquier cifra anterior al 2026-08-22 no es comparable con las de ahora. Las de este handoff ya son las nuevas.

3. Lo que dejó la sesión del 22 (24 componentes en F2-B, todos con 0 diffs)

grid-list 85 · textarea 80 · tree-view 92 · code-block 81 · card-group 90 · spinner 70 · picker-shell 95 · link-preview 100 · drag-drop 89 · kbd 86 · link 36 · code 100 · virtual-list 67 · virtual-grid 73 · skeleton 45 · label 100 · announce 88 · clipboard 63 · scroll-frames 33 · mark 60 · menu-dial 38 · knob 75 · skin-media-player 22 · text-circular 100 · text-focus 100. Más cinco adjudicados como 0 % estructural (aspect-ratio, text-blur, cascade, motion, date-picker), la revisión adversarial §7.7, el arreglo del censo y el test del suelo.

Tres cosas que enseñó y conviene tener delante al seguir:

  1. La propuesta §4 generada falla de cuatro maneras. Hereda el incumplimiento del CSS de partida (si ya violaba recipe-css-contract, lo repite); infla hasta 4× (71 tokens para 23 knobs en spinner); nombra por el primer selector de la regla y no por el nodo pintado (llamaba root-bg a una guía de indentación); y usa el slot color donde la gramática firmada exige fg — R-5.3 lo caza, pero después de escribirlo.
  2. Declarar un público «de facto» puede MATAR una escala. Varios componentes consumen var(--c-x, <default>) sin declarar --c-x. Si detrás del fallback hay un privado por talla (knob), declarar el público lo mata: el público va primero y gana siempre. Hay que subir la escala al TSC. Si no lo hay (text-circular, text-focus), declarar es seguro.
  3. Cuenta los nodos que ve la sonda antes de creerte un gate verde. Cuatro componentes miden UN nodo, picker-shell medía CERO. Y el centinela tiene tres puntos ciegos ya medidos: congela transiciones, su valor grande se capa contra min(), y no ve lo que la demo no monta.

3.bis Lo último ejecutado — text-gradient (2026-08-22)

67 % → 100 %, 7 claves. Detalle y porqués en PLAN §8 y en el veredicto §5 de su ficha. Lo que conviene tener delante al seguir con la cola:

  • La ficha §4 falla también contra el CATÁLOGO, no sólo contra la doctrina. Proponía conservar weight (77 claves del catálogo dicen font-weight), y marcaba width/height con «⚠ eje físico» cuando el calc(100% - 2px) era UN knob —el trazo del marco— del que el hueco deriva. Antes de acuñar un nombre, cuéntalo en base.ts: el catálogo es el que habla.
  • Un literal igual a un paso de escala no se congela: se nombra. 1px → var(--border-width), blur(4px) → var(--blur-sm). Y ojo con blur: es familia MÉTRICA del eje scaling (theming §23), así que un literal es ciego al zoom global aunque el computed coincida a scaling 100.
  • Los dos instrumentos ya saben encender una VARIANTE. La sonda tiene DEMO_VARIANTS (chips que se re-pulsan tras CADA recarga — la sonda recarga una vez por talla) y el centinela acepta openWith apuntando a un chip, no sólo a un trigger de panel. Sin eso, esta demo daba 2 nodos y los tres tokens del marco no se medían. Cuenta los nodos antes de creerte el gate.
  • Las demos NO forman parte del eje (autor, 2026-08-22): sólo los componentes. Un hallazgo de demo o del harness del sitio no se registra ni se mide; la demo se toca únicamente como INSTRUMENTO (encender una variante para que la sonda vea los nodos), y lo mínimo.

3.ter text-scramble ADJUDICADO (2026-08-22) — sin contrato, y por qué

Su 50 % no es deuda ni logro: dos literales son la técnica sr-only (como text-blur) y los dos «públicos» son tipografía que no debería acuñar, o sea la firma 1.bis. Escribirle un contrato ahora sería acuñar justo lo que esa firma propone retirar. Veredicto §5 escrito, cero cambios de código.

De paso salió el barrido mecánico que --radius-xs y --font-size-4xl pedían: 9 referencias fantasma con fallback en el catálogo (5.793 nombres declarados). Tabla en next-features §13. La más grave no es de esta familia: --color-content-tertiary sigue vivo en TRES recetas —es el caso histórico que recipe-contract §1 cita como origen de la 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í.

4. La cola que queda, por alcance

componente knobs nota
palabras 471 (22 %) el mayor del catálogo con diferencia, sin tocar; D-TH.3 decide si entra
field-langs 41 espera el mandato Field (firma 2), que NO es tokenización: manda que los x-field incorporen el wrapper Field — cambia composición y mueve píxel
mockup 29 (38 %) ⚠ sin ruta de demo (404), como picker-shell: sin sonda ni guard. Y sale NEEDS-WORK en el audit, preexistente
chronos 245 (1 %) el segundo mayor; sin abrir
result · banner · … varios ver el censo, reach < 20 % = 19

avatar ya está al 75 % y knob al 75 % (ambos hechos). heading / text / display son primitivos tipográficos: su superficie es --style-* y el censo los saca del ratio. range-calendar / month-grid / year-grid están a 0 % honesto (consumen calendar-surface).

heading / text / display son primitivos tipográficos: su superficie es --style-* y el censo ya los saca del ratio. range-calendar / month-grid / year-grid están a 0 % honesto (consumen calendar-surface). alert-dialog, css-field, mask-field, number-field, highlight, stack, wrap, auto-grid tienen cero knobs: no hay nada que tokenizar.

5. Lo que espera TU FIRMA (todo medido, nada bloquea la cola)

  1. El plano overlay impone tipografía a VEINTE componentes (font-family + line-height junto a superficie/borde/sombra). ¿Un plano de profundidad manda sobre la tipografía o sólo sobre la superficie? — §12.9. Es la que más urge: cada overlay que se tokenice la arrastra. 1.bis La familia text-effects ACUÑA tipografía propia contra el canon (autor, 2026-08-22: «¿qué sentido tiene que text-effects introduzca diferentes nombres de tipografía cuando ya hay un canon establecido?»). Siete claves en CUATRO de los cinco (medido al adjudicar text-scramble): text-focus.word-size (3rem, vía el fantasma --font-size-4xl) y word-weight (900) · text-circular.weight (900) y font-size (1.5rem) · text-gradient.font-weight (500, subida el 22) · text-scramble.font y .size (sin declarar). La de text-scramble apunta además a --font-mono, que NO existe: pinta ui-monospace e ignora la mono del tema. text-blur es el único limpio: hereda. Son opiniones del seed react-bits convertidas en contrato por «valor verbatim». Doctrina F2-B regla 2: una receta CONSUME la capa tipográfica (--style-*) o HEREDA (link → inherit); un tratamiento de pintura sobre texto real no tiene tipografía propia (un TextGradient dentro de un h1 ES un h1). Resolución propuesta: retirar las cinco claves y heredar; mueve defaults (TextFocus deja de imponer 3rem/900, TextGradient deja de forzar 500) → firma. Con ello muere el fantasma 4xl. El módulo de tipografía NO se toca. Registrado en next-features §13.
  2. El harness del sitio tapa a code-block y code: [data-uix-docs] pre pisa SIETE propiedades del <pre>, y [data-uix-docs] code el radio del inline. Acotarlo toca el CSS que sirve a ~162 páginas — §13.
  3. El censo no distingue «0 % por deuda» de «0 % POR NATURALEZA» (cinco componentes ya). Sin una clase structural, el gate de F3 es inalcanzable por construcción — §13.
  4. El velo derrama sobre el subárbol entero en tree-view (archetype: 'item' en el <li> branch: 336 px velados en vez de 36) — §12.8, morfo.
  5. La selección de fila de grid-list nunca ha pintado (soma estampa data-state, no data-selected) — §13, morfo.
  6. El borde de foco de textarea sólo se ve con Tab: hover (0,4,0) > foco (0,3,0) > invalid (0,2,0) — §13.
  7. picker-shell sin demo propia (404) y con partes de nombre genérico: el instrumental está ciego por convención — §13.
  8. Extender la clase system del censo a las capas compartidas (viene de antes de esta sesión).

EL PROTOCOLO, EN CORTO (el largo está en PLAN-theming.md §7)

Un componente = un commit. Por componente:

  1. Leer entero: receta, su bloque en recipes/base.ts, README de eidos, morfo, y el veredicto §5 de su ficha.
  2. Sonda ANTES (__theming-probe.ts {c} antes.json <url>).
  3. Escribir el contrato en recipes/base.ts — fusionando en el bloque existente si ya lo hay (los forwards de paleta THM-2 ya ocupan bloque en muchos componentes; un segundo bloque con el mismo nombre se descarta EN SILENCIO).
  4. La receta consume los nombres resueltos; retirar los bloques [data-size] del CSS — los emite el TSC, y dejarlos vivos hace que el diff dé 0 por la ruta vieja.
  5. npm run generate:eidos-css, sonda DESPUÉS, diff = 0.
  6. Guard R-5.4 (npm run theming:sentinel -- {c} <url>) y adjudicar cada «no effect» uno a uno EN EL LEDGER (scripts/theming-sentinel-exceptions.ts, una razón medida por token) — el guard falla con un muerto sin adjudicar, y avisa de excepciones STALE.
  7. Guards: censo --only {c} · component-audit --only {c} · eidos-lint {c} · vitest run src/uix/eidos (comparar POR FICHERO: el rojo conocido es skin-media-player) · rtl:check · docs:check · npm run check atribuido POR FICHERO (73 errores globales son de otras sesiones).
  8. README del componente con su sección «Talla y tema».
  9. Tab Tokens en la demo — una línea: <TokensPanel component="x" stage={stageRef ?? undefined} /> + su botón.
  10. Commit con las cifras y los artefactos en el mensaje.

LO QUE F2-B ENSEÑÓ (2026-08-22 — léelo antes de tocar nada)

Un alcance bajo puede ser doctrina bien aplicada, no deuda. 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 (el enlace toma la tipografía del párrafo); scroll-frames 33 % porque es layout puro. El % no es la métrica de calidad — lo que importa es que lo que queda fuera esté MEDIDO y escrito.

Cuatro clases que se repiten, con su veredicto ya fijado:

  1. Un privado con DOS fuentes es un conmutador, no deuda. Cinco casos (textarea, spinner, skeleton, code, label): aplanarlo obligaría a duplicar cada regla por color. Se queda privado y se adjudica.
  2. Lo que la capa tipográfica posee NO se acuña (--style-*): cinco casos (code-block, kbd, announce, code, label).
  3. Un forward de paleta THM-2 leído a pelo no admite público sin duplicar la regla.
  4. Identidad ≠ knob: 100%, 50% de un círculo, line-height: 1 de una tecla, transparent de una variante ghost.

La propuesta §4 generada FALLA de tres maneras distintas, y las tres costaron medición:

  • Hereda el incumplimiento del CSS de partida: copia el valor verbatim, así que si ya violaba recipe-css-contract (primitivo crudo en vez del bundle), la propuesta lo repite. Lo cazó la suite en code-block.
  • Infla hasta 4×: en spinner pedía 71 tokens para 23 knobs, multiplicando UNA coordenada de talla por variante × parte × dimensión — veinte claves con el mismo valor.
  • Nombra por el primer selector de la regla, no por el nodo pintado: en tree-view llamaba root-width / root-bg a la GUÍA de indentación, y fundía en un solo nombre el padding-inline de la fila y el paso de profundidad, que se SUMAN en el mismo calc.

El instrumento estándar no ve tres clases de componente, y una sonda que compara pocos nodos pasa en falso: panel portalado que abre en hover (link-preview: 1 nodo), componente sin demo propia y con partes de nombre genérico (picker-shell: 0 nodos), y parte que sólo existe durante una interacción (el fantasma de drag-drop, el indicador de clipboard, el rótulo de scroll-frames). Cuenta siempre los nodos que ve la sonda antes de creerte un gate en verde.

El guard R-5.4 ganó SEIS capacidades en este bloque, todas nacidas de una medición y con los del ledger re-verificados sin regresión tras cada una: puntero parqueado tras el blur (reopen() corre antes de CADA token), lectura de ::placeholder (la nota de §13 que decía que no se podía era falsa), COMPONENT_OVERRIDES de prefijo de atributo y selector de apertura, apertura por hover, y text-decoration-thickness / -underline-offset en la lista de propiedades.

Y dos errores de proceso que costaron tiempo:

  • El guard de claves duplicadas del handoff inspeccionaba el VACÍO: grep -E lee como una t literal — 0 coincidencias sobre 130 bloques. Corregido más abajo con una versión que CUENTA.
  • $? tras una tubería es el del último comando, no el del guard: un fichero de excepciones con sintaxis inválida pasó por bueno. Comprueba el exit REAL.

LO QUE F2-A ENSEÑÓ (léelo antes de tocar nada)

Sobre los veredictos. Tres de ocho tenían un error de lectura que sólo apareció midiendo: el título de feed escala por aria-level, no por talla; --gp-current-gradient lo estampa soma, no el wrapper de eidos; y el trigger de gradient-picker no había que tokenizarlo sino borrarlo. El veredicto orienta; la medición decide.

Un token que no mueve nada es un token que miente. Ocurrió tres veces: table.selected-row-fg y la tinta de listbox (el arquetipo las gana por especificidad) y el cromo ENTERO del trigger de gradient-picker (36 de 45 tokens sin efecto — lo pinta popover.css, que gana 0,2,0 contra 0,1,0). En los tres casos la acción correcta fue retirar la declaración muerta, y en los tres retirarla dio 0 diffs, que es la prueba de que estaba muerta.

Un alias puro no se renombra: se borra. Los dieciséis --_mp-* de media-player eran alias de su propia fuente; el veredicto pedía renombrarlos y lo que procedía era eliminar la capa entera.

Lo que la capa posee, el consumidor no lo acuña. listbox se queda en 68 % a propósito: su ritmo de fila es de list-surface. La métrica penaliza hacer lo correcto — registrado en next-features.md §13.

Antes de acuñar un hover-*, mide contra la capa de estado. En listbox el highlighted pinta DOS veces (plano + velo); se dejó sin token para no bendecir un duplicado condenado.

La sonda miente de tres maneras distintas, y todas costaron tiempo aquí: congela transition pero nunca animation; el estado hover se mide después de forzar tallas, así que arrastra la última; y un empate de especificidad resuelto por orden de carga hace que tree-grid dé 0 ó 3 diffs según la recarga. Ante un diff inesperado, lo primero es correr la sonda dos veces sobre el MISMO código. La revisión adversarial encontró y arregló la causa del caso feed (2026-08-21): no era una animación sin localizar sino la demo aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma sema commit-settle anima box-shadow sobre el nodo medido. Sonda y guard esperan ahora a que [data-busy] caiga; con eso, dos corridas del mismo código dan 0 diffs. Y el «falso negativo sin causa» de command.input-border también tenía causa: el centinela ABRÍA haciendo clic en el input, y un clic de ratón sobre un input de texto SÍ casa :focus-visible — la regla de foco pintaba el borde y el token de reposo leía muerto. El instrumento viejo acumulaba 22 falsos negativos de 26; las tres causas están arregladas en theming-sentinel.ts.

LAS INCIDENCIAS NO SE ARREGLAN AQUÍ

Todo lo que este eje destapa y no le toca arreglar está en docs/next-features.md §12 (el contrato de cascada del velo de estado: 131 declaraciones-atajo en 45 componentes, la prop hoverable que no suprime nada, el empate por orden de carga, la banda que mata el hover, el token de tinta que no puede ganar al arquetipo, el thumb de scroll-area sin velo) y §13 (huecos de instrumento y de demo, más los cuatro que salieron de gradient-picker y listbox).

Regla: si una incidencia mueve píxel o toca morfo, se MIDE, se anota ahí y se sigue. No se arregla dentro del commit del componente.

PENDIENTE DE FIRMA (nada bloquea la cola)

  • Diseño de calendar-surface — la firma 1 aprobó el PATRÓN, no un diseño concreto; ese se presenta.
  • scroll-area.thumb-bg-hover — único knob de la firma 3 sin resolver: su thumb lleva archetype: 'thumb', que no recibe velo. Cambiarlo es morfo.
  • Extender la clase system del censo a las capas compartidas, como se hizo con --style-* para los primitivos tipográficos (§13 del registro).
  • El striped de table inerte con RowDetail — FIRMADO Y EJECUTADO 2026-08-21: :nth-child(even of [data-table-row]) con :where(:not([data-selected])), mismo (0,6,0) para no matar el hover. 12 diffs exactos (2 filas × 6 estados); selected y hover verificados.
  • El arquetipo item de table vive en la fila Y en la celda — el velo cae en la celda bajo el puntero (elementsFromPoint, 2026-08-21). La decisión de morfo del hoverable (§12.2) debe contemplar los dos portadores.
  • Que soma lea de un token el gap entre diapositivas de carousel — hoy es canal de valor inline y el trío item-gap* se retiró muerto; darle superficie de tema toca soma (§13).

REGISTRO DE LA SESIÓN DEL 2026-08-20/21 (histórico — no es la agenda)

EJECUTADO 2026-08-21 — la capa calendar-surface (firmada y medida)

La medición cambió el encargo. El handoff la vendía como «una capa nueva, ~220 knobs, 43 % → ~48 %». No lo era: la capa ya existía de hecho, sin nombre ni casa. --calendar-* se emite en :root (76 claves) y sus consumidores no acuñan NADA — range-calendar 114 referencias prestadas y 0 propias, month-grid 77/0, year-grid 77/0, y lo único ajeno que leen es sistema puro (--focus-ring-*, --state-hover). Su 0 % era el artefacto de listbox (§13) pero TOTAL.

Lo que se escribió: lib/calendar-surface.css con el hook data-calendar-surface, cuatro coordenadas por talla (padding, control-size, day-size, font-size, xs..lg — la familia no tiene xl, y la celda va DOS pasos por debajo del bundle de control, deviación que ahora se lee en un sitio en vez de esconderse en cuatro bloques [data-size] idénticos) y la FORMA del anillo de evento. El vocabulario se queda en la entrada calendar de la receta, que pasa a ser la de la FAMILIA: ésta es una capa HÍBRIDA y es lo que la distingue de list-surface / viewport-placement, que componen primitivos y no tienen entrada de receta (76 claves semánticas tienen que seguir siendo alcanzables una a una por config).

El defecto que la justificó, medido: --calendar-event-shadow y --calendar-day-holiday-shadow se emitían con ámbito [data-calendar] y los otros tres los leían desde hosts que nunca llevan ese atributo → variable VACÍA, box-shadow inválido en computed, el anillo sema no pintaba jamás en tres componentes. Ahora la forma vive en la capa y el acento entra por --_calendar-surface-accent, que cada superficie alimenta con su forward de paleta THM-2.

⚠️ Lo que casi se me cuela, y la lección: estampar el hook sólo en los cuatro wrappers standalone dejaba a DatePicker y DateRangePicker con --calendar-padding VACÍA y el panel a padding 0 — renderizan la superficie soma por sus PROPIOS wrappers (date-picker-calendar, -month-view, -year-view, date-range-picker-calendar) y un panel portalado no hereda nada. Son SIETE wrappers, no cuatro. La tentación era enganchar la capa a las cuatro identidades de componente; eso viola la regla 1 de capas compartidas, y la respuesta correcta es un sello por wrapper.

El censo aprendió a no penalizar: LAYER_VOCABULARY en theming-census.ts (mismo precedente que D-TH.2-b con --style-*). Global 43 % → 45 %, calendar 75 % → 84 %. Los tres consumidores siguen leyendo 0 %, y es honesto: con el vocabulario de la capa fuera del denominador sólo les quedan los forwards de paleta THM-2 (que el censo cuenta como private en TODO el catálogo) y literales de layout. Reclasificar el forward de paleta es la misma pregunta un piso más abajo — anotada en §13. list-surface NO se registró: sus consumidores puentean por privados, otra forma, y mueve diez componentes de golpe.

EJECUTADO 2026-08-21 — revisión adversarial de F2-A (§7.7) + guard R-5.4

Molde Sidebar, medición propia contra worktree con solo el CSS revertido a la base del bloque. 34 hipótesis · 26 refutadas · 8 reales. Refutado: el «default idéntico» aguanta en los ocho (0 diffs; los 3 de tree-grid y los 5 de feed son del instrumento, idénticos corriendo dos veces el mismo código), 0 tokens huérfanos de 336, el bundle ES 1:1 con los primitivos, el «verificado a mano» de gradient-picker era verdad. Los 8 reales: (1) el re-point de --command-radius en el Dialog era declaración muerta con comentario falso — retirado, 0 diffs; (2) el 2.25 vertical del carrusel iba a pelo y active-indicator-scale solo alcanzaba la horizontal — tokenizado, 0 diffs; (3) el trío item-gap* de carousel no podía ganar al gap INLINE de soma — retirado, 0 diffs (canal de valor, como --gp-current-gradient); (4) el striped de table es inerte con RowDetail (parity de :nth-of-type) — PENDIENTE DE FIRMA; (5-7) el instrumento tenía 22 falsos negativos de 26 (clic-foco en el input · override solo en [data-{c}] · pseudos invisibles) y la no-determinación de feed era la demo cargando (commit-settle sobre [data-busy]) — las cuatro causas arregladas en theming-sentinel.ts y la sonda; (8) tres cifras de especificidad del registro §12 estaban desviadas en uno y el arquetipo de la fila de table vive TAMBIÉN en la celda — corregidas. R-5.4 nace: npm run theming:sentinel -- <c> <url>, ledger theming-sentinel-exceptions.ts con una razón medida por token (46 excepciones en los ocho, cinco entradas mías salieron STALE y las borró el propio guard), doctrina en recipe-contract §4. Dos hallazgos los produjeron los propios arreglos: el blur apaga focus-input-border (adjudicado) y la espera de asentado desmonta el spinner de feed (adjudicado). Censo tras la retirada: carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto.

Lo que sigue es el detalle de cómo se llegó aquí: las firmas, el codemod, el guard, y el bloque componente a componente. Se conserva porque cada decisión lleva su porqué medido, pero la agenda es lo de arriba.

EJECUTADO 2026-08-20 — vocabulario de tokens: D-TH.6 (codemod) + R-5.3 (guard)

Por qué antes que nada (conversación 2026-08-20): el canon de nombres existe (theming §6.7 slots + recipe-contract §1 dimensionales) pero no tiene guard, y la medición del 2026-07-01 ya demostró que canon sin guard deriva 30-85 %. Está derivando: el backfill de ayer escribió la forma DOCUMENTADA (modificador delante, 36 claves) mientras el catálogo habla al revés — cada componente que se corrija sin firmar esto ensancha la brecha. El guard de eventos (eidos-event-vocabulary.ts) es el precedente: nació de un commit-resize muerto tres meses en una receta; R-4.4 es el otro (una regla de nombres ya guardada, deuda muerta «en el mismo pass, sin allowlist»).

FIRMADO 2026-08-20 — el slot de tinta es fg; color como slot MUERE. La sesión del 20 leyó lo que la del 19 no había leído (arts/color, architecture/eidos.md entero, motion §17, §25/§38) y encontró que la pregunta central no era «normalizar hacia lo documentado» sino una CONTRADICCIÓN entre dos doctrinas: theming §6.7 r7 (fg) contra el principio de plataforma del px/py («el token se llama como la propiedad»: color). El autor adjudicó fg: el principio de plataforma gobierna los ejes DIMENSIONALES, no la pareja bg/fg (si la gobernara, bg sería background-color); color es la palabra más sobrecargada del sistema (--color-*, data-color, 9 roles, arts/color); on-fg compone; Chakra v3/Panda hablan la pareja.

Inventario CORREGIDO 2026-08-20 (instrumental: los scripts/__names-*.ts de esta sesión — census, slots, owners, audit7, audit7b; el exacto y regenerable lo produce --names en el paso 1). Sobre 3.333 claves públicas, 301 desviadas en 66 componentes — 193 con color como slot, 78 con el modificador DETRÁS a nivel de receta, 30 con ambas. La semilla __names-inventory.ts decía 370 porque contaba como desviadas 47 claves {rol}-{slot-de-rol} que son CANÓNICAS (button.primary-solid-hover, toggle.palette-hover… — COLOR_ROLE_SLOTS pone el modificador detrás POR CONSTRUCCIÓN) — un codemod sobre las 370 las habría roto. Adopción viva: 196 -color contra 55 -fg (las fg son mayormente las del backfill de este eje). Peores: tag-group 22 · toast 15 · field 13 · calendar 13 · tabs 12. Tres familias, tres gramáticas (el guard codifica las tres): rol/paleta = modificador DETRÁS (canon) · sistema (--state-hover, --opacity-hover) = detrás, fuera del alcance del guard · recipe-level = §6.7 r7 (delante: hover-bg, 13 claves conformes contra 22 bg-hover).

Falsos amigos (22) — FIRMADOS en el acta: *-focus-ring-color (3, espeja la familia del SISTEMA) · aura.orb-color-* (6, color como sustantivo del orbe) · gradient-builder.stop-color-* (4, stop-color ES la parte — verificado: gradient-builder-stop-color.svelte + parts DOM) → se quedan · los 7 con modificador «fuera de vocabulario» resultaron ser DOS clases que el morfo ya resuelve (valores de data-state/ejes declarados: active, partial, read, failed; y current = aria, que entra al vocabulario universal) más una de pseudo-partes (played/buffered) → se renombran (destinos en el acta) · background.scrim-color-on-{dark,light} (2, chocaban con el prefijo on-) → scrim-fg-over-{dark,light}.

Hovers neutros — firmado: NO se renombran, MIGRAN: §38 + R-4.3 deprecan el hover neutro por componente (es la capa --state-*). dropdown-menu.item-bg-hover, context-menu.item-bg-hover, scroll-area.track/thumb-bg-hover, splitter.handle-bg-hover… la firma 3 del acta ordena su migración a la capa de estado (captura + diff explicado por componente). El codemod los EXCLUYE y el --names los lista aparte como cola de esa migración.

LOS CUATRO PASOS ESTÁN HECHOS (f09e04fab acta · bd916ef3f codemod · 7781d6e4c guard + doctrina, sin pushear). Lo que queda del bloque, y sólo esto:

  • El tercer muro espera a la firma 3. El tipo en defineRecipes (molde PhysicalAxisKey) está escrito y probado: dispara sobre 17 claves, que son los hovers neutros con color en cola de migración. Meterlo hoy rompe npm run check a todo el mundo. Entra cuando la migración las vacíe — dos líneas: una rama ColorSlotKey y las tres excepciones firmadas (*focus-ring-color, orb-color-*, stop-color-*) deletreadas.
  • __names-inventory.ts quedó superado por theming-census --names (contaba 370 porque metía las 47 role-slot). No se borra sin que lo digas.

El registro de los pasos, para quien audite después:

Los pasos, en orden y con su gate (el paso 0 murió el 2026-08-20: D-TH.6 está firmada COMPLETA — ver §«Firmas del 2026-08-20»; la gramática ejecutable es: slot fg · delante lo interactivo, detrás lo dimensional/contextual · modificador ∈ universal (+current) ∪ data-state/ejes del morfo · rol/paleta detrás · hovers neutros excluidos · renombres nominales de los ex-falsos amigos en el acta):

  1. --names en el censo: el inventario exacto por clase de desviación, regenerable, con las 47 role-slot reconocidas como canónicas, los falsos amigos resueltos por el acta (los 3 grupos que se quedan excluidos; los 4 renombrados contados como desviación con su destino) y los hovers neutros listados APARTE (van a la firma 3, no al codemod). Muta-prueba del instrumento: 3 desviadas conocidas salen, 3 role-slot NO salen.
  2. Codemod value-preserving (molde: px/py del 2026-07-06 y su triple muro): -color → -fg (196 + 49 mediales, menos los 3 grupos que el acta deja quedarse y los hovers neutros que migran aparte), los renombres nominales del acta (rating-group · chat-message · breadcrumb · waveform · scrim-fg-over-*), y modificador detrás → delante a nivel receta (bg-hover → hover-bg, 22 claves); renombra la clave en recipes/base.ts Y todos los consumidores (var(--{c}-{clave}) en css/svelte/ts/md — con git ls-files, no de memoria; los .md de HISTORIA — changelog, errores-toxico, PLAN-affix/background — NO se reescriben), regenera, y verifica: diff de generated/ = renombres 1:1, censo global IDÉNTICO (un rename no cambia alcance), sonda de computed en 3 componentes renombrados = 0 diffs. Un commit, script commiteado como registro.
  3. Guard R-5.3 en component-audit — de GRAMÁTICA, no de lista (a diferencia del de eventos, que valida pertenencia a un vocabulario cerrado), y POR FAMILIA: {rol|palette}-{slot-de-rol} con modificador detrás (COLOR_ROLE_SLOTS) ∪ recipe-level [variante-]?[modificador-]?[parte-]?[slot][-talla]? donde talla ∈ las 7 canónicas, modificador ∈ vocabulario cerrado y DELANTE, slot ∈ (§6.7 recipe-level ∪ ejes dimensionales de recipe-contract §1), y la PARTE contra compileMorfo(morfo).parts + la allowlist eidos-only (el mismo criterio que eidos-lint). Severidad error directo: tras el codemod no hay deuda que tolerar, y la rampa warn es como estos guards mueren. Muta-prueba obligatoria de tres caras antes de darlo por bueno: -bg-hover rojo · trigger-color rojo · primary-solid-hover VERDE (memoria a-guard-that-inspects-nothing-passes).
  4. Docs en el mismo pass: recipe-contract §1 gana la fila del modificador (hoy sólo la insinúa §6.7) y §4 la fila R-5.3; theming §6.7 nota fechada con la firma de fg y el acotamiento del principio de plataforma a los ejes dimensionales; T-TH.2 del plan se cierra.

Gates del bloque entero: censo global idéntico antes/después del codemod · suite eidos sin rojos nuevos · component:audit sin fallos nuevos fuera de R-5.3 · docs:check 0 · rtl:check 0.

Después — por dónde seguir

  1. node --import tsx/esm scripts/theming-census.ts — debe dar 162 recetas · 5.203 knobs · 1.841 públicos (37 %) · 56 sin contrato (antes del codemod; tras él, el MISMO alcance con nombres nuevos). Si no cuadra, alguien tocó recetas: regenerar el informe (--report) antes de nada.
  2. Abrir la ficha del componente que toque y leer su §5 Veredicto: dice qué de la propuesta es correcto, qué está mal y qué espera firma. Está verificado contra el CSS; la §4 generada NO se sigue a ciegas. Ojo: los veredictos se escribieron ANTES del acta del 2026-08-20 — sus «espera firma» se cruzan con §«Firmas del 2026-08-20» (la mayoría ya están resueltos: calendar, Field, hover→estado, D-TH.2) y sus nombres propuestos se pasan por la gramática firmada (p. ej. el guide-color de la fila tree-grid de la cola es guide-fg).
  3. Ejecutar con el protocolo §7 entero. Un componente = un commit.

PRIMERA POSICIÓN — repaso de los 7 ya corregidos contra la gramática nueva

Encargo del autor (2026-08-20): revisar lo ya ejecutado por si incumple las reglas nuevas. Auditados los 7 con scripts/__names-audit7.ts y __names-audit7b.ts (claves públicas + privados + consumo en css/README). Resultado: 6 de 7 limpios, 1 hallazgo real.

componente claves veredicto
combobox 106 ✅ limpio — ya habla fg, sin modificador detrás, privados sanos
natural-time-picker 62 ✅ limpio
date-range-picker 24 ✅ limpio
time-range-picker 22 ✅ limpio
proof-of-human 20 ✅ limpio
time-picker 16 ✅ limpio
gradient-builder 72 ⚠ 1 corrección (abajo)

Que los 6 salgan limpios NO es casualidad ni suerte: el backfill de este eje venía escribiendo fg (55 de las ~55 claves -fg del catálogo son suyas), que es exactamente lo que la firma ratificó. La firma valida el trabajo hecho, no lo invalida.

La corrección — gradient-builder.checker-color → checker-fg (1 clave, consumida 4 veces en gradient-builder.css:29-32). Es el color de los cuadros del damero de transparencia: color ahí es slot, no sustantivo — cae de lleno en la regla firmada. Las otras 4 claves que el audit marca (stop-color-title-font-size, -font-weight, -fg, stop-color-sliders-gap) son falso amigo verificado: stop-color es la PARTE (gradient-builder-stop-color.svelte, [data-gradient-builder-stop-color-*]), y una de ellas ya termina en -fg correctamente. No se tocan.

Ejecución: va DENTRO del codemod del paso 2 (misma mecánica, mismos gates), no como commit aparte — es una clave, y sacarla del codemod duplicaría la verificación. Si el codemod se retrasa por las firmas pendientes, se ejecuta sola con el protocolo §7.

BLOQUE F2-A — CERRADO 2026-08-21 (preparado el 20, ejecutado el 21)

Los ocho, con su commit y su alcance final:

componente antes → después commit
pieza 0 (censo) 37 % → 38 % bd032534b
command 0 % → 98 % 4ddea7fe2
table 0 % → 86 % 564133b48
media-player 0 % → 88 % b7a4cef08
tree-grid 0 % → 95 % 9748733ad
gradient-picker 0 % → 88 % d177ee331
listbox 0 % → 68 % 3b325c439
carousel 0 % → 77 % 4cab77667
feed 0 % → 94 % 34a95a550

Global 37 % → 43 %; sin contrato 56 → 49; al 100 % 6 → 7. Más 90aa6dc5f (el TokensPanel que las demos enseñan) y 8324e2ced (el registro de incidencias).

El plan del bloque, tal como se preparó:

El primer bloque del backfill bajo TODAS las firmas. Ocho componentes con veredicto §5 verificado + una pieza de instrumento que va primero. Suma 330 knobs hoy a 0 %; al cerrar, el global debe rondar 37 % → ~42 %. Cada componente: protocolo §7 entero, un componente = un commit, con la gramática firmada (¡el guard R-5.3 y el muro de tipos ya vigilan!).

Pieza 0 — el instrumento ANTES del bloque (D-TH.2-b, firmada). La familia tipográfica B5 mide su superficie por --style-* como sistema transversal: el censo añade --style-* a la clase system PARA los primitivos tipográficos (heading, text, display, code, s-text…), con gate: las cifras de TODOS los demás componentes quedan idénticas, y heading pasa de «36 privados» a su verdad (consume la capa semántica). Va primero porque cambia el suelo del censo — hacerla a mitad de bloque falsearía los antes/después.

# componente knobs defectos medidos (censo + ficha) corrección firmada por el veredicto
1 ✅ command 52 SIN bloque en base.ts; 35 globales · 9 privados sin derivar · 6 literales contrato entero con talla resuelta (patrón Sidebar); radius con la adaptación al Dialog como CONTEXTO (lee un público de dialog, legítimo — no partir en dos); list-padding-inline + scrollbar-inset (patrón combobox); sonda con el palette ABIERTO
2 table 46 23 privados · 14 globales · 5 literales root-height-{k} es la fila → row-height-{k} al bundle; el prefijo root- sobra en el resto; talla
3 media-player 45 37 globales · 8 literales; privados --_mp-* ABREVIADOS (§6 r5 los prohíbe) renombrar --_mp-* al nombre completo (⚠ prettier: la anotación /* literal: */ en la MISMA línea — trampa de natural-time-picker) + tokens; su acento theme-stable está FIRMADO en base.ts — no tocarlo; el ⚠ de bg son DOS knobs (superficie y velo)
4 tree-grid 42 29 privados · 8 globales root-bg-image son las guías de indentación: el knob es la tinta (guide-fg — era guide-color pre-acta), los 5 gradientes se quedan en la receta; talla
5 gradient-picker 38 privados --gp-* ABREVIADOS · 23 globales renombrar --gp-* + tokens; content-width es por talla (20/18/22rem), no un solo knob
6 listbox 38 23 globales · 9 privados; una fila FALSA en la propuesta tokens; retirar la fila font-size = var(--list-font-size): es público de la capa list-surface — un consumidor no acuña lo que la capa posee
7 carousel 36 12 globales · 14 privados · 5 literales tokens + talla; los indicator-width-{k} son geometría propia (rem), ni bundle ni desviación
8 feed 33 16 globales · 13 privados tokens + talla; el título es por talla y el feed tipa por debajo del 1:1 (md → sm), verbatim — no «corregirlo» al 1:1

Cada componente engancha su tab Tokens en el MISMO commit (firmado 2026-08-20, a raíz de que el autor viera que la demo de command no enseñaba su superficie de tema — y resultó que ninguna lo hacía). Es una línea: <TokensPanel component="x" stage={stageRef} /> más el botón del tab; el panel (web/routes/uix/lib/TokensPanel.svelte) lee el contrato vivo con eidos.getRecipeTokens(), así que no hay tabla que mantener. Un componente temable cuya demo esconde sus tokens está a medio entregar.

Gates de cada componente (§7.4/7.5, sin excepciones): censo --only {c} al 100 % o excepción escrita · sonda antes/después = 0 diffs · centinela por token nuevo · component:audit PASS (R-5.3 incluida) · eidos-lint 0 invalid · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero · README del componente con la tabla «Talla y tema» (molde navigation-menu). Al cerrar el bloque: revisión adversarial §7.7 (molde Sidebar: refutar «idéntico», «alcanza», «píxel arriba» con medición propia).

Al cerrar F2-A, lo desbloqueado por las firmas espera en este orden:

  1. Diseño de la capa calendar-surface (firma 1) — se PRESENTA antes de escribirla (nombre, ejes, qué posee; molde list-surface y sus 4 reglas). Desbloquea los tres gordos del catálogo: range-calendar (98) + month-grid (61) + year-grid (61) y media date-range-picker — 220 knobs, el salto de ~42 % a ~47 %.
  2. Mandato Field ejecutable (firma 2) — field-langs (41) + las mitades de time-picker / time-range-picker / date-range-picker.
  3. El resto del orden B1-B8 de D-TH.4 (grid-list 32 · textarea 28 · …).

AL TERMINAR EL EJE ENTERO — partir recipes/base.ts por componente

Firmado 2026-08-20, a raíz de la pregunta del autor «¿por qué todos los tokens viven en un fichero?». Va al final de TODO el themeable, no al cerrar un bloque: mientras quede un componente por tokenizar, ese fichero se toca en cada commit.

recipes/base.ts son 6.223 líneas y 128 componentes, y NO es un fichero de tokens: es el valor del campo recipes de EidosConfig, serializable, que un tema hidrata. Por eso los valores no pueden mudarse a components/{x}/ — se intentó y se retiró (architecture/eidos.md: «The old tokens/components/* were retired»). Lo que sí puede repartirse es la AUTORÍA:

// lib/recipes/table.ts
export const tableRecipe = { 'row-height-md': '…', … };
// lib/recipes/base.ts  — el valor sigue siendo UNO
export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ table: tableRecipe, … });

Verificado antes de proponerlo, no supuesto: el muro de tipos de defineRecipes SIGUE disparando cuando las claves llegan por spread desde un fichero suelto — probado con trigger-color (slot color) y padding-x (eje físico), ambas dan error de compilación igual que inline. El único consumidor sensible a la forma es eidos-purge.ts, que hace Object.keys() y es indiferente. recipe-css-contract, el censo, R-5.3 y getRecipeTokens() leen el objeto compuesto.

Los tres costes que paga el fichero único, todos medidos en la sesión del 2026-08-20: casi entra un SEGUNDO bloque table que el catálogo habría descartado en silencio (la trampa está anotada abajo); una coma perdida rompió el catálogo entero dos veces; y es el punto de conflicto de todas las sesiones concurrentes. Repartido, un error afecta a un componente.

Por qué al final y no antes: son ~6.000 líneas movidas en el fichero que cada commit del backfill toca. Hacerlo con componentes pendientes garantiza conflicto con toda sesión que esté tokenizando; con el eje cerrado es un commit mecánico y aislado.

Gate: THEME_BASE_RECIPE_TOKENS idéntico antes/después (comparación ESTRUCTURAL del objeto, no del fichero) · censo global idéntico · generated/base.css sin un solo cambio · el muro de tipos con su muta-prueba (las dos claves malas siguen sin compilar).

FIRMA 3 en ejecución — hover → capa de estado (act. 2026-08-20)

El diagnóstico cambió al empezar. La firma decía «migrar los hovers neutros a la capa de estado». Al hacerlo salió que el velo del sistema no llegaba a ninguno, y no por el hover: por la regla BASE. archetypes.css pinta el velo con :where(...), peso (0,0,0); cualquier receta que declare el ATAJO background: sobre ese mismo nodo fija background-image: none con (0,1,0) y lo cancela SIEMPRE — ni en hover ni nunca. El hover propio no era un capricho: era la cicatriz de un velo que no llegaba.

Así que cada migración son DOS cambios: background: → background-color: en la base, y fuera el hover propio.

componente commit medido
checkbox b35855c20 8 diffs, todos en hover: velo + fondo base (2.436 valores)
splitter c66b73810 2 diffs en el asa (812 valores)
switch 3219a19c3 2 diffs en el track off (377); el swap del checked, verificado
date-range-picker 0c80a8efb sin medir el pintado — la demo no monta kind month/año

Los 6 knobs de la cola eran 47 hasta que se midió la PROPIEDAD: el velo es background-image, así que un hover de borde (22) o de tinta (19) no es la capa de estado, sea cual sea su valor. Esos 41 eran deuda de nombre y se renombraron (93017975c).

Las incidencias van al registro, no al eje

Todo lo que este eje destapa y NO le toca arreglar está registrado en docs/next-features.md §12 y §13 — con su medición, no como impresión:

  • §12 · El contrato de cascada del velo de estado. Seis incidencias que son la misma: nadie fijó cómo compone el velo de archetypes.css con las reglas de una receta. Incluye las 131 declaraciones-atajo en 45 componentes, la prop hoverable que no suprime nada, el empate (0,5,0) que resuelve el orden de carga, la banda de tree-grid que mata el hover, el token de tinta que no puede ganar al arquetipo, y el thumb de scroll-area sin velo al que migrar.
  • §13 · Huecos de instrumento y de demo. Lo que hizo que una medición mintiera o no existiera: falsos negativos del centinela, pseudo-elementos y componentes compuestos que no ve, la sonda que no pasa el ratón por un <tr>, y la lección general — una sonda sobre una parte que la demo no monta compara CERO valores y pasa.

Regla para las sesiones de este eje: si una incidencia mueve píxel o toca morfo, se MIDE, se anota ahí y se sigue; no se arregla dentro del commit del componente.

Lo que queda de la firma 3, y por qué no lo he hecho

  1. scroll-area.thumb-bg-hover — pide DECISIÓN de morfo. El thumb lleva archetype: 'thumb', que NO recibe velo. Migrarlo exige cambiar su arquetipo, y un arquetipo es morfo: se presenta, no se toca de oficio (§7.3-9). La alternativa es dejar su hover como excepción anotada.
  2. El hallazgo sistémico: 141 declaraciones en 45 componentes cancelan el velo del mismo modo, medido con scripts/__statelayer-shorthand.ts (que comprueba contra el morfo que el atajo cae sobre el nodo VELADO, no sobre cualquiera). Peores: radio-group 12 · tag-group 8 · checkbox 7 · table 7 · combobox 6 · tabs 6. Esto explica la adopción 21/135 que midió la auditoría del 2026-07-01: el velo estaba escrito y estructuralmente derrotado. Es un barrido de catálogo que mueve píxel en 45 componentes: no entra en la firma 3, que se firmó sobre unos knobs concretos.
  3. Hueco de demo: date-range-picker no expone kind='month'/'year' (norma N-6), así que la mitad de su API no se ve ni se mide.

Firmas del 2026-08-20 — el acta (las 14, «FIRMO TODAS»)

Presentadas con recomendación fundada y firmadas en bloque por el autor. El detalle razonado de cada una vive en la conversación del 2026-08-20 y en la fila correspondiente del plan §4; aquí el QUÉ ejecutable:

  1. Familia calendar = capa compartida (molde lib/list-surface.css: attr de capa, un eje = token público + ranura privada var(--_x, var(--x)), guardada por shared-layer-contract.test.ts + layer:check). NUNCA ~150 alias --calendar-*. Desbloquea range-calendar (98), month-grid (61), year-grid (61) y media date-range-picker. El diseño de la capa (nombre, ejes, qué posee) se presenta antes de escribirla.
  2. Mandato Field: ejecutable. Los x-field/pickers componen el wrapper Field; las alturas de segmento salen del eje size a nivel familia (--field-control-height-{k}); muere la re-duplicación height-{xs..xl} por componente (theming-audit §2-B: computaban auto desde su nacimiento).
  3. hover → capa de estado, donde reaparezca (empezando por los 3 knobs de combobox). Mueve píxel hacia el canon §38/R-4.3 ⇒ cada migración lleva captura antes/después y su diff explicado en el commit. Desbloquea la poda D-2 (changelog §38 ◇: los --{x}-bg-hover huérfanos, incluido pagination.selected-bg-hover = capa sobre acento).
  4. D-TH.6 completa — slot fg + modificador DELANTE con la regla: delante lo interactivo, detrás lo dimensional y contextual; modificador válido ∈ vocabulario universal (+current) ∪ valores data-state/ejes del morfo del componente; waveform.played/buffered = pseudo-partes; scrim-color-on-* → scrim-fg-over-{dark,light}; hovers neutros FUERA del codemod (migran por la firma 3). Renombres nominales de los ex-falsos amigos: item-fg/active-item-fg/partial-item-fg (rating-group), read-status-fg/failed-status-fg (chat-message), current-link-fg (breadcrumb), played-fg/buffered-fg (waveform). Se quedan: *-focus-ring-color (sistema), aura.orb-color-* (sustantivo), gradient-builder.stop-color-* (parte).
  5. D-TH.1 R-5 dura · D-TH.3 tracks WIP en censo, error sólo fuera de carril · D-TH.5 el default no cambia (única excepción: la firma 3, con captura).
  6. D-TH.2 con la opción (b) de heading: la familia tipográfica B5 mide por --style-* como sistema transversal — no se acuñan --{c}-* tipográficos. La lista KNOB_PROPS es el perímetro; cambios = firma.
  7. D-TH.4 con enmienda: la cola revisada de este CONTINUE prioriza sobre el orden de familias B1-B8.
  8. D-TH.7 con enmienda: R-5.3 a error directo tras el codemod; R-5.1/5.2 esperan F3.
  9. D-TH.8: panel de tokens en /temas, en F4.

Lo ejecutado (censo antes → después)

Bloque del vocabulario, 2026-08-20 (tres commits, sin pushear):

commit qué
f09e04fab el acta de las 14 firmas + la revisión de los 7 ya corregidos
bd916ef3f codemod: 269 claves · 793 referencias · 101 ficheros · alcance IDÉNTICO · 0 diffs de computed
7781d6e4c R-5.3 en error (muta-prueba de 3 caras) + recipe-contract §1/§4 + theming §6.7 + checklist

Lo que el bloque enseñó (más caro de lo que parece):

  • El plan del codemod se escribe ANTES de tocar nada. Releer el clasificador después da CERO desviaciones —ya están renombradas— y el verificador del diff pasa sobre el vacío. Costó dos vueltas.
  • Un valor puede cambiar de texto sin cambiar de significado: 22 privados reapuntan a nombres nuevos. El gate normaliza por el plan antes de comparar, o los cuenta como cambio de valor y da rojo en falso.
  • No pasar prettier sobre lo que el codemod tocó. El árbol tiene CRLF (autocrlf=true) y 88 de 101 ficheros ya salían «sucios» ANTES; un --write en masa reformateó 300 ficheros de deriva ajena. Se midió lo que importaba —¿alarga el renombrado alguna línea?— y la respuesta fue no en todos, así que prettier no hacía falta.
  • Clasificar un hover por el NOMBRE es clasificarlo mal: dropdown-menu.item-bg-hover lee var(--color-primary-element) — es el palette swap con valencia de recipe-contract §2, no el hover bespoke que §38 deprecó. Lo decide el VALOR. Yo lo había listado mal en este mismo handoff.
componente alcance commit
gradient-builder (piloto) 0 % → 80 % 3cdb5b5f0
combobox 0 % → 76 % cef2d32f1
natural-time-picker 0 % → 61 % e774ca7e8
date-range-picker 0 % → 42 % 07778b597
time-range-picker 0 % → 19 % 68eb8af26
time-picker 0 % → 18 % e4fcdd62e
proof-of-human 0 % → 14 % ae91573c3

Global del catálogo: 33 % → 37 % (1.621 → 1.841 knobs públicos). Informe + instrumento: 2591ec2f0; revisión de las fichas 11-20: 3bfe0d413.

El instrumental (vive en scripts/, prefijo __ = temporal del eje)

node scripts/__theming-probe.ts <componente> <salida.json>   # sonda antes/después
node scripts/__theming-diff.ts <antes.json> <después.json>   # el gate: diff VACÍO
npm run theming:sentinel -- <componente> <url>               # guard R-5.4: ¿alcanza cada token? (ya no es `__`)
node scripts/__shot.ts <componente> <salida.png>             # captura 2× del stage

Se ejecutan con node, no con tsx (tsx inyecta helpers que rompen page.evaluate) y desde la raíz del repo, contra el dev server en localhost:5173. Los scripts/__dbg*.ts y __sentinel-targeted*.ts son de un solo uso (verificaciones dirigidas de ayer); no se commitean.

Lo que el instrumento aprendió a fuerza de mentir

  • La sonda congela transition, NUNCA animation: una propiedad transicionada devuelve su valor INICIAL justo tras escribir el token (un token vivo parecía muerto), pero congelar animation impide que Presence monte el panel — y entonces mides un popup que no existe.
  • Un token RESUELTO no se mueve desde :root: se declara en [data-{c}] o en su parte, así que el tema mueve la coordenada. El centinela escribe en :root y en el host, y aun así los resueltos salen «muertos» por diseño.
  • Un panel portalado no ve el ámbito del componente: sus tokens se emiten en :root (o con parts), y un override scoped no lo alcanza.
  • La demo puede tapar el componente: proof-of-human declara su propio min-block-size y fondo sobre el stage — ahí el centinela no sirve, y se dice.
  • Un valor centinela igual al real lee como «no efecto» (9999px contra un --radius-full que ya era 9999px).

Trampas que costaron un commit cada una (no repetirlas)

  • Clave duplicada en recipes/base.ts: añadir un bloque que ya existía lo descarta en silencio (gana la segunda). Guard antes de generar — y que cuente cuántos bloques ve: la versión con \t que vivió aquí hasta el 2026-08-21 no casaba NINGUNO (grep -E lee \t como una t literal) y salía «vacío = OK» sin haber mirado nada — el antipatrón del guard que inspecciona el vacío, en el propio handoff.

    node -e "const t=require('fs').readFileSync('src/uix/eidos/lib/recipes/base.ts','utf8').replace(/\r\n/g,'\n');const k=[...t.matchAll(/^\t'?([a-z0-9-]+)'?: \{/gm)].map(m=>m[1]);const d=k.filter((x,i)=>k.indexOf(x)!==i);console.log('bloques',k.length,'| duplicados',d.length?d:'ninguno')"
    

    Debe imprimir los ~130 bloques y duplicados ninguno. Si dice 0 bloques el guard está roto, no es que el fichero esté limpio.

  • Dejar vivos los bloques [data-…][data-size='…'] del CSS al emitir la cascada por el TSC: pisan los tokens nuevos con los valores viejos, y el diff de computed da 0 porque la ruta vieja sigue mandando. Lo cazó el centinela.

  • Correr component:audit DESPUÉS de prettier, no antes: al renombrar un privado a su nombre completo, prettier partió tres declaraciones en varias líneas y separó la anotación /* literal: … */ de su valor — R-2.1 la exige en la MISMA línea, así que el guard pasó a ver colores crudos donde antes veía excepciones anotadas. Se arregla con /* prettier-ignore */ sobre la declaración: NO acortando el comentario ni tocando el valor. Regresión real en natural-time-picker, cazada un día tarde por correr el audit antes del formateo.

  • Backticks en node -e "…" desde bash: se ejecutan y vacían el texto. Para editar markdown con código, usar el editor, no el shell.

  • prettier --write sobre un README no protege de que un Edit se coma un encabezado: comprobar grep -c "^## Gaps" después de insertar secciones.

  • Lo de ayer sigue vigente: medir el nodo que el usuario señala (píxel arriba), el hover es del sistema, el default no cambia, rama compartida sin stash ni --amend, check por fichero.

Fuentes

  • Plan y decisiones: PLAN-theming.md · informe: docs/audit/theming/README.md.
  • Auditoría del SISTEMA (fase 1, cerrada): theming-audit.md — §B familia calendar, §5.3-3 mandato de Field.
  • Doctrina: docs/theming/reference.md (§5 talla, §6 nombres, §38 capa de estado) · docs/canon/recipe-contract.md · docs/canon/tsc.md · docs/architecture/eidos.md (vertebración tipográfica, columna unused) · src/uix/eidos/components/README.md (capas compartidas).
  • Precedente de la forma por talla: PLAN-sidebar.md §3 + F3 y el bloque sidebar de recipes/base.ts.

Powered by TurnKey Linux.