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

1912 lines
120 KiB

This file contains ambiguous Unicode characters!

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

# CONTINUE — eje Theming «theme-reach» (handoff, act. 2026-08-25)
## EMPIEZA AQUÍ
**El eje ya no tiene cola.** Los 16 componentes que nunca habían pasado están
cerrados o adjudicados, la deuda de artefactos está saldada, y **§12.9 y la
firma B′ quedaron FIRMADAS Y EJECUTADAS** — las dos decisiones más caras
pendientes. Global **68 %**, **45 componentes al 100 %**.
Lo que queda NO es ejecución: son **cinco firmas** y una lista de deuda acotada.
No abras nada nuevo sin leer §«Lo que espera TU FIRMA» de aquí abajo.
### FIRMA EJECUTADA (2026-08-25) — el velo del sistema no muere por un ATAJO
**Qué se firmó** (changelog §56, reverso de §55 el mismo día): al bajar el sobre
de trigger de `popover.css` al suelo, §55 lo dejó a **(0,0,0) — el mismo peldaño
que `archetypes.css`**, que es quien pinta el velo de estado del sistema. Y el
suelo declaraba su superficie con el ATAJO `background:`, que expande a
`background-image: none` — **la propiedad exacta donde vive ese velo**. Resultado:
el velo de hover de todo trigger desnudo dependía de qué hoja cayera después.
No sólo por la regla de hover: el `:where()` deja también la de REPOSO empatada
con la de hover del arquetipo, y ésa sola bastaba. La corrección son DOS
PALABRAS — `background-color:` en `popover.css:60` y `:75`.
**Los números**: **0 px** en el barrido de las **14 identidades del catálogo**
que montan un `[data-popover-trigger]` (68 lecturas en reposo → 0 movidas, 68 en
hover → 0 movidas, `rules split=2` en las 14) con control negativo diff 5 / 4;
las dos sondas del eje a **0 diffs** (`popover` 448 valores / 8 estados, `chronos`
36 832 / 7); y **el velo sobrevive al orden invertido**, donde con el atajo moría
(`linear-gradient(…8 %…)` → `none`). Cae con la firma el pin `chronos.css:417`
(cinco declaraciones a (0,3,0) que ya no defendían nada): **0 diffs en 20
combinaciones `variant` × `size`**, control negativo 20/20 — §13 lo había
adjudicado mal, al velo del `+N more` lo mata `button.css:69`/`:88`. Guarda §55
apartado **(f)** en `active-eidos-config.test.ts`, **4 mutaciones / 4 mordidas**
(las dos primeras muerden la mitad positiva, las dos segundas —el atajo AÑADIDO
junto al longhand— la negativa).
**Lo que quedó fuera, con su número en `next-features.md` §13**: el residuo
`transition` del MISMO empate (suelo `background, border-color` vs arquetipo
`opacity, background-color`, las dos a (0,0,0) — hoy el `border-color` del hover
de popover salta en vez de animarse), la decisión de producto «popover deja de
declarar hover propio» (no es coste cero: 0.9821 → 0.931 **más** el velo hoy,
sólo el velo después) y `calendar-select.css:14`, que **no es un pin** — retirarlo
rompe el tipo en 3 de 4 tallas (12 / 14 / 20 → 16 px).
**Fichas y prosa saneadas**: `chronos.md` (censo 202→198, propuesta 50→47, en
bloques `<!-- mano:… -->` para que sobrevivan a `--report`), `calendar.md` (su
⚠ HALLAZGO «no determinista» lo cerró §55 y ya no apunta a §13), `emoji-picker.md`
y los tres comentarios de código que seguían afirmando **(0,2,0)** del sobre
(`emoji-picker.css`, `gradient-picker.css` —sólo su mitad base, la de estados
sigue viva— y `lib/recipes/base.ts`). Se ANOTAN, no se reescriben: cada medida
fue correcta con la cascada de su día.
### FIRMA B′ EJECUTADA (2026-08-24) — la cascada de paleta es una ESCALERA
**Qué se firmó**: los tres escritores de `--{c}-palette-*` pasan a tres peldaños
que **no empatan** — suelo `:where([data-{c}])` `(0,0,0)` < forward
`[data-{c}]:where([data-color], [data-color-custom])` `(0,1,0)` < tono
`[data-{c}][data-color='X']` `(0,2,0)`. Antes el forward genérico se emitía el
ÚLTIMO empatado a `(0,2,0)` con los bloques por tono, así que **ganaba por orden
de emisión** y 419 claves públicas de tono en 49 recetas no pintaban nunca.
**Por qué B′ y no reordenar la emisión** (que es lo que §13 proponía): el orden
es **contrato invisible** —no se lee en el artefacto, no lo verifica un guard sin
recorrer la hoja entera, se rompe moviendo una línea—. La especificidad es
**contrato EN el artefacto**. Y es la misma ley que §12.9 acababa de firmar para
el plano de profundidad: **una doctrina aplicada dos veces**, no dos parches.
**Los números**: neutra en píxel sobre **~297.000 valores computados**
comparados antes/después (70.912 + 34.336 planos en dos lotes, 57.720 en el
tercero, 134.464 con los tonos ESTAMPADOS en las 49 unidades) — **0 diffs
reales**, y los 17 crudos probados ruido reproduciéndolos sobre código idéntico.
**207 de 353 claves** de tono volvieron a mover un valor computado. Guarda por
**mutación** en `active-eidos-config.test.ts:1761` (6 mutaciones, 6 mordidas;
cierre estructural: toda declaración de paleta vive en uno de los tres peldaños;
anti-vacío `> 40`). Contrato del forward **ensanchado de 2 a 49 recetas** —
vigilaba sólo las de paleta pública y era ciego a las 47 privadas. Suite 35/36
(el rojo `skin-media-player` es AJENO y conocido).
**Dónde vive todo**: doctrina en [`theming/changelog.md`](../theming/changelog.md) §53 ·
contrato en [`theming/reference.md`](../theming/reference.md) (bloque THM-2) ·
emisor `src/uix/eidos/lib/render-css.ts` (`isPaletteSlotToken` parte el bucket
host — **sólo las ranuras de paleta bajan**, el chasis se queda a `(0,1,0)`) ·
guarda `active-eidos-config.test.ts` · ledger `scripts/theming-sentinel-exceptions.ts`.
**Tres leyes que deja escritas, y las tres costaron una medida**:
1. **El silencio del guard NO es señal cuando la adjudicación es por PATRÓN.**
`theming-sentinel.ts` detecta STALE sólo contra el ledger EXACTO
(`k in ledger`), nunca contra `SENTINEL_PATTERN_EXCEPTIONS`. Las 207 claves
resucitadas no dispararon ni un aviso: el estrechamiento hubo que hacerlo
comparando a mano contra las 13 entradas. La única que el guard sí vio fue
`editable.risk-border`, que era exacta.
2. **Un tono por defecto ESTAMPADO resuelve por el forward, no por el suelo.**
El envoltorio estampa `data-color` en toda instancia, la default incluida, así
que `<Button color="primary">` lee el forward y `--button-primary-solid` no
pinta. Quitar el atributo ES hablar en silencio, y entonces sí pinta (medido:
`oklch(0.5556 0.1829 305.86)` → `rgb(1, 2, 3)`). Eso es `PALETTE_FLOOR`; el
resto de supervivientes son `TONE_UNREACHED` (el guard no sabe combinar el
estampado del tono con el hover ni con la variante). Ledger 17 → **28 patrones**.
3. **Un `0/N` se investiga, no se reporta.** `switch` dio **0 de 12** claves de
tono vivas y `button` sólo 14 de 49 — y ninguna de las dos era la cascada: la
ficha de `switch` monta UN switch y arranca `unchecked` (y todas sus ranuras
cuelgan de `[data-state=checked]`), y `button` no declara `COMPONENT_OVERRIDES`,
así que no se barre `data-variant` y su ficha monta UN botón sólido. Forzando
el estado, las dos alcanzan. Es la ley 2 del §«Lo primero que debes saber»,
confirmada una vez más.
### Lo primero que debes saber mañana
1. **⚠⚠ EL ÍNDICE DE GIT ES COMPARTIDO.** Un solo `.git/index` para todas las
sesiones de este worktree. Verificar con `git diff --cached` NO basta: entre
que miras y confirmas, otra sesión puede reescribirlo. **Verificación y
`git commit` van ENCADENADOS en la misma invocación de shell.** Costó un
commit con trabajo ajeno dentro y un ledger sin parsear durante cuatro
commits de HEAD. Y me lo hice yo mismo una segunda vez con un `git add`
derivado de `git diff` en vez de rutas explícitas.
2. **El instrumento miente antes que el código, y hoy mintió cinco veces.**
La sonda no congelaba nada · el guard mide UN nodo en siete portalados · un
árbol de trabajo rancio da un `0/98` falso · un grep con la frase exacta
encuentra 2 de 7 · y una lista escrita por Python en Windows lleva `\r` y
revienta el barrido entero con «todos fallan igual». **Un `0/N` o un «todos
igual» no es un resultado: es el instrumento.**
3. **Antes de retirar una clave, mira quién la lee** (paso 8.bis de §7.3, nuevo).
`scroll-area.auto-max-block` leía muerta en su demo y la consumen CINCO
recetas ajenas. Retirarla habría roto seis componentes sin que un guard se
pusiera rojo.
### Qué hay para hacer, por orden
- **Nada de tokenización.** Si aparece un componente nuevo, el protocolo §7 del
PLAN es la ley y el brief de agentes vive en el scratchpad de la sesión.
- **Deuda B — 3, cada uno por su razón**: `audio-player` (no tiene README de
eidos donde escribir «Talla y tema»), `palabras` y `proof-of-human` (carriles
propios).
- **Deuda de instrumento, registrada en §13 y sin bloquear a nadie**: el guard
congela `transition` pero NO `animation` (la sonda ya sí) y no fotografía
`transform`/`translate`; `eidos-lint` no conoce las exenciones que el test de
vitest sí tiene; la columna `size` del censo se apaga cuando el eje sube al
TSC. Las tres piden re-verificar el ledger entero, así que son firma pequeña.
- **Lo que espera TU FIRMA** (todo medido, ninguno bloquea):
1. ~~**La cascada de paleta**~~ — **✅ FIRMADA Y EJECUTADA 2026-08-24 como
firma B′**: era la pregunta hermana de §12.9 y se resolvió con la misma
herramienta, `:where()`. Todo (doctrina, contrato, emisor, guarda, ledger)
está en §«FIRMA B′ EJECUTADA» de aquí arriba. **No queda nada que firmar.**
2. ~~**El componente COMPUESTO contra la receta que lo compone**~~ — **✅
FIRMADA Y EJECUTADA 2026-08-24**: el bloque de acción de `form.css` apunta
ya sólo a los dos botones del array de AutoFields, `submit`/`reset` ceden
todo su cromo al `Button` que componen (0 diffs medidos) y mueren sus
cuatro forwards `_palette-*`; las 23 públicas se quedan, re-adjudicadas
como rama que la demo no monta. `gradient-picker` (36) y `emoji-picker` (3)
**ya estaban ejecutados** (2026-08-20 y 2026-08-23, verificado el
2026-08-24: hoy ninguno adjudica una sola clave por supersesión del
compuesto). **Salida barata ya probada**: `toolbar` cede el cromo al
`Button` y no pierde ni una clave, porque su receta no escribe la regla.
**Se ENSANCHA a la TINTA (verificado 2026-08-24)**: la misma firma §5.2 del
harness cubre ya el `color` de `code`. Medido por CDP
(`getMatchedStylesForNode`): sobre `[data-code]` **sólo DOS reglas declaran
`color`** — `[data-code]` (0,1,0) y `[data-uix-docs] code` (0,1,1,
`web/routes/uix/uix.css:792`, `var(--uix-text)` = `rgb(24,24,27)`) — y gana
el harness. Con `data-color='risk'` estampado, `--_code-palette-text` y
`--_code-color` SÍ se mueven, pero `color` no: la cadena del componente
funciona y la pisa la del harness al final. **No es entrada nueva**: es el
mismo expediente que el ledger ya cita para `code.radius`
(`theming-sentinel-exceptions.ts:665`) y `code-block.pre-padding` (`:676`),
y que reaparece por tercera vez en `prose.code-bg` / `code-radius` /
`code-font-family` (`:862-865`). Lo que cambia es el ALCANCE de la firma:
de radio / padding / tipografía **a incluir también la tinta**.
3. ~~**`file-upload` barre DESCENDIENTES**~~ — **✅ FIRMADA Y EJECUTADA
2026-08-24**: la mitad descendiente pasa a cinco selectores por parte propia
(el morfo declara `data-disabled` en 6 nodos y los 6 llevan su attr de
parte, así que el barrido era redundante hacia dentro). Única ocurrencia en
**171** recetas, no 162. `file-upload` e `image-picker` con diff CERO;
`chat-composer` recupera 6 valores —el textarea salía a **0.4³ = 0.064** de
alpha en el tema POR DEFECTO, 925 px— y su entrada del ledger se RETIRA.
Detalle en §8 del PLAN.
4. ~~**Las cuatro claves de `avatar` fuera de gramática**~~ — **✅ FIRMADA Y
EJECUTADA 2026-08-25** (opción A-c): las cuatro **no eran la misma cosa**,
y la medida las separó en tres tratos. **UNA se renombra** —
`badge-color-custom-contrast` → **`badge-fg-custom-contrast`**, la única
tinta de verdad (alimenta `color:`) y alcanzable desde `:root` (medido:
`white` → `rgb(4,5,6)`); es la salida literal del clasificador firmado, que
deja el calificador contextual DETRÁS de la ranura como en el precedente
`scrim-color-on-dark` → `scrim-fg-over-dark`. **DOS se retiran del
contrato** — `ring-color-custom` y `badge-color-custom` son **canal de
valor**: la puerta `data-*-custom` y la escritura inline nacen de la MISMA
expresión del envoltorio, así que no existe ni puede existir instancia en
la que un tema les gane. Pasan a privadas `--_avatar-{ring,badge}-color-custom`
escritas por el envoltorio y leídas con `var(…, currentColor)` — la forma
que `background`, `carousel`, `checkbox-group`, `file-upload` y
`gradient-builder` ya usan (5 de 5 privadas de envoltorio NO se declaran en
la receta), y la adjudicación que `tabs` hizo con su indicador. **UNA queda
EXENTA** — `group-carve-color` alimenta `box-shadow`: es la GEOMETRÍA del
carve, no tinta, y `group-carve-fg` sería gramática correcta con semántica
peor. Contrato público **88 → 86** (medido en vivo: la pestaña se llama ya
«Tokens 86»), censo `--names` **4 desviadas → 0 en TODO el catálogo**
(4556 claves, exentas 13 → 14). Sonda §7.4: **0 diffs sobre 576 valores**,
y el píxel de los nodos con puerta idéntico (anillo ámbar
`rgb(245,158,11)`, insignia violeta `rgb(124,58,237)`, tinta `white`),
incluida la «instancia imposible». **0 píxeles**, como firmaba la opción.
Abrió dos deudas de INSTRUMENTO, las dos en next-features §13: el codemod
de nombres **aborta sobre `avatar`** (`blockRange()` no lee la IIFE, la
misma ceguera que `f68bac4a6` arregló en censo y centinela), y el centinela
**elige el valor de sonda por el NOMBRE** (`fg$`, sin `-fg-`), así que las
cinco claves del catálogo con `fg` MEDIAL reciben `1234px`. Detalle en §8
del PLAN.
5. ~~**`tags-input`** sella `aria-selected='true'` como LITERAL en cada
etiqueta~~ — **✅ FIRMADA Y EJECUTADA 2026-08-25** (opción B-a2): el morfo
ata `aria-selected` al estado que soma YA deriva de `highlightedIndex`
(`v.literal('true')` → `v.stateRef('active')`), una línea. Era el **único
`aria-selected` incondicional del catálogo**. El árbol AX pasa de **dos
`option` con `selected: true` dentro de dos `listbox` con
`multiselectable: false`** a **cero en reposo y exactamente uno navegando**
(medido por CDP). `--tags-input-item-fg` **revive** —el centinela sube a
**71/91** y su entrada del ledger se RETIRA— y la etiqueta **recupera la
afordancia de hover**: reposo y hover eran byte a byte IDÉNTICOS (media RGB
212,10 en los dos) y ahora son 224,58 → 212,10. Diff de sonda: **14
valores de 2.504, todos `background-image` de las dos etiquetas en los
siete pases sin puntero** — el velo de estado saliendo del reposo, y nada
más. Detalle en §8 del PLAN. Lo que NO arregla: la tinta de tono de la
etiqueta activa.
**Enmienda del 2026-08-25**: esa tinta ya no está abierta en §13 sino en
**next-features §12**, como cuarta ocurrencia de §12.5 y con su decisión ya
redactada (`docs/next-features.md:516`) — es de la capa del SISTEMA, no del
componente. Y las dos cifras de esta entrada estaban mal: la especificidad
es **(0,4,0)**, no (0,3,0), y el ganador no es el selector de
`data-highlighted` sino **el BLOQUE** `archetypes.css:245-257`, cuyos dos
selectores —`data-highlighted` y `aria-selected`— casan LOS DOS la
etiqueta activa desde esta misma firma; retirar uno no mueve nada.
Los dos `listbox` anidados que esta entrada describe **ya no existen**: la
firma «el árbol ARIA de `tags-input` habla UN patrón» (2026-08-25, F-1 de
la ficha de componente) los sustituyó por el layout grid del `apg`
—`grid > row > gridcell`, el campo un textbox en su celda— sin mover un
píxel: **0 diffs sobre 2496 valores computados** en los 8 pases de la
sonda, y ningún CSS del ecosistema selecciona por `role`. No es theming y
no toca el ledger de claves; el centinela no se re-corrió en esa firma
(su script está en obras en otra sesión), pero no hay valor computado que
se haya movido.
6. ~~**El chip de añadir reacción de `chat-message`** lo pinta `popover.css`:
los chips de reacción son píldoras redondas y el de añadir un cuadrado
gris.~~ — **✅ FIRMADA Y EJECUTADA 2026-08-25** (opción b, «la ley del
suelo»): el arreglo NO fue de `chat-message` sino del sobre de
`popover.css`, que se autodescribe como _«a baseline button envelope»_ y
estaba a **(0,2,0)** con su `:hover` a **(0,5,0)**. Baja a
`:where(...)` — (0,0,0) — reposo y hover. **Tercera aplicación de la ley**
de §12.9 y de B′/§53, y la primera que cae en una receta escrita a mano.
**76 instancias en 16 rutas, cuatro estados**: 69 no mueven un valor de
receta (los 64 botones desnudos del sitio de docs, `gradient-picker`, la
demo `data-perm-step` y los tres field-trigger excluidos) y **7 mueven, las
7 hacia lo que su propia receta declara** — el chip vuelve a ser **píldora**
(34×26, radio 9999, 12px), `chronos` recupera su `plain`, `calendar` pasa a
ghost y **deja de ser no determinista** (reproducido antes: 2 de 6 cargas en
14px en vez de 16px; después: 8 cargas en dos órdenes, el mismo píxel),
`emoji-picker`, `palabras` y `natural-time-picker` recuperan lo suyo. **0
movimientos** en 167 nodos de referencia. Centinela: `natural-time-picker`
**44/62 → 48/62**; ninguna entrada del ledger salió STALE (ninguna estaba
adjudicada por este sobre). Cierra de paso el empate latente de
`chat-message.css:462`. Guarda por mutación en
`active-eidos-config.test.ts` (§55, 6 mutaciones, 6 mordidas). Doctrina en
`theming/changelog.md` §55; lo que queda abierto —los dos estados a (0,3,0)
y los pines redundantes— en `next-features.md` §13.
**CON ESTO LA LISTA QUEDA VACÍA: no espera NINGUNA firma tuya en este eje.**
Los seis puntos están ejecutados. Lo que siga tiene que abrirse como entrada
nueva, no heredarse de aquí.
### Lo último que se hizo (2026-08-24 noche): §12.9 FIRMADA
**El plano de profundidad es el SUELO, a especificidad cero.** La Decisión 8
daba por bueno el empate a `(0,1,0)` porque «las recetas cargan después de la
fundación y ganan por orden de cascada» — y esa premisa era FALSA: la fundación
se inyecta en runtime y las recetas llegan como chunks de Vite, así que ganaba
quien cargara el último, medido AL REVÉS en dev que en producción. Una moneda al
aire, no una decisión.
La regla de apariencia del plano se emite en `:where(...)`: la receta gana donde
el componente HABLA, el plano pinta lo que CALLA. **El `frost` NO baja** — es un
opt-in explícito por elemento y debe ganarle al fondo del componente.
**Restauró**: los seis tonos de `toast` (4 de 4 distintos donde antes 3 de 3
eran idénticos, y la franja de acento de 0,67 px de gris a 3 px del color de la
intención) · las tres variantes de `tooltip` · 17 claves que salieron STALE
solas del ledger. **Movió**: 16 de 19 adoptantes con diff CERO sobre ~49.000
valores; los tres restantes son la firma trabajando, incluido el `line-height`
que la receta de `link-preview` tenía escrito como pendiente de esta firma.
⚠ **Para medir el plano**: la sonda compartida mide UN nodo en los portalados y
nunca abre la superficie. El «antes» se reproduce EN RUNTIME inyectando la regla
vieja a `(0,1,0)` (`scripts/__plane-open.mjs`), nunca revirtiendo el fichero.
---
> **LO ÚLTIMO (2026-08-24): DOCE componentes en paralelo, con SUPERVISIÓN.**
> `chat-log` 77 → **100 %** (`fd8f01bac`) · `chat-composer` 79 → **100 %**
> (`71a097ddc`) · `rating-group` 80 → **100 %** (`f6a618328`) · `toggle`
> 81 → **100 %** (`edc019515` + `007c2af52`) · `select` 83 → **92 %**
> (`cc010f79a`) · `avatar` 75 → **90 %** (`f68bac4a6`) · `calendar` 84 → **89 %**
> (`509d1144f`) · `file-upload` 76 → **83 %** (`0c06d42aa`) · `editable`
> 75 → **82 %** (`2117d7422`) · `switch` 76 → **81 %** (`bbdc77c07`) ·
> `radio-group` 77 → **81 %** (`f5d252443`) · `card` 78 → **78 % CERRADO**
> (`f3189a05a`). Global **68 %**.
>
> **Los doce se supervisaron uno a uno, y la supervisión encontró cosas.**
> Refutación estática de los doce commits (cero líneas ajenas en el generado),
> centinelas re-corridos, y cinco hallazgos propios que están abajo. **El
> protocolo §7.7 no es un adorno: cuatro de los cinco salieron de él.**
>
> **Lo que enseñó esta tanda, en una línea cada una:**
> el ÍNDICE DE GIT ES COMPARTIDO y verificar el árbol indexado NO BASTA ·
> un árbol rancio hace MENTIR al instrumento sin tocar un commit ·
> la cascada de paleta se lleva TAMBIÉN el tono por defecto ·
> un componente puede BARRER DESCENDIENTES y comerse el token de sus huéspedes ·
> y la sonda y el guard MUEREN con `--import tsx/esm`.
> **HISTÓRICO (2026-08-23, tarde): SIETE componentes.** `navigation-menu`
> 44 → **100 %** (`85988785d`) · `waveform` 55 → **86 %** (`11b92ded6`) ·
> `color-field` 56 → **93 %** (`3c6742b9c`) · `background` 57 → **95 %**
> (`9bdc5a5d5`) · `s-text-virtual-list` 58 → **70 %** (`1f785b6f2`) · `badge`
> 59 → **75 %** (`271fcd4df`) · `cropper` 60 → **94 %** (`f03ee5af4`).
> Global **66 %**. Los siete con diff 0 y su guard limpio.
>
> **Las cinco cosas que enseñaron, en una línea cada una** (detalle abajo):
> el instrumento tiene TRES caras del mismo punto ciego · un guard puede
> CERRAR lo que mide y montar MÁS puede medir MENOS · subir un privado al
> contrato lo somete a reglas nuevas · lo que se escribe INLINE nunca es tema ·
> y la mitad del trabajo que queda es ADJUDICAR, no acuñar.
**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) y el **2026-08-23 entraron
DIECISÉIS** (tabla abajo).** Alcance global **65 %** (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). Sin contrato **24**; **al 100 % 25**; alcance <20 % **12**. Últimos: `chart` 9 % → **89 %**
y `result` 14 % → **90 %**, los dos con `global` a CERO tras la COSTURA. El
censo cuenta 54 knobs menos: los literales ANOTADOS salen del ratio (clase
`exception`). Y `prose` 1 % → **83 %**.
**Desde el 2026-08-23 (tarde) NO queda ningún rojo del bloque viejo** —
`drawer` (24), `metrics` (18), `color-picker` (25) y **`float-panel` (12)**
medidos uno a uno y adjudicados por escrito; el guard R-5.4 pasa en todos. Los
tres hallazgos reales que salieron —el velo del cajón que no pinta, las tallas
del cajón que soma escribe inline y el eje `size` del picker que muere en el
portal— están en §13 y esperan firma.
⚠ **Esta frase estuvo MAL un día**: se escribió el 23 por la mañana dando por
cerrado el bloque viejo cuando `float-panel` seguía con sus doce rojos sin
medir, escritos como «lo que queda» en su propio commit del 22. Lo destapó el
barrido del centinela sobre TODO el ledger (`REVIEW-theming-2026-08-23.md`).
**Un componente cuyo commit dice «lo rojo que queda» NO está cerrado**, y la
única forma de saberlo es correr el guard sobre el ledger entero, no sobre los
que tocas.
## LA COLA A ESTÁ CERRADA (2026-08-24)
`scroll-area` 94 → **100 %** · `slider` 97 → **100 %** · `tabs` 96 → **97 %
CERRADO** · `toast` 98 → **100 %**. Global **68 %**, componentes al 100 %
**45**.
**Los cuatro cerraron SIN ACUÑAR UNA SOLA CLAVE**, y eso es el resultado, no un
atajo: a los cuatro les quedaba un literal, y en los cuatro era identidad
(`opacity: 1` que deshace el `0` del estado oculto · el `100%` de un raíl cuyo
knob real es su suelo `min-inline-size` · el `width: 100%` de `fitted`, que ES
«la lista llena el eje» · el `opacity: 1` del polo en reposo de un par
abierto/cerrado). Firmar un literal de identidad y cerrar es una respuesta tan
válida como acuñar.
### Las dos leyes que dejó
1. **⚠⚠ ANTES DE RETIRAR UNA CLAVE, MIRA QUIÉN LA LEE** — ya es el paso
**8.bis de §7.3** (`868bd235b`). `scroll-area.auto-max-block` leía muerta en
su propia demo y la consumen **cinco recetas ajenas** (`combobox`,
`command`, `gradient-picker`, `select`, `palabras`);
`slider.min-block-size` la fija `media-player` para su volumen vertical.
Retirarlas habría roto seis componentes **sin que un solo guard se pusiera
rojo**, porque ninguno mide el catálogo entero. Leer muerto en SU demo no es
leer muerto en el catálogo.
2. **El indicador de un tablist es CANAL DE VALOR, no superficie de tema.** La
ficha de `tabs` proponía acuñar `indicator-width`/`-height` sobre las vars
que `MeasuredIndicator` escribe en cada movimiento: un público encima sería
un knob que un tema puede FIJAR, y fijarlo rompe el deslizamiento. Rechazado
por escrito, con el precedente de `navigation-menu`.
### ⚠⚠ El hallazgo con más coste del eje: §12.9 se lleva la tarjeta de `toast`
**Un toast `risk` y uno `fulfill` visten la misma tarjeta gris.** Re-medido en
la supervisión sobre la demo real con transiciones y animaciones congeladas:
`risk`, `fulfill` y `affirm` computan **idénticos** (fondo, borde, franja de
acento de 0,67 px y sombra). La tarjeta lleva `data-intent` **y**
`data-depth='overlay'`; escribir `--toast-risk-surface` en `:root` no la mueve
y `--depth-overlay-border` la repinta. **La intención llega al DOM y muere en
la cascada** — 21 claves.
Es la primera vez que §12.9 tiene coste de PRODUCTO y no sólo de contrato, así
que sube el precio de dejarla sin firmar. ⚠ Al medirlo, el tono viaja en
**`data-intent`**, no en `data-color`: una sonda que lea `data-color` lo
encuentra `null` y diagnostica «el tono no llega», que es un defecto DISTINTO.
### Qué queda del eje
- **Cola A: nada.** Los 16 que nunca habían pasado, cerrados o adjudicados.
- **Deuda B: tres**, cada uno por su razón — `audio-player` (sin README de
eidos donde escribir la sección), `palabras` y `proof-of-human` (carriles
propios).
- **Pestañas `Tokens`: ninguna pendiente.** Las 15 entregadas, y de propina el
contador de la pestaña pasa a DERIVARSE del contrato en las 105 demos
(`9fc38b19a` + `c88b2f861`): tres mentían (`carousel` 30/27, `drag-drop`
10/9, `link-preview` 33/35).
- ✅ **§12.9 FIRMADA Y EJECUTADA (2026-08-24)** — el plano es el SUELO,
`:where()`, especificidad cero: la receta gana donde el componente HABLA, en
cualquier orden de carga, y el plano sigue pintando lo que CALLA. El `frost`
NO baja (opt-in explícito por elemento). Doctrina en `changelog.md` §29,
emisor en `render-css.ts`, guarda en `active-eidos-config.test.ts`.
**Medido**: de 19 adoptantes, **16 con diff CERO** sobre ~49.000 valores; los
tres que se mueven son la firma haciendo su trabajo (`toast` recupera sus
seis tonos y una franja de acento de 3 px en vez de 0,67 px de gris;
`tooltip` sus tres variantes; `popover`/`tooltip`/`link-preview` el
`line-height` que sus recetas piden — la decisión de píxel que la receta de
`link-preview` tenía escrita como pendiente de esta firma). 17 claves
salieron STALE y se retiraron del ledger.
- **Esperan tu firma**: la cascada de
paleta (que se lleva también el tono por defecto) · el componente compuesto
contra la receta que lo compone (con la salida barata ya probada en
`toolbar`) · el barrido de descendientes de `file-upload` · las cuatro claves
de `avatar` fuera de gramática · el `aria-selected` literal de `tags-input`
· el chip de reacción de `chat-message`.
## LO QUE DEJÓ LA SEGUNDA TANDA DEL 2026-08-24 (ocho componentes)
Ocho más, por cuatro sesiones en paralelo, **con la ley del índice compartido
ya en el brief** — y esta vez ningún commit se llevó trabajo ajeno. Global
**68 %**, componentes al 100 % **36 → 42**.
| componente | antes → después | lo que enseñó |
| --- | --- | --- |
| `chat-message` | 84 → **100 %** | el guard leía 45/75 y las TREINTA muertas eran ejes que la demo no enciende |
| `chat-typing` | 89 → **100 %** | sus puntos son `> span` desnudos y la demo arranca en `idle`: 3 nodos de 8 |
| `fab` | 88 → **100 %** | el centinela **se auto-cancela**: un lift de 1234 px se lleva el nodo de debajo del cursor |
| `image-adjustments` | 67 → **100 %** | **medir de MÁS midió menos**: el pase de hover recorre 30 nodos como mucho |
| `separator` | 67 → **100 %** | sin acuñar UNA sola clave: sus dos `100%` son identidad |
| `collapsible` | 90 → **100 %** | el que ANIMA no tiene un knob de motion: su firma es `emerge-expand`/`-collapse` |
| `toolbar` | 91 → **94 % CERRADO** | **ceder el cromo al compuesto SALE GRATIS** |
| `tags-input` | 88 → **90 % CERRADO** | su tinta de etiqueta **no ha pintado nunca** (`aria-selected` literal en el morfo) |
**Dos techos, y los dos son doctrina**: los 4 privados de `tags-input` son los
cuatro el puente THM-2, y los 2 de `toolbar` son la identidad de la variante
(una barra `ghost` ES la que no tiene relleno ni sombra).
### El hallazgo que más vale de la tanda
**Ceder el cromo al componente compuesto SALE GRATIS.** `toolbar` deja que
`[data-button]` gane —misma especificidad, emitido después— y **no pierde ni
una clave**, porque su receta ya no escribe reglas sobre el botón (A-112).
Medido ANTES de tocar nada: 51/54, y ninguna silenciosa era del botón. Es el
**reverso exacto de `form`**, que conserva 23 declaraciones muertas por la
misma causa. La firma pendiente de §13 («¿manda el compuesto o la receta que lo
compone?») tiene por tanto una salida barata y ya probada: no escribir la regla.
### Lo que la supervisión arregló
- **La sonda NO congelaba nada** (`10832bef4`). El gate del protocolo es «diff
de computed CERO» y quien lo mide leía transiciones a medias y animaciones en
bucle donde pillaran. `chat-typing` dio **18 diffs de `opacity` corriendo la
sonda dos veces sobre código idéntico**; con la congelación, **0 sobre 1.568
valores**. ⚠ El GUARD sigue congelando sólo `transition`: cambiarlo obliga a
re-verificar el ledger entero (§13).
- **164 fichas regeneradas, clasificadas una a una** (`5c8e707a0`): 155 sólo la
fecha, 7 mejoraban, **2 borraban prosa medida** — cosidas. El generador pisa
lo escrito a mano y eso ya está en §13 con su arreglo.
- **Refutado un susto**: una sesión avisó de que había revertido fichas ajenas y
de que cuatro veredictos §5 podían estar vacíos en HEAD. Comprobados los ocho:
**intactos, de 27 a 81 líneas cada uno**.
### Defectos reales anotados, NO corregidos (mueven píxel o tocan morfo)
- **`chat-message`**: el chip de añadir reacción lleva `data-popover-trigger` y
`popover.css` (0,2,0) le pinta fondo, borde, radio, tinta, altura, padding y
tamaño de letra. **Se ve en la captura**: los chips de reacción son píldoras
redondas y el de añadir es un cuadrado gris. Espera decisión de diseño.
- **`tags-input`**: el morfo sella `aria-selected='true'` como LITERAL en cada
etiqueta, así que `archetypes.css` (0,4,0) le fija tinta y velo de estado EN
REPOSO y su token nunca ha pintado. Tiene cara de defecto de a11y (un lector
anuncia todas las etiquetas como seleccionadas). Toca morfo ⇒ §12.
### Cola A: CERRADA (2026-08-24)
Los cuatro, por dos sesiones en paralelo: `scroll-area` 94 → **100 %**
(`3282ef978`) · `slider` 97 → **100 %** (`4d3ca2fd8`) · `tabs` 96 → **97 %
CERRADO** (`300153f9f`) · `toast` 98 → **100 %** (`b8eccc1c4`). **Los cuatro
cerraron sin acuñar UNA sola clave**: sus contratos ya estaban enteros y lo que
quedaba fuera eran tres literales-identidad (un rail al 100 %, una barra al
`opacity: 1`, la lista `fitted` al 100 %, la tarjeta al `opacity: 1`) y, en
`tabs`, el canal de VALOR de soma. Cuando el número no se mueve porque no debe,
eso ES el resultado.
**Lo que dejaron los dos últimos, en una línea cada uno:**
- **`tabs`** — la mitad de un contrato puede vivir en las VARIANTES (40/79 →
70/79 barriendo `data-variant` y usando las dos URL que el propio demo
ofrece), y su indicador es **canal de valor**: `--indicator-{x,y,w,h}` los
escribe `MeasuredIndicator` en cada movimiento, así que un público encima
sería un knob que un tema puede fijar — y fijarlo rompe el deslizamiento.
- **`toast`** — **§12.9 en una tarjeta PORTALADA se lleva la TARJETA ENTERA**,
21 claves, y con coste de PRODUCTO: un toast `risk` y uno `fulfill` visten la
misma tarjeta gris, y la franja de acento no se ve nunca en reposo (el plano
declara el ATAJO `border`, que pisa el `border-inline-start`). Matiz para la
firma: los seis `{tono}-accent` SÍ alcanzan bajo `[data-loading]`, donde el
`@keyframes` del pulso gana a toda declaración normal — **la misma clave
miente en reposo y dice la verdad en animación**. Y una del instrumento:
**poner `openMarker` EMPEORA la medición** cuando la superficie se
auto-descarta (tres corridas discrepando); sin marcador el guard dispara una
tarjeta nueva antes de cada token y da 51/85 tres veces seguidas.
De la deuda B siguen tres: `audio-player` (sin README de eidos donde escribir
la sección), `palabras` y `proof-of-human` (carriles propios). Más las pestañas
`Tokens` que queden pendientes, la deuda hermana que destapó saldar la B.
## LO QUE DEJÓ LA SESIÓN DEL 2026-08-24 (doce componentes + supervisión)
Doce componentes cerrados por cinco sesiones en paralelo, cada uno con su
commit aislado, y **una pasada de supervisión sobre los doce** (refutación
estática de cada commit + centinela re-corrido + medición propia de todo claim
que no fuese trivial). Cinco componentes llegan al **100 %**; `card` cierra en
**78 %** porque ése ES su techo, y así está escrito en su ficha.
### Los cinco hallazgos de la SUPERVISIÓN (no de la ejecución)
1. **⚠⚠ EL ÍNDICE DE GIT ES COMPARTIDO** (`5be7dabf4`). Hay un solo
`.git/index` para todas las sesiones. Construir el índice desde HEAD y
comprobarlo con `git diff --cached` protege del árbol ajeno, **no de la
ventana entre mirar y confirmar**. Costó `edc019515` (salió con tres
fragmentos de `rating-group` y sin la mitad de los suyos), su reparación
`007c2af52`, y **una coma de más que dejó `theming-sentinel-exceptions.ts`
SIN PARSEAR durante cuatro commits de HEAD** (`88a208644`). El guard no
quedaba en rojo: quedaba MUERTO, y sólo se veía en checkout limpio.
**Regla nueva: verificación y `git commit` en la MISMA invocación de shell.**
2. **Un árbol de trabajo rancio hace MENTIR al instrumento sin tocar un
commit.** El guard de `card` daba `0/98` —la firma del instrumento ciego—
por un `theming-sentinel.ts` sucio con el bloque de `chat-log` DUPLICADO.
Desde copia limpia de HEAD: `49/98`, verde. **Antes de creer un `0/N`,
comprueba que el instrumento coincide con HEAD.**
3. **La cascada de paleta se lleva TAMBIÉN el tono por defecto** (`b7f5a7d75`).
El §13 decía que las `{c}-primary-*` salvan a la instancia sin `data-color`;
donde soma trata el color como valor RESUELTO lo estampa siempre y `neutral`
cae con los otros once. Medido en `switch` y `editable`.
4. **El canal inline filtrado al contrato son CUATRO claves, no dos**
(`009af3300` + `8a4bb8e0e`). Los dos de `avatar` parecían fuera porque su
bloque de `base.ts` es una IIFE que el censo no supo leer hasta `f68bac4a6`.
Re-medidos: ni el fondo del badge ni el `box-shadow` del anillo se mueven
desde `:root`, y **no puede existir instancia alcanzable** — la puerta
`data-*-custom` y la escritura inline viajan JUNTAS.
5. **La sonda y el guard MUEREN con `--import tsx/esm`** (`b4e4fe1fe`).
`ReferenceError: __name is not defined` dentro de `page.evaluate`: el
ayudante `keepNames` de esbuild no existe en la página. Van con `node` a
secas. El error no nombra ni a tsx ni a la sonda.
### Lo que enseñó cada componente
| componente | antes → después | lo que enseñó |
| --- | --- | --- |
| `chat-log` | 77 → **100 %** | el instrumento daba 15/31 y las DIECISÉIS «muertas» estaban vivas |
| `chat-composer` | 79 → **100 %** | **`file-upload` BARRE DESCENDIENTES** y se come el token del huésped |
| `rating-group` | 80 → **100 %** | una escala privada sube al contrato; el guard no fotografía `transform` |
| `toggle` | 81 → **100 %** | el guard sólo sabía barrer UN eje: 36/126 → 70/126 sin adjudicar nada |
| `select` | 83 → 92 % | el portal parte el instrumento: el estado que abre TAPA los de reposo |
| `avatar` | 75 → 90 % | su contrato era INVISIBLE para los dos lectores (una IIFE en `base.ts`) |
| `calendar` | 84 → 89 % | una entrada de receta de FAMILIA no se juzga en la ruta de UN componente |
| `file-upload` | 76 → 83 % | una clave que no pintaba en NINGUNO de sus tres botones — retirada |
| `editable` | 75 → 82 % | el guard CERRABA la superficie que medía (sale del modo con un `blur`) |
| `switch` | 76 → 81 % | su tono NEUTRAL cae con los otros once |
| `radio-group` | 77 → 81 % | su punto NUNCA ha pintado su escala (el SVG escribe `1em` inline) |
| `card` | 78 → **78 % CERRADO** | el número cerrado ES el resultado: sus 8 privados son puente y conmutador |
### Dos verdades sobre el GATE del «diff cero»
Dos componentes NO cerraron a cero, y los dos lo declararon. **Los dos son
ruido del instrumento, reproducido por la supervisión sobre código idéntico**:
- `calendar`: **28 diffs corriendo la sonda dos veces sin tocar nada**, en los
dos mismos nodos (`month-select` / `year-select`) y las dos mismas
propiedades. Empate de especificidad resuelto por orden de carga de Vite. Su
commit además era SÓLO comentarios, así que no podía mover un píxel.
- `chat-composer`: 2 diffs de `blockSize` (30 ↔ 36 px) en `[data-…-attach]`,
reproducidos en tres corridas del mismo código.
**Un diff no cero no es automáticamente una regresión — pero SÓLO se puede
declarar ruido reproduciéndolo sobre código idéntico.** Los dos lo hicieron.
### Lo que hay que saber antes de seguir
- **`card` y `radio-group` están CERRADOS por debajo del 100 %** y no es
deuda: sus privados son puente THM-2 y conmutadores por variante. Un público
encima mataría el `color=` por instancia.
- **`file-upload` es HOY el único componente con un barrido de descendientes**
(`[data-file-upload] [data-disabled]`, una sola ocurrencia en 162 recetas).
Acotarlo mueve píxel de cualquier huésped → espera firma.
- **El guard no fotografía `transform`** y ya son tres los componentes que lo
sufren (`background`, `rating-group`, `card`). Arreglarlo obliga a
re-verificar el ledger entero, así que se adjudica de uno en uno.
- **La demo de `chat-log` se TOCÓ como instrumento** (montaba tres de sus seis
partes sólo en la prosa, así que once claves no tenían nodo que pintar). Roza
la regla de que las demos quedan fuera del eje: queda dicho, no escondido.
- **LA COLA, con la separación que la primera redacción se dejó** (corregido
2026-08-24 al revisar: decía once y son treinta y cuatro, contados contra el
censo y el registro §8).
**A. Nunca han pasado por el eje — 16.** Sin entrada en §8 y sin README:
`field-langs` 0 % (bloqueado por el mandato Field) · `chronos` 1 % (carril
WIP) · `mockup` 38 % (⚠ sin ruta de demo) · `sidebar` 55 % (eje propio) ·
`image-adjustments` 67 % · `separator` 67 % · `chat-message` 84 % ·
`fab` 88 % · `tags-input` 88 % · `chat-typing` 89 % · `collapsible` 90 % ·
`toolbar` 91 % · `scroll-area` 94 % · `tabs` 96 % · `slider` 97 % ·
`toast` 98 %.
**B. Pasaron por el eje pero les FALTABA el README «Talla y tema» — 18,
quedan 3.** ✅ **Quince saldados el 2026-08-24** (`a6df34c05`), con la tabla
DERIVADA del contrato y del ledger. Quedan `audio-player` (no tiene README de
eidos: no hay dónde escribir la sección), `palabras` y `proof-of-human`
(carriles propios). ⚠ **Y sale una deuda hermana**: ninguno de los quince
tiene pestaña `Tokens` en su demo — el paso 11 del protocolo llegó después de
que cerraran, así que hasta que la tengan **la tabla del README es la
fuente**, y así está escrito en los quince. La lista original era: Su
número está adjudicado y su commit hecho; lo que falta es el artefacto del
paso §7 que se volvió obligatorio después de que cerraran (`e522e04e6`
recuperó 21, éstos quedaron fuera): `proof-of-human` · `palabras` ·
`audio-player` · `drawer` · `radio-cards` · `password-field` · `tag-group` ·
`popover` · `color-picker` · `timeline` · `stepper` · `aura` · `metrics` ·
`float-panel` · `chart` · `accordion` · `gradient-builder` · `nav-tree`.
**No es cola de tokenización: es deuda de documentación**, y se salda con un
barrido derivado del contrato como el que ya se hizo una vez.
Regenera siempre esta lista del censo, nunca de memoria: el criterio es
«alcance < 100 % y sin sección `## Talla y tema`», y el desempate entre A y B
es si tiene entrada en el registro §8.
## LO QUE DEJÓ LA SESIÓN DEL 2026-08-23
**Dieciséis componentes** (11 cosidos + 6 de cola + 2 adjudicados sin tocar
código), tres pasadas de rojos anteriores (`drawer` 24, `metrics` 18,
`color-picker` 25) y **una corrección a la baja**: `context-menu` 90 → 87 %, al
retirarle un token que mentía.
| componente | antes → después | lo que enseñó |
| --- | --- | --- |
| `menubar` | 15 → 88 % | el plano `overlay` GANA donde el componente habla |
| `field` | 73 → 98 % | sus 18 rojos estaban vivos; `::placeholder` sin `placeholder` |
| `onion-menu` | 29 → 84 % | `:global()` en CSS plano ⇒ regla DESCARTADA |
| `button` | 55 → 91 % | la cascada de paleta anula 419 claves del catálogo |
| `callout` | 27 → 79 % | la base óptica deja de copiar las métricas del sistema |
| `empty-state` | 29 → **100 %** | 26/26 sin una sola adjudicación |
| `dropdown-menu` | 57 → 96 % | y la tipografía del gemelo, RETIRADA |
| `checkbox` | 59 → 72 % | un huérfano que el test de huérfanos no puede ver |
| `image` | 60 → 96 % | la página que nunca alcanza `networkidle` |
| `anchor-nav` | 20 → **100 %** | el censo no veía los bordes POR LADO |
| `banner` | 30 → **100 %** | 4/42 → 53/53 al estampar `data-intent` y barrer variantes |
| `date-range-field` | 29 → **100 %** | una clave pública que giraba OTRA cosa |
| `time-range-field` | 29 → **100 %** | el gemelo con el MISMO defecto copiado |
| `tooltip` | 52 → 75 % | el plano le anula las TRES variantes |
| `audio-player` | 37 → 56 % | el instrumento medía CERO nodos |
| cola pequeña | 6 comp. | `section` TAPABA sus propias claves públicas |
**Las cinco leyes que salieron, en una línea cada una:**
1. **El portal es una frontera de cascada** — un privado de la raíz no la cruza
(mató el eje `size` del color-picker).
2. **El plano `overlay` gana donde el componente habla** — y no sólo la
tipografía: en `tooltip` se queda el fondo, el borde y la sombra, y con ellos
las tres variantes.
3. **La cascada de paleta anula los tonos del componente** — el bloque genérico
va el ÚLTIMO y gana por orden: 419 claves en 49 recetas.
4. **Un respaldo escrito a mano envejece contra su token** — `, 1.4` contra un
`--font-line-height-sm` que vale 1.45, en dos componentes.
5. **Donde hay un par, el defecto viene en par** — los dos range-field traían el
mismo nombre equivocado, copiado al duplicar.
**Lo que el instrumento aprendió (todo en `scripts/`, ya commiteado):**
- `openMarker` — no pulsar lo que YA está abierto (onion-menu nace abierto y el
guard lo cerraba: 12 de 20 tokens muertos).
- `sweepAttr` — barrer una variante mientras se mide (banner, tooltip, button).
- El barrido de tono estampa `data-color` **y** `data-intent` (banner).
- `extraNodes` para hooks de CLASE (onion-menu) y para partes de OTRO chasis
(audio-player: 0 → 48 nodos).
- Caída a `load` cuando `networkidle` no llega nunca (image).
- `urls` para un componente sin ruta (surface, audio-player).
- Excepciones por PATRÓN (`SENTINEL_PATTERN_EXCEPTIONS`) para una razón
arquitectónica que afecta a 56 claves de golpe.
- El censo: literal ANOTADO dentro de un privado = ausencia firmada; y los ocho
longhands de borde POR LADO (+30 filas en el catálogo).
⚠ **Sonda y centinela filtran por `data-{c}-*`**: un componente que estiliza
HTML crudo (prose) o cuyas partes no llevan atributo se mide sobre UN nodo y el
gate no significa nada. Los dos aceptan ya un selector extra
(`EXTRA_NODES` en la sonda, `extraNodes` en `COMPONENT_OVERRIDES`).
## LA REVISIÓN DE LOS 26 (2026-08-23) — lo primero que hay que saber
Todo lo ejecutado desde la revisión del 22 pasó por fin su análisis:
[`REVIEW-theming-2026-08-23.md`](./REVIEW-theming-2026-08-23.md). **33
componentes, 5 errores reales, 5 desviaciones, y la §7.7 con 5 hipótesis y 5
refutaciones.** Arreglado todo (`233ef989b` · `bfe7727b0` · `e522e04e6` ·
`2f243e1c0`). Las cuatro lecciones que cambian cómo se trabaja:
1. **Corre el guard sobre el ledger ENTERO, no sobre lo que tocas.** Así salió
que `float-panel` llevaba DOCE rojos sin adjudicar desde el 22 —escritos como
«lo rojo que queda» en su propio commit— mientras el handoff daba el bloque
viejo por cerrado. Un componente cuyo commit dice «lo que queda» NO está
cerrado.
2. **El nombre de un token no es el de la propiedad.** `badge` acuñó
`min-block-size-{k}` donde la ranura es `height`: 24 componentes dimensionan
por `…-height-{k}` y era el único. R-5.3 no guarda el vocabulario
DIMENSIONAL (§13) — mientras no lo haga, **cuenta el nombre en el catálogo
antes de acuñarlo**, también para las dimensiones.
3. **Los pasos 8 y 9 del protocolo (README «Talla y tema» + pestaña `Tokens`) se
saltaron en 22 componentes de una tanda entera** y nadie lo notó hasta
revisar los `git show --stat`. Si no está en el commit, no se hizo.
4. **Antes de comparar contra una base, comprueba que la base MONTA.** El
worktree necesita `server: { fs: { allow: [...] } }` cuando su `node_modules`
es una junction; sin eso Vite responde 403, la página no hidrata y el diff
sale limpio contra la nada.
## LO QUE DEJÓ LA TANDA DE SIETE (2026-08-23) — léelo antes de seguir
**El instrumento tiene TRES caras del mismo punto ciego, y ninguna se detecta
sola.** El filtro es `data-{c}-*`, así que no ve: (1) HTML crudo estilado por
descendencia (`prose`), (2) partes que compone el CONSUMIDOR — los `<a>` desnudos
de un mega-menú (`navigation-menu`: 10 nodos, ninguno era el panel), y (3) partes
de un componente COMPUESTO — el playhead de `waveform` ES el thumb de un Slider
embebido (4 nodos, ninguno era él). Añade una cuarta forma: **las capas OPT-IN**
(`background` medía 2 de 37 tokens; `badge`, 2 nodos de 5 porque dot / icon /
remove nacen apagados). **Cuenta los nodos SIEMPRE, y si el número es pequeño,
sospecha antes de creerte el gate.**
**Un guard puede cerrar lo que mide, y montar más puede medir menos.**
`navigation-menu` abre por `pointerenter`, y el aparcado del puntero en (0,0)
que el guard hace tras el clic es exactamente el gesto que lo cierra: daba
no-determinismo y las dos caras del error a la vez (falso negativo en las filas,
falso POSITIVO en el token que ensancha el panel). Y en `background`, encender
el foco puntual y el paralaje **bajó** la corrida de 17 a 9 tokens, porque los
dos repintan la capa donde se miden los patrones. Nace `prepareWith` (enciende
TODOS los controles que se le den, al contrario que `openWith`), y con él la
regla: **enciende sólo lo que no tapa, y compruébalo corriendo el guard antes y
después de cada control**.
**Subir un privado al contrato lo somete a reglas que en el CSS no se le
aplicaban.** Dos veces la misma tarde: `badge.min-block-size-*` y
`cropper.zoom-btn-font-size` copiaban el primitivo CRUDO que el CSS usaba, y
`recipe-css-contract` exige la coordenada del BUNDLE. La suite lo caza en el
acto — pero sólo DESPUÉS de escribirlo, así que escribe el bundle desde el
principio.
**Lo que el componente escribe INLINE no es tema, nunca.** `s-text-virtual-list`
se queda en 70 % porque su triple tipográfico lo escribe él en cada render desde
props resueltas (y tiene que ser exacto: el canvas que mide el texto lee
`getComputedStyle`). Igual el `--_background-gradient-image` y el rect del
indicador de nav-menu. Un público encima MIENTE.
**La mitad del trabajo que queda es adjudicar, no acuñar.** `color-field` tenía
13 tokens que nunca se habían medido y `background` 19; los dos con contrato
sano. Un componente sin veredicto §5 no es un componente sin tokens: es uno sin
MEDIR. Y hay dos atajos legítimos: la excepción POR PATRÓN cuando la razón es
arquitectónica (las 40 claves de tono de `badge`, la cascada de paleta) y la
clase «sólo el paso en vigor pinta» para toda escala resuelta por atributo.
**Dos trampas de medición que costaron tiempo:**
- **Congela `transition` en TODA medición a mano.** Una propiedad transicionada
devuelve el valor VIEJO si la lees en el acto — pasó con `color` en
`navigation-menu`, y ya había pasado con el cuentagotas del color-picker.
- **Mide el nodo que PINTA, no el primero que casa el selector.** En
`color-field` sospeché del `@media (prefers-reduced-motion)` durante tres
diagnósticos: la causa era que el primer `[data-color-field-segment]` es un
`literal`, que declara `transition: none` de su propia cosecha.
## LO QUE DEJÓ `navigation-menu` (2026-08-23) — el detalle
**Tres cosas que se repetirán en la cola, y cuestan una medición cada una:**
1. **Cuenta los nodos ANTES de creerte el gate — otra vez, y por una causa
nueva.** Las filas de su mega-menú son los `<a>` **desnudos del consumidor**,
que la receta estiliza por DESCENDENCIA
(`[data-navigation-menu-content] :is(a, …)`). Sonda y centinela filtran por
`data-{c}-*`: veían 10 nodos y **ninguno era el panel ni una fila** — 9 de
sus 19 knobs quedaban fuera del diff. Es la clase de `prose` y `onion-menu`,
pero por **composición del consumidor**. Los dos instrumentos aceptan ya el
selector extra para este componente, y el paso `open` de la sonda —que NO
honraba `EXTRA_NODES`— ahora sí.
2. **Un panel de HOVER lo cierra el propio guard.** Abre con `pointerenter` y
`pointerleave` PROGRAMA el cierre; el guard abre con clic y luego aparca el
puntero en (0,0), que es justo el gesto que lo cierra. Daba
**no-determinismo** (dos corridas del mismo código discrepando) y las **dos
caras del error a la vez**: falso negativo en los tokens de la fila y falso
POSITIVO en `content-link-padding-inline`, que leía «vivo» porque ensancha
el panel que sí se medía. Se arregla con `openBy: 'hover'`. **Regla: antes
de creer un «no effect», comprueba que la superficie seguía abierta.**
3. **`color` puede estar EN TRANSICIÓN.** Forzar `data-disabled` y leer en el
acto devuelve el valor VIEJO: tres diagnósticos falsos («ni el inline lo
mueve») hasta congelar. Es la trampa del cuentagotas del color-picker, en
otro componente y sobre otra propiedad. **Toda medición a mano congela
`transition` — el guard ya lo hace, tu script de un solo uso no.**
**Y dos de doctrina:**
- **El catálogo decide el nombre, no la §4.** Aquí falló de CINCO maneras (las
cuatro conocidas más una nueva: un aviso de capa compartida disparado por
**homonimia** — `menu-indicator` es el check de los CheckboxItem, no el
subrayado de esta barra). Contar en `base.ts` resolvió los 19 nombres.
- **Un nombre de parte que existe en OTRO sitio del mismo componente miente.**
Los gemelos llaman `item-*` a la fila del panel; aquí `item` es el `<li>` de
la barra y el `Link` de barra viste el cromo del trigger, así que ni `item-*`
ni `link-*` valen: `content-link-*` nombra el nodo por donde vive.
**Contabilidad**: el censo decide el eje `size` grepeando `data-size` en el CSS,
y el protocolo §7.4 ORDENA retirar esos bloques — así que **todo componente que
aplique bien el protocolo aparece «sin eje `size`»**. Ya le pasa a `sidebar`,
`nav-tree` y ahora a `navigation-menu`. Registrado en §13; no es deuda.
## LO PRIMERO AL ENTRAR (cinco minutos, en este orden)
1. **Comprueba que el suelo no se ha movido**:
```bash
node --import tsx/esm scripts/theming-census.ts # 162 · 5.137 · 2.770 (60 %) · 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í.
### 3.ante LA VÁLVULA DEL CANON ya se lee (2026-08-22)
`recipe-contract` §3: un literal con `/* literal: <razón> */` **en su propia
declaración** es una desviación FIRMADA. `component-audit` la honra desde
siempre; el censo no, porque `strip()` blanquea los comentarios antes de
clasificar — 81 anotaciones contaban como deuda. Ahora caen en la clase
**`exception`, fuera del ratio** como `system`: **54 knobs** en 12 componentes
(prose 21 · proof-of-human 16 · palabras 3 · timeline 2 · result 1 …). Global
58 % → **59 %**.
**Consecuencia práctica al tokenizar**: antes de acuñar un literal, mira si ya
lleva su anotación — si la lleva, el canon ya decidió que ahí no va un token, y
la ficha lo lista ahora en su **§1.4** con la razón escrita. Y si vas a dejar un
literal fuera, ANÓTALO: es la única forma de que no vuelva a contarse como
deuda.
### 3.pre EL PUENTE DE PALETA no es deuda (2026-08-22)
De las **687** declaraciones del catálogo que leen un privado, **130 leen un
`--_{c}-palette-*`**: el puente THM-2 que la capa compartida alimenta por
instancia desde `[data-color]`. **No se acuña**: un público encima dejaría que
un tema lo fijara y matara en silencio el `color=` de cada instancia. Domina la
columna `private` de todo componente con paleta, así que **el techo de ésos no
es el 100 %**. Tres ya adjudicados (`tag-group` 65 %, `stepper` 79 %,
`timeline` 73 %, éste además con el CONMUTADOR por `data-intent`); los demás
heredan la razón: radio-group 10, checkbox 8, file-upload 8, calendar /
editable / los tres grids 7 — pero ojo, esos tienen ADEMÁS `global` y sí les
toca trabajo real.
### 3.quater LA COSTURA (2026-08-22) — lo más importante de la sesión
**El valor es del sistema; el knob es del componente.** Tokenizando `chart` y
`result` di por doctrina que «el texto no recibe tokens del componente» porque
su valor viene del canon, y dejé el CSS leyendo `var(--style-caption-font-size)`
y `var(--color-content-*)` A PELO. Es media doctrina: PLAN §2-A pide que TODO
knob de apariencia lea `--{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: `--accordion-trigger-font-family: var(--style-label-font-family)`
(§Vertebración tipográfica), y D-TH.2 aclara que **sólo los seis PRIMITIVOS**
quedan fuera. Resultado: chart 49 % → **89 %**, result 23 % → **86 %**, y
`global` a CERO en los dos.
**Dos formas, y se eligen midiendo**: por ROL cuando un rol se repite sobre
muchos nodos (chart: 2 roles × 3 ejes = 6 claves para 19 nodos de texto; por
parte habrían sido ~30 con dos valores) y por PARTE cuando cada parte tiene
tipografía propia y ningún rol la cubre (result: 15). Agrupa las filas por valor
ANTES de decidir.
**Y una nota de §13 retirada por equivocada**: escribí que el censo penalizaba
consumir la capa tipográfica. El censo medía bien; faltaba la costura. **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: pendiente de revisar con este molde.
### 3.quinquies `chart` — el instrumento, tres veces ciego
Lo que hay que llevarse de aquí:
- **La ficha pedía 71 claves; ~20 habrían llevado el mismo valor.** Antes de
aceptar una propuesta larga, **agrupa las filas por VALOR**: las 84 de chart
colapsaban a 26. Nombrar por parte lo que es un concepto es la inflación de
`spinner` con otra cara.
- **El texto de un componente no recibe tokens propios**: rol de tamaño de los
estilos con nombre (`caption` / `label`), tinta y peso de las ranuras del
sistema. Sólo se acuña la tinta de lo que NO es texto.
- **El guard ya sabe medir en VARIAS rutas** (`urls` en `COMPONENT_OVERRIDES`):
un token vive si alguna superficie del componente lo sigue. Chart tiene 16;
`picker-shell` ya lleva la del date-picker (su ruta propia es 404).
- **El guard ya lee SVG** (`fill`, `stroke`, `strokeWidth`, `fillOpacity`, `r`,
`rx`…) y **elige el valor centinela por el FINAL del nombre**, no por
subcadena — `slice-stroke-width` recibía un color por contener «stroke».
- Tras cualquier cambio del guard: **re-verificar los 28 componentes con
ledger** (se hizo: cero regresiones, cero STALE). Y ojo, correr doce a la vez
contra el mismo dev server da falsos exit=1 por timeout: repite el que falle
a solas antes de creerte una regresión.
### 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 |
| `proof-of-human` | 95 (15 %) | escena de tono fijo firmada; lo demás son hooks de clase (eje aparte) |
| `prose` | 74 (1 %) | su superficie es la capa tipográfica: mismo caso que chart, un piso más arriba |
| `nav-tree` · `banner` · … | varios | ver el censo, `reach < 20 %` = 12 |
**Cerrados el 2026-08-23 (siete)**: `navigation-menu` 100 % · `background` 95 % ·
`cropper` 94 % · `color-field` 93 % · `badge` 75 % · `s-text-virtual-list` 70 % ·
`waveform` 86 %. **El siguiente por alcance entre los medibles y sin bloqueo es
`emoji-picker` (61 %)**, y luego `meter` / `spin-field` (64 %), `pin-input`
(65 %), `dialog` (67 %, ⚠ overlay: no acuñes tipografía hasta §12.9),
`progress` (69 %), `color-swatch` (70 %), `form` (71 %)… El mayor no bloqueado
sigue siendo `chronos` (1 %, carril WIP de D-TH.3, y «tiempo» es el ÚLTIMO de
D-TH.4).
`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 — y en
`tooltip` se queda además el FONDO, el BORDE y la SOMBRA**, con lo que sus
tres variantes computan lo mismo (medido 2026-08-23: `solid`, `outline` y
`ghost` idénticas). Ya no es una pregunta sobre tipografía: es sobre qué
manda, el plano o la receta. Cuatro componentes han tenido que RETIRAR o
ADJUDICAR claves por esto (menubar, dropdown-menu, context-menu, tooltip;
`combobox` sigue con la suya, registrada).
(`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.**
**Y ya tiene MECANISMO** (menubar, 2026-08-23): el plano no sólo impone
donde el componente calla — **GANA donde el componente habla**. Declara
`font-family` y `line-height` con la misma especificidad (0,1,0) y más tarde
en la cascada, así que dos claves acuñadas sobre declaraciones que la receta
YA tenía salieron muertas en el centinela, mientras `color` y `font-size`
del mismo bloque alcanzaban. Traducción operativa: **hasta que firmes, no
acuñes `font-family` ni `line-height` en ninguna superficie `overlay`** —
miente.
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. **CUATRO canónicos sin demo propia** (404): `picker-shell`, `mockup`,
`surface` y `audio-player`. Sin ruta no hay sonda ni guard, y el guard
**miente en verde-rojo**: sobre una página vacía reportó 0/25 para surface,
incluido un `radius` vivísimo. Los tres medibles se apuntaron a la ruta donde
SÍ se renderizan; `mockup` sigue sin poder medirse — §13.
8. Extender la clase `system` del censo a las **capas compartidas** (viene de
antes de esta sesión).
11. **Dos reglas que el navegador DESCARTA**: `onion-menu.css` y
`timeline.css` escriben `:global(svg)` en un CSS PLANO, donde el selector es
inválido. El glifo del onion computa 14px en vez del 70 % que la regla cree
pintar. Arreglarlo mueve píxel en los dos — §13.
10. **⚠⚠ LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE — 419 claves
públicas** (medido 2026-08-23 en `button`, `badge` y `callout`). El forward
emite un bloque por tono y, AL FINAL, uno genérico `[data-{c}][data-color]`
que resuelve desde el `--palette-*` global; misma especificidad (0,2,0), gana
el último. `--{c}-risk-solid` no mueve nada; `--palette-solid` y
`--color-risk-solid` en el nodo sí. **49 recetas** emiten ese bloque. El
arreglo cabe en el ORDEN DE EMISIÓN y no movería un píxel, pero cambia el
contrato de la jaula del color → §13. **Es la más gorda del eje.**
9. **El eje `size` del `color-picker` muere en el portal** (2026-08-23): la
anchura del panel está cableada al paso `md`
(`--_color-picker-content-width` se declara tres veces y no lo consume
nadie) y el tamaño de las muestras DENTRO del panel también. Un picker
`sm` y uno `lg` abren el mismo panel de 344 px. El arreglo es una escala
por talla en el ámbito del CONTENIDO —que sí lleva su `data-size`—; mueve
píxel en `sm` y `lg` → §13. **Ley general: el portal es una frontera de
cascada; un privado declarado en la raíz no la cruza.**
## 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`](../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:
```ts
// 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`](../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)
```bash
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).
- **Los tokens que SON la transición no se pueden medir congelando** (2026-08-23):
`transition-duration` / `transition-ease` salen muertos SIEMPRE, en cualquier
componente, porque el congelado es el arreglo que hace medible todo lo demás.
Se miden en una pasada aparte, sin congelar (color-picker: 0.12s → 11.5s).
- **Ni `!important` en línea mueve una propiedad EN TRANSICIÓN si lees en el
acto** (2026-08-23): la lectura devuelve el valor viejo y parece un token
imposible de mover. Pasó con el fondo del cuentagotas: tres diagnósticos
falsos hasta congelar.
## 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.
```bash
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`](./PLAN-theming.md) · informe:
[`docs/audit/theming/README.md`](../audit/theming/README.md).
- Auditoría del SISTEMA (fase 1, cerrada): [`theming-audit.md`](../audit/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`](./PLAN-sidebar.md) §3 +
F3 y el bloque `sidebar` de `recipes/base.ts`.

Powered by TurnKey Linux.