*/` **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 ``, 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 `
` 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 `).
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} `) 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:
`` + 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 -- `, 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:
`` 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 ``,
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 # sonda antes/después
node scripts/__theming-diff.ts # el gate: diff VACÍO
npm run theming:sentinel -- # guard R-5.4: ¿alcanza cada token? (ya no es `__`)
node scripts/__shot.ts # 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`.