uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía 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 (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* theming - sentinel - exceptions — the written adjudications R - 5.4 requires .
*
* A public token the sentinel reports as "no effect" is EITHER a lie ( retire
* it ) OR its silence has a measured reason that lives here , one line per
* token : the part is conditional and the demo does not mount it , the state
* needs forcing , the consumer is a pseudo - element or a composed component the
* instrument cannot read , the value sits behind a media query . " Forced →
* reaches " means the adjudication PROVED the token alive by mounting what it
* needs ; an instrument - limit entry names the limit ( they are also registered
* in next - features § 13 ) .
*
* An entry whose token starts moving again is reported as STALE by the guard —
* prune it ( a stale exception is the same debt as a stale allowlist row ) .
*
* All entries below were measured during the F2 - A adversarial review
* ( 2026 - 08 - 21 ) unless dated otherwise .
* /
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const PLANE_SUPERSEDED =
'SUPERSEDED by the depth plane: the part carries data-depth=overlay, and [data-depth=overlay] declares this property at the same specificity (0,1,0) and LATER in the cascade, so the recipe key is bypassed (see next-features 12.9, measured on tooltip, popover and float-panel)' ;
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía 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 (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
export const SENTINEL_EXCEPTIONS : Record < string , Record < string , string > > = {
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos
Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte
receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela
**83/88** con 5 adjudicadas. El default NO se mueve.
## Lo primero: el instrumento no veía el contrato
La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un
helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el
`return {`, un tabulador más adentro. Consecuencia medida:
- el censo la leía como «sin entrada en base.ts» — 84 claves invisibles;
- el centinela **moría** con `no recipe block for avatar`: el componente no se
podía medir, y su gate no significaba nada.
Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo
lo demás de este commit habría sido humo.
## Lo que entra al contrato (6 claves)
`group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`.
Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis
bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde
escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el
mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por
`--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 ·
−16,8 · −22,4 · −33,6 px → 37 px, uno a uno).
## Lo que se retira (2 declaraciones muertas, diff 0 las dos)
- `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta
declaraba su default `99`. **No lo leía nadie** — el tope se aplica con
`data-has-max` + `:nth-child(n + M)` porque una variable no entra en
`:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos
sitios; el `+3` del grupo sigue exactamente donde estaba.
- el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara
`--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo
podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue
computando `rgb(255, 255, 255)`.
## Seis literales firmados (salen del ratio)
Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie
del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo
centrado por la caja flex.
## Lo que se queda privado (el techo real es 90 %)
`--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian
de fuente con la variante (solid · soft · outline) y su valor sale del forward de
paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`.
Un público encima dejaría que un tema los fijara y matara el `color=` de cada
avatar.
## Artefactos
- Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576
valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072).
- Capturas 2× antes/después de las dos demos: **idénticas al byte**.
- Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y
`font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza —
el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo
el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas).
- El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de
`data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en
su propia ruta (21 muertos → 5).
- `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido
`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check`
COMPLETED sin un solo error en `avatar`.
- README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador
con `networkidle` y clic real).
## Lo que NO se arregla aquí (anotado)
- **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática
exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que
renombrarlas cambia el contrato público de tres canales: se lista, el audit
sigue en NEEDS-WORK por esto y sólo por esto.
- **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token
en `:root` y también sobre cada nodo, y para una propiedad personalizada que
la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema
pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo.
- **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE
las seis excepciones de `metrics`, así que se hace con la re-verificación del
ledger entero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
avatar : {
// 2026-08-23. Five steps of scales the measured stage does not wear, all
// forced on the REAL nodes with transitions frozen. Two of them sit on the
// `xxl` step, which the size sweep of the guard does not reach (the same
// limit `metrics` recorded below).
'size-xxl' :
'the size sweep of the guard stops at xl; forced data-size=xxl -> reaches (block-size 96px -> 1234px)' ,
'font-size-xxl' :
'same xxl step, read on the fallback initials (the image carries no font); forced -> reaches (28px -> 37px)' ,
'radius-none' : 'only the step in force paints; forced data-radius=none -> reaches (0px -> 37px)' ,
'ring-width-sm' :
'only the step in force paints (the demo boots ringWidth=md); forced data-ring-width=sm -> reaches (halo 1.5px -> 17px)' ,
'group-overlap-xxl' :
'same xxl step, on AvatarGroup; forced data-size=xxl on the real group -> reaches (margin-inline-start -33.6px -> 37px)'
} ,
uix(editable): temable — 75 % → 82 %, y el guard CERRABA la superficie que medía
Protocolo PLAN-theming §7 sobre `editable`. Censo 75 % → **82 %**, contrato 56
claves, centinela **42/56** con 14 adjudicadas. El default NO se mueve.
## Lo que costó medirlo: dos superficies y una apertura frágil al revés
- **Preview e input comparten celda de rejilla y se excluyen** (`display: none`),
igual que los triggers (Edit en reposo, Save/Cancel editando): una pasada deja
fuera la mitad de la receta. Todo se midió DOS veces.
- **Salir del modo edición es lo que hace un `blur`** —da igual lo que diga
`submitMode`, esa opción sólo decide si el valor se compromete— y el guard
blurea justo después de abrir. Abriendo con clic en el trigger, la superficie
ya estaba cerrada cuando se medía: `submit-fg` leía muerto y mueve
perfectamente abierta (`rgb(255,255,255)` → `rgb(1,2,3)`). Se abre por FOCO,
con la demo en `activationMode=focus`, que es la apertura que el guard no
deshace. Misma ley que `navigation-menu`, con el gesto invertido.
## Lo que entra al contrato (1 clave)
`disabled-fg` — la tinta apagada de TODO el componente: preview, input y los
tres triggers comparten UNA regla, así que el nombre desnudo del catálogo es el
que toca (`label` y `select` ya lo llevan con este mismo valor). Era el único
knob que iba a un primitivo a pelo, y encima con un respaldo escrito a mano
—`var(--color-content-disabled, var(--color-content-muted))`— que estaba MUERTO:
el token sí está declarado, así que el respaldo no podía pintar nunca. Retirado
con él.
## Lo que sale (1 clave huérfana)
`control-border`: ninguna regla lo consumía. El borde en reposo del control es
`transparent` por diseño y el del input es el acento
(`--_editable-palette-border`), así que esa clave pública prometía un knob que no
existe — la deuda INVERSA del eje. Diff 0, y el centinela lo confirmaba muerto.
## Dos literales de identidad firmados
El `fit-content` del preview (es tan ancho como el texto que enseña; la ficha
proponía acuñarlo como `preview-width`, que sería acuñar la definición de la
parte) y el `100 %` del input (llena la celda que comparte con el preview).
## Lo que se queda privado (el techo honesto es 82 %)
Los SIETE knobs por privado son el puente de paleta THM-2
(`--_editable-palette-{solid,border,solid-hover}`), que la capa compartida
alimenta por instancia desde `[data-color]`: un público encima dejaría que un
tema lo fijara y matara el `color=` de cada instancia. Otros cuatro son sistema
transversal (anillo de foco y velo de hover), ya fuera del ratio.
## Las catorce adjudicaciones, en dos razones — y una tercera que casi se cuela
- El par `transition-*` **ES** la transición que el guard congela; medido sin
congelar: `0.12s → 4.321s` y `cubic-bezier(0.4, 0, 0.2, 1) → steps(3)`.
- Las doce claves de tono son la cascada de paleta, por PATRÓN y con el mecanismo
a la vista: el nodo lleva `--palette-solid` de la capa por instancia, el bloque
genérico `[data-editable][data-color]` —emitido EL ÚLTIMO, misma
especificidad— lo lee, y el respaldo que nombra la clave del componente no
llega nunca. Quítale `data-color` a la misma instancia y
`--editable-primary-solid` vuelve a pintar (`rgb(1, 2, 3)`).
- ⚠ **`risk-border` NO cae en ese patrón**: las tres reglas de inválido la leen
DIRECTAMENTE, así que su silencio tiene otra causa —la demo es válida— y
entrada propia: forzando `data-invalid` alcanza en el input Y en el control.
Una excepción por patrón que engulle una clave con vida propia es una
adjudicación falsa.
## Artefactos
- Sonda: **0 diffs** en las dos caras — modo preview (1.696 valores, 7 estados) y
modo edición (2.528 valores, 8 estados).
- Captura 2× del reposo idéntica al byte. La de edición NO es comparable por
bytes: la traza de la demo imprime un TIMESTAMP, así que dos corridas del mismo
código difieren (comprobado antes de creerme el diff). Revisada a ojo.
- `component-audit --only editable`: **PASS** · `eidos-lint` 0 invalid ·
`vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED, 0 errores en
`editable`.
- README «Talla y tema» + pestaña `Tokens` (56 filas, verificada en el navegador
con `networkidle` y clic real).
## Defecto anotado, NO arreglado
El tinte inválido ES la clave de tono (`--editable-risk-border`): dos knobs con
una sola llave — un tema que quiera otro color de error mueve también las
instancias `color="risk"`. El arreglo es una clave de estado por parte
(`invalid-input-border` / `invalid-control-border`, la forma que `switch` acuñó
el mismo día) y es contrato: se lista.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
editable : {
// 2026-08-23. Only two reasons, both measured: the transition pair IS the
// transition the guard freezes, and the twelve tone keys are the palette
// cascade (adjudicated by PATTERN below).
'transition-duration' :
'the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 4.321s on the three transitioned properties of the preview)' ,
'transition-ease' :
'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))' ,
// NOT the palette pattern: this one key is ALSO read straight by the invalid
// rules, so its silence has a second, different cause — the demo is valid.
'risk-border' :
'the palette path is superseded like its eleven siblings, but this key is ALSO the invalid tint, read directly by three rules; forced data-invalid on the real root -> reaches on the input AND on the control (oklch(0.8059 0.1123 59.96) -> rgb(1, 2, 3))'
} ,
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
background : {
// 2026-08-23. Its parts are independent OPT-IN layers and only ONE pattern
// renders at a time, so the default stage moved 3 of 37 tokens. The guard
// now switches the scrim on and sweeps `data-pattern` (18/37); what is
// left is measured here, forcing the attribute on the REAL layer — never
// on a node built by hand.
//
// The mask: the guard does not read `mask-image`, so both read dead while
// being perfectly alive (instrument gap, next-features §13).
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'fade-size' :
'the guard does not snapshot mask-image; measured by hand → reaches (the radial mask changes)' ,
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'fade-at' :
'the guard does not snapshot mask-image; measured by hand → reaches (at 50% 35% → at 11% 50%)' ,
// The ink context: only under `on='dark'` / `on='light'`.
'scrim-fg-over-dark' : "only under on='dark'; forced → reaches (the veil colour changes)" ,
'scrim-fg-over-light' : "only under on='light'; forced → reaches (the veil colour changes)" ,
// Two scales where only the step in force paints (the `section` / `qr-code`
// class): every step measured one by one on the real scrim layer.
'scrim-blur-sm' : 'only the step in force paints; forced data-blur=sm → reaches (4px → 33px)' ,
'scrim-blur-md' : 'only the step in force paints; forced data-blur=md → reaches (8px → 33px)' ,
'scrim-blur-lg' : 'only the step in force paints; forced data-blur=lg → reaches (12px → 33px)' ,
'scrim-blur-xxl' : 'only the step in force paints; forced data-blur=xxl → reaches (24px → 33px)' ,
'scrim-strength-xs' :
'only the step in force paints; forced data-strength=xs → reaches (alpha 0.08 → 0.9)' ,
'scrim-strength-sm' :
'only the step in force paints; forced data-strength=sm → reaches (alpha 0.13 → 0.9)' ,
'scrim-strength-lg' :
'only the step in force paints; forced data-strength=lg → reaches (alpha 0.4 → 0.9)' ,
'scrim-strength-xl' :
'only the step in force paints; forced data-strength=xl → reaches (alpha 0.7 → 0.9)' ,
// A gradient layer EXCLUDES a pattern layer, so the sweep cannot have both.
'gradient-drift-duration' :
'the gradient layer is exclusive with the pattern one the sweep needs; forced data-kind=gradient → reaches (24s → 42s)' ,
// The spotlight is off on the default stage and, once on, it repaints the
// pattern layer it shares a node with — measured on its own pass.
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'spotlight-size' :
'spotlight is off in the demo; forced data-spotlight → reaches (40% 40% → 42px)' ,
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'spotlight-strength' :
'spotlight is off in the demo; forced data-spotlight → reaches (the mix stop moves)' ,
'spotlight-fg' :
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'spotlight is off AND the demo layer carries data-color, where --palette-solid wins by design; forced data-spotlight with no data-color → reaches' ,
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Structural, not conditional: the travel only enters through the
// scroll-linked `background-parallax` keyframes, which need a scroll
// timeline the measured stage does not have.
'parallax-travel' :
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'the demo boots at speed 0, so no layer carries data-parallax; forced data-parallax + speed 1 on the real layer, the scroll-linked translate follows it (14.6 -> 60.5 px at the same scrollY). The guard would not see it anyway: translate / transform are not in its property snapshot (next-features 13)' ,
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// No node at all: the demo never mounts the pause control.
'pause-offset' : 'the pause control is not mounted in the demo (0 nodes on the page)' ,
'pause-z' : 'the pause control is not mounted in the demo (0 nodes on the page)'
} ,
uix(badge): temable — 59 % → 75 %, y subir un privado al contrato lo somete a reglas nuevas
Seis claves nuevas (65 → 71), dos literales firmados, cinco privados
adjudicados. Diff de computed 0 sobre 992 valores en 7 estados.
LA ALTURA DEL CHIP ERA EL HUECO
Badge monta su altura en la escala de CONTROL a propósito —para que `md`
signifique lo mismo lleve o no un control dentro (el ✕ removible es un
IconButton: medido, el mismo badge md daba 30px sin él y 46px con él)— pero lo
hacía desde un privado con cuatro fuentes por talla que leía --control-height-*
A PELO. Eso es una ESCALA, no un conmutador: sube al TSC como
min-block-size-{xs,sm,md,lg} + el resuelto, el molde que sus vecinas
padding-inline / gap / font-size ya tenían.
Y AL ENTRAR EN EL CONTRATO, LA SUITE LO CORRIGIÓ
El valor verbatim del CSS era el primitivo CRUDO (--control-height-{k}) y
recipe-css-contract exige la coordenada del BUNDLE (--size-{k}-control-height,
theming §5). Es la trampa que el handoff nombra —«la propuesta hereda el
incumplimiento del CSS de partida»— y que ya mordió a code-block: SUBIR UN
PRIVADO AL CONTRATO SOMETE SU VALOR A REGLAS QUE EN EL CSS NO SE LE APLICABAN.
Corregido; el diff sigue en 0.
dot-radius ES EL PAR QUE FALTABA
Redondo es el DEFECTO, no la definición: knob.indicator-radius vale
--radius-sm, así que el catálogo ya trata el radio de un marcador como knob.
Los dos 100% del glifo son identidad (el icono ES su ranura), firmados.
LOS CINCO PRIVADOS SON LAS TRES CLASES DE F2-B EN UN SOLO COMPONENTE
--_badge-bg / --_badge-fg son un CONMUTADOR (cambian de fuente con la variante:
track · solid · transparent, y aplanarlos obligaría a duplicar cada regla);
--_badge-fill-finish es el canal que el generador de degradados deriva de la
paleta de ESTA instancia; y --_badge-palette-* son forwards THM-2 que la capa de
color alimenta por instancia — un público encima dejaría que un tema los fijara
y matara el color= de cada chip.
LAS 40 CLAVES DE TONO NO MUEVEN NADA, Y YA SABÍAMOS POR QUÉ
El bloque genérico [data-badge][data-color] del forward de paleta se emite EL
ÚLTIMO y gana por orden a igual especificidad: la pintura sale del --palette-*
global. Es el hallazgo de cascada de paleta medido en button, badge y callout el
mismo día (§13, pendiente de firma) y entra como EXCEPCIÓN POR PATRÓN — una
razón arquitectónica escrita UNA vez, no cuarenta accidentes. Con ella y los dos
pasos de radius adjudicados (forzando data-rounded: 4px → 37px y 10px → 37px),
el guard queda en 29/71 y limpio.
EL INSTRUMENTO NO VEÍA TRES DE SUS CINCO PARTES
Dot, icon y remove son interruptores OPT-IN apagados por defecto: la sonda medía
2 nodos —raíz y etiqueta— y ninguna de las partes que la receta pinta.
Encendidos, 5. Misma clase que las capas opt-in de background.
⚠ component:audit da NEEDS-WORK y es PREEXISTENTE: R-1.5, sin tratamiento de
foco en la receta (grep focus-visible = 0 tanto en HEAD como después). El ✕ trae
el suyo del IconButton que compone; el chip no es focalizable. No se toca aquí:
darle foco mueve píxel y es decisión de diseño.
VERIFICACIÓN
Sonda antes/después 0 diffs (992 valores · 7 estados · 5 nodos con las tres
partes encendidas) · R-5.4 29/71 con el resto adjudicado (dos entradas propias +
una excepción por patrón), cero STALE · censo 75 % · eidos-lint 5 morfo-backed /
14 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite
eidos 439/440 (el rojo es skin-media-player, el conocido).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
badge : {
// 2026-08-23. The demo mounts ONE chip, and its `rounded` axis paints only
// the step in force — measured forcing each on the real chip.
'radius-sm' : 'only the step in force paints; forced data-rounded=sm → reaches (4px → 37px)' ,
'radius-lg' : 'only the step in force paints; forced data-rounded=lg → reaches (10px → 37px)'
} ,
uix(color-swatch): temable — 70 % → 100 %, y el color del chip NO es un knob
Cuatro claves nuevas por una retirada (12 → 14 en el contrato), dos adjudicaciones
medidas. Diff de computed 0 sobre 832 valores en 15 estados y DOS rutas; capturas
2× byte a byte idénticas.
LA §4 PEDÍA UN NOMBRE Y LO QUE FALTABA ERA UNA ESCALA
La ficha marcaba `radius` con ⚠ y tres valores en colisión. No es una colisión: es
el eje `rounded` con cuatro pasos, y la clave que ya estaba en el contrato ERA el
paso `sm` bajo un nombre que no lo dice. Contado en base.ts: siete recetas
escriben `radius-{k}`, y una es `badge` — el gemelo que lee ESTE MISMO
`data-rounded` con el mismo conmutador privado. Así que entra la escala entera
(radius-sm/md/lg/full) más `--_color-swatch-radius`, exactamente la forma que su
propio eje `size` usaba dos líneas más arriba. Dejar `radius` al lado de
`radius-md` habría dejado el paso `sm` llamándose `radius`: un nombre que miente.
`fill` SALE DEL CONTRATO: ES UN CANAL DE VALOR, NO UN KNOB DE TEMA
El wrapper escribe `--color-swatch-fill: {color}` INLINE en cada render desde una
prop obligatoria. Medido sobre un chip real con transiciones congeladas: escribir
el token en :root deja el ::before en rgb(124, 58, 237) — la declaración inline
gana siempre. Un público encima sólo podía mentir. La forma correcta ya estaba
escrita en el guard de la capa: recipe-css-contract sanciona `var(--x, default)`
para las vars que el runtime escribe («el respaldo ES el default»), que es justo
lo que la receta tenía. Por eso el respaldo NO se retira: al salir del contrato
deja de ser código muerto y pasa a ser el default.
⚠ EL CENTINELA DABA `fill` POR VIVO — ES UN FALSO POSITIVO SUYO
El guard escribe el token en :root Y en el `style` inline de cada nodo del
componente, que es exactamente donde vive el canal de valor: lo pisa y lee
«mueve». Es la cara opuesta de drag-drop.preview-z, donde la adjudicación se
había medido sobre un nodo fabricado. Regla: un token que el componente escribe
INLINE no se puede juzgar con el guard — se mide a mano escribiendo sólo en :root.
EL INSTRUMENTO ESTABA CIEGO, DOS VECES
1. color-swatch NO TIENE RUTA DE DEMO (404): es el QUINTO canónico en ese estado,
tras picker-shell, mockup, surface y audio-player. La sonda medía 1 nodo. Se
mide donde de verdad se renderiza — gradient-builder (3 chips en el stage) y
color-picker (el del trigger).
2. El eje `rounded` no lo monta NADIE: ningún consumidor del repo pasa la prop, así
que los tres pasos que no son el default no tenían nodo que pintar. El guard los
barre con sweepAttr: data-rounded, como meter hace con su shape.
LAS DOS ADJUDICACIONES SON EL ANILLO OPT-IN
border-width y border-fg: `bordered` nace apagado y ningún consumidor lo enciende,
así que su ::after no existe en ninguna página. Forzado data-bordered sobre un chip
real, los dos llegan (inset 0 0 0 1px → 1234px; oklch(0.931 0 0) → rgb(1, 2, 3)).
ARTEFACTOS
Sonda antes/después en dos rutas (256 valores × 8 estados en color-picker, 576 × 7
en gradient-builder) → 0 diffs, 0 nodos perdidos · el eje `rounded` medido a mano
forzando el atributo: 4 / 4 / 6 / 10 / 9999 px antes y después · capturas 2×
idénticas byte a byte · R-5.4 12/14 con las dos adjudicadas, exit 0 · censo --only
70 % → 100 % (knobs 10 → 7: los tres border-radius por variante pasan a declarar
el privado) · component:audit PASS · eidos-lint 2 morfo-backed / 8 eidos-only / 0
invalid · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo conocido,
skin-media-player) · npm run check COMPLETED sin errores en ficheros tocados.
LO QUE NO ENTRA Y POR QUÉ
- Paso 9 del protocolo (pestaña Tokens en la demo): no hay demo que tocar.
- docs/audit/theming/README.md: es un agregado global y otra sesión tiene form y
progress en vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo,
de base.ts, generated/base.css, theming-sentinel.ts y el ledger sólo entran mis
hunks (verificado con git diff --cached).
Defectos anotados, no arreglados (mueven documentación ajena a este cambio): la
cabecera del CSS, el docblock del .svelte y types.ts siguen diciendo que la talla
sale de `--icon-size-*`, cuando desde la extracción tiene escala propia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'color-swatch' : {
// 2026-08-23. The inset ring is OPT-IN (`bordered`, off by default) and NO
// consumer in the repo passes it, so the `::after` that draws it exists on
// no page. Forced `data-bordered` on a real chip of the gradient-builder
// stage, transitions frozen: the ring appears and both tokens follow.
'border-width' :
'the `bordered` ring is opt-in and no consumer mounts it; forced data-bordered on the real chip → reaches (inset 0 0 0 1px → 1234px)' ,
'border-fg' :
'same opt-in ring; forced data-bordered on the real chip → reaches (oklch(0.931 0 0) → rgb(1, 2, 3))'
} ,
uix(form): temable — 71 % → 86 %, y 24 claves suyas NO PINTAN por una cascada
Diez claves nuevas (54 → 64), un literal firmado, veinte adjudicaciones medidas
y un hallazgo que espera firma. Diff de computed 0 sobre 832 valores en 7
estados; capturas 2× antes/después.
LAS DIEZ IBAN A LOS PRIMITIVOS A PELO
El resumen de errores (error-summary-{bg,fg,border}) —es el señalizador de nivel
de página y su superficie, su tinta y su borde SON el tono risk—, el acento de
formulario inválido (invalid-accent, un privado con UNA sola fuente, no un
conmutador), las tres superficies del árbol de AutoFields, las dos del widget y
el subrayado a media tinta de los enlaces del resumen (link-underline-fg, el par
del -offset que ya existía).
⚠ R-5.3 CAZÓ MI NOMBRE EN EL ACTO: lo acuñé como link-underline-color y la tinta
es `fg` (theming §6.7 r7, firmado el 20). Renombrado antes de commitear. El
guard de nombres funciona — es el vocabulario DIMENSIONAL el que no cubre (ver
badge, hoy mismo).
⚠⚠ VEINTICUATRO CLAVES DEL CONTRATO NO PINTAN, Y ES UNA CASCADA
[data-form-submit], [data-form-reset] y los dos botones del array llevan también
data-button porque COMPONEN el Button canónico, y [data-button] casa con la
MISMA especificidad (0,1,0) pero carga DESPUÉS: gana button.css. Medido
propiedad a propiedad sobre el nodo real —altura, padding-inline, tamaño y peso
de letra, interlineado, hueco, radio, grosor de borde, el par de transición y la
opacidad deshabilitada—: NINGUNA se mueve.
Es el defecto que gradient-picker documentó (36 de sus 45 claves) y que
emoji-picker encontró hoy en su trigger. Allí la respuesta fue RETIRAR las
declaraciones muertas; aquí son 24 claves públicas, y eso es una decisión del
autor, no del que ejecuta. Quedan adjudicadas POR PATRÓN (una razón
arquitectónica escrita una vez) y anotadas en §13 con la pregunta de fondo, que
es la misma de §12.9 con el plano overlay: qué manda, el componente compuesto o
la receta que lo compone. Si mandan las recetas, se sube la especificidad y no
se retira nada; si mandan los compuestos, hay ~63 claves públicas que retirar en
tres componentes.
LAS DEMÁS ADJUDICACIONES SON RAMAS QUE LA DEMO NO MONTA
El layout grid (forzado alcanza — y ojo: su grid-template-columns sólo CAMBIA si
el valor altera el número de columnas; con 90px pasa de una columna de 502px a
cuatro de 110,5), el resumen de errores VACÍO (sin <ul> que indentar ni <a> que
subrayar) y toda la rama de ARRAY de AutoFields: el modo `auto` de la demo monta
group y field, nunca array, item ni widget.
Fuera por doctrina: los tres privados del panel son un CONMUTADOR (variante
plain contra panel) y los cuatro --_form-action-* leen el forward de paleta
THM-2.
VERIFICACIÓN
Sonda antes/después 0 diffs (832 valores · 7 estados) · R-5.4 22/64 con las 20 +
el patrón adjudicados, exit 0 · component:audit PASS (tras el renombrado) ·
--names 0 desviadas · censo --only 86 % · eidos-lint 36 morfo-backed / 0 invalid
/ 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo
conocido) · el ledger sigue en sus 63 hunks de prettier previos.
Rama con cuatro sesiones en paralelo: índice construido desde HEAD + sólo mis
hunks (base.ts por bloque), verificado con `git diff --cached`. No entra
docs/audit/theming/README.md — es un agregado global que ahora mismo mezcla
trabajo en vuelo de otras sesiones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
form : {
// 2026-08-23.
'grid-min' :
"only under [data-layout='grid'], and the computed template only CHANGES when the value alters the column count: forced the layout and 90px -> reaches (502px -> four 110.5px columns)" ,
'error-summary-list-indent' :
'the summary renders EMPTY in the demo (no errors), so it has no <ul> to indent' ,
'link-underline-offset' : 'same empty summary: no <a> inside it' ,
'auto-fields-array-padding' :
"the array branch needs a schema field of type array; the demo's `auto` mode mounts group + field only" ,
'auto-fields-border-width' : 'same array branch' ,
'auto-fields-radius' : 'same array branch' ,
'auto-fields-item-gap' : 'same array branch (the item exists only inside an array)' ,
'auto-fields-item-padding' : 'same array branch' ,
'widget-padding-inline' : 'the widget is the array toolbar affordance; same missing branch' ,
'widget-radius' : 'same missing branch' ,
'widget-border-width' : 'same missing branch' ,
'grid-column-gap' :
"the demo runs layout='stack'; forced data-layout=grid -> reaches (20px -> 37px)" ,
'invalid-accent' :
'only under [data-form][data-invalid]; forced on the form -> reaches (oklch(0.8059 0.1123 59) -> rgb(1,2,3)) on the summary border' ,
'link-underline-fg' : 'same empty summary as the offset above: no <a> to underline' ,
'auto-fields-array-bg' : 'same array branch the demo never mounts' ,
'auto-fields-item-bg' : 'same array branch' ,
'auto-fields-border' : 'same array branch (the group and the field do not draw it)' ,
'widget-bg' : 'same missing branch' ,
'widget-border' : 'same missing branch'
} ,
uix(dialog): temable — 67 % → 89 %, y el borde dice CÓMO se cerró
Siete claves nuevas (37 → 44), un literal firmado, siete adjudicaciones medidas.
Diff de computed 0 sobre 480 valores en 8 estados.
LAS SIETE SON LAS DOS TINTAS SEMÁNTICAS QUE LE FALTABAN
Cuatro para los tonos EVALUATIVOS (risk-border, risk-title-fg, threat-border,
threat-title-fg): dialog no tiene forward de paleta THM-2 y su trato de tono es
deliberadamente estrecho —el borde y la tinta del TÍTULO, nunca la superficie
del cuerpo, para que el texto largo no compita con la señal—, pero iban a
--color-risk-* a pelo.
Tres para el tinte de SALIDA (saved-border, failed-border, dismissed-border): el
morfo escribe data-last-action antes de que data-state pase a closed, así que el
borde superior dice CÓMO se cerró mientras se va. El tercero cubre cancelled /
dismissed / dismissed-outside, que comparten valor: todo cierre sin resultado.
{tono}-border es el nombre del catálogo (48 claves) y failed-border el de
proof-of-human.
EL 11 % QUE QUEDA SON CUATRO ESCOTILLAS, NO DEUDA
Los --_dialog-content-*-override los escribe el wrapper INLINE desde las props
width / minWidth / maxWidth / height / minHeight. Un público encima lo pisaría el
inline y mentiría — la clase de --_background-gradient-image y del triple de
s-text-virtual-list. El nombre del privado ya lo dice: -override.
⚠ NO se acuñó tipografía de superficie, por §12.9: el contenido lleva
data-depth='modal' y un plano declara font-family / line-height a la misma
especificidad y más tarde en la cascada. Las claves de título y descripción que
ya existían son de PARTES internas, no de la superficie del plano.
SIETE ADJUDICACIONES, DOS DE ELLAS CON SEGUNDA PASADA
Estados que la demo no monta: la hoja inferior, el tamaño full, las ocho celdas
no centradas de la rejilla de posición, el data-position del botón de cierre y
los tres tintes de salida (que sólo existen mientras data-state='closed' y
data-last-action COEXISTEN). Y dos costaron una segunda pasada por el VALOR del
atributo: la rejilla usa top-left / top-right (físicos), no top-start; y el botón
de cierre NO lleva data-position en esta demo aunque todas las reglas que leen
close-inset lo exigen.
VERIFICACIÓN
Sonda antes/después 0 diffs (480 valores · 8 estados · 8 nodos con el diálogo
abierto) · R-5.4 37/44 con las siete adjudicadas, exit 0 · component:audit PASS ·
censo --only 89 % · eidos-lint 15 morfo-backed / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo conocido) · capturas 2×
· el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
dialog : {
// 2026-08-23. Seven tokens of states the demo does not mount: it shows a
// middle-centred `md` dialog, open and neutral. All forced on the real
// content node, transitions frozen.
'content-max-height-sheet' :
'only under [data-sheet] (the wrapper sets it below the sm breakpoint); forced -> reaches (850px -> 377px)' ,
'content-full-inset' : "only under [data-size='full']; forced -> reaches (12px -> 37px)" ,
'content-position-inset' :
'only on the 8 non-centred cells of the position grid; forced data-position=top-left -> reaches (16px -> 37px)' ,
'close-inset' :
'the close button carries NO data-position in this demo, and every rule that reads it needs one; forced data-position=top-right -> reaches (12px -> 37px)' ,
// The exit tint: the morfo writes `data-last-action` before `data-state`
// flips to closed, so both attributes only coexist WHILE the dialog leaves.
'saved-border' :
'only during the exit, with [data-state=closed][data-last-action=saved]; forced -> reaches (oklch(0.9582 0.0223 155) -> rgb(1,2,3))' ,
'failed-border' :
'same exit window; forced -> reaches (oklch(0.9555 0.0207 13.8) -> rgb(1,2,3))' ,
'dismissed-border' :
'same exit window (cancelled / dismissed / dismissed-outside share it); forced -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))'
} ,
uix(pin-input): temable — 65 % → 100 %, y un respaldo que mentía por 0,15
Dos claves nuevas (20 → 22), cuatro privados renombrados, VEINTIDÓS respaldos
retirados y seis adjudicaciones medidas. Diff de computed 0 sobre 1.856 valores
en 8 estados.
SU 35 % DE DEUDA ERAN TRES DEFECTOS, NINGUNO DE DISEÑO
1. Un prefijo ABREVIADO: los privados se llamaban --_pin-cell-* dentro de un
componente llamado pin-input, así que el censo no podía reconocerlos como
suyos y los cuatro knobs que los leen contaban como deuda aunque DERIVAN de
públicos. Renombrados a --_pin-input-cell-* — el cuarto caso de esta clase
tras color-picker, metrics y time-picker.
2. Un respaldo A SÍ MISMO: font-family leía
var(--style-label-font-family, var(--style-label-font-family)), que es un
no-op con forma de defecto. Ahora es la costura.
3. La tinta del dígito iba a --color-content-primary a pelo → cell-fg.
Y LOS VEINTIDÓS RESPALDOS ESCRITOS A MANO
Cada var(--pin-input-x, <valor>) duplicaba lo que el contrato ya declara en
:root: código muerto, y retirarlos dio 0 diffs, que es la prueba. Pero UNO
MENTÍA: disabled-opacity respaldaba 0.55 cuando --opacity-disabled vale 0.4.
Es la ley de los `, 1.4` contra un --font-line-height-sm de 1.45 que este mes ya
salió en dos componentes, aquí con más deriva. Un respaldo no es una red de
seguridad: es una segunda fuente que nadie actualiza.
SEIS ADJUDICACIONES, Y DÓNDE VIVE CADA ESTADO
La demo muestra un pin vacío, editable y válido, así que los seis tokens de
estado no tenían nodo. Forzados sobre los nodos reales alcanzan los seis — con
un detalle que costó una pasada: `filled` y `active` son atributos de la CELDA,
pero `invalid` y `readonly` se estampan en el CONTENEDOR y cascadean (soma los
OR-fusiona con el Field que las envuelve). Forzarlos en la celda no hace nada.
VERIFICACIÓN
Sonda antes/después 0 diffs (1.856 valores · 8 estados · 8 nodos) · R-5.4 16/22
con las seis adjudicadas, exit 0 · component:audit PASS · censo --only 100 % ·
eidos-lint 8 morfo-backed / 0 invalid / 0 class-hooks · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido) · capturas 2× antes y
después · el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'pin-input' : {
// 2026-08-23. Six STATE tokens: the demo shows an empty, editable, valid
// pin. Forced on the real nodes — note WHERE: `filled` and `active` are
// per-CELL attributes, but `invalid` and `readonly` are stamped on the
// CONTAINER and cascade down (forcing them on the cell does nothing).
'cell-filled-bg' :
'no filled cell in the demo; forced data-filled -> reaches (oklch(0.9588 0.0249 311.71) -> rgb(1,2,3))' ,
'cell-filled-border' :
'same state; forced -> reaches (oklch(0.8043 0.0918 309.69) -> rgb(1,2,3))' ,
'cell-active-border' :
'the cell under the caret; forced data-active -> reaches (oklch(0.5556 0.1829 305.86) -> rgb(1,2,3))' ,
'cell-invalid-border' :
'stamped on the CONTAINER (soma OR-merges it with the enclosing Field); forced there -> reaches (oklch(0.6256 0.1933 23.03) -> rgb(1,2,3))' ,
'cell-readonly-bg' :
'container attribute too; forced there -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))' ,
'disabled-opacity' :
'no disabled pin in the demo; forced data-disabled -> reaches (0.4 -> 0.137)'
} ,
uix(spin-field): ADJUDICADO — 67 % es su techo, y lo dice la cabecera de su propio CSS
CERO claves nuevas, un literal firmado, siete adjudicaciones medidas. Diff de
computed 0 sobre 2.400 valores en 8 estados.
EL TECHO ESTABA ESCRITO, Y ES DOCTRINA
Ocho de sus knobs leen los privados RESUELTOS del Field
(--_field-control-height, --_field-control-padding-inline, --_field-control-gap)
porque los steppers y el scrubber IGUALAN la fila del input a cualquier talla.
La cabecera del CSS lo dice con estas palabras: «never a parallel --spin-field-*
chrome that would alias the Field base». Acuñar ahí sería el antipatrón que el
fichero prohíbe por su nombre. Préstamo con dueño —se tema en la ficha de
field— y el censo lo puntúa como deuda porque no sabe distinguirlo: el mismo
artefacto que listbox (68 %) y radio-cards (61 %) ya registraron.
Los dos factores del apilado (* 0.62 para el ancho del botón, * 0.34 para el
glifo) tampoco se acuñan: viven dentro de un calc() sobre ese mismo privado de
Field, así que un token propio no cambiaría quién manda.
SIETE ADJUDICACIONES, Y UNA SORPRESA
Los tres active-control-* no responden a :active NI forzando el pseudo por CDP:
su estado es el [data-scrubbing] que soma pone en el SCRUBBER mientras se
arrastra. Forzado el atributo sobre el nodo real, los tres alcanzan. Más
focus-control-bg (pseudo forzado por CDP), control-disabled-opacity (0.48 →
0.137) y el par de transición sin congelar (0,12s → 11,5s).
ES UNA CAPA, NO UN COMPONENTE
No tiene wrapper ni ruta propia, así que el guard lo mide dentro de
number-field — declarado ahora en `urls`, para que nadie tenga que saberlo.
⚠ Y una corrida de sonda dio 5 «nodos ausentes» que no eran nada: una recarga
que no montó la talla xl. Repetida, 0 diffs y 0 ausentes contra el mismo ANTES.
Un «missing node» tampoco se cree a la primera.
VERIFICACIÓN
Sonda antes/después 0 diffs · R-5.4 13/20 con las siete adjudicadas, exit 0 ·
component:audit sin hallazgos · censo 67 % con las ocho filas de préstamo
explicadas · docs:check 0 · el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'spin-field' : {
// 2026-08-23. Measured inside number-field (this is a shared LAYER with no
// route of its own), transitions frozen except for the last pair.
'active-control-border' :
"the ACTIVE state is the scrubber's [data-scrubbing], not :active; forced on the real scrubber -> reaches (oklch(0.8043 0.0918 309.69) -> rgb(1,2,3))" ,
'active-control-bg' :
'same drag state; forced -> reaches (oklch(0.9932 0.0034 325.6) -> rgb(1,2,3))' ,
'active-control-fg' :
'same drag state; forced -> reaches (oklch(0.5168 0.1733 305.88) -> rgb(1,2,3))' ,
'focus-control-bg' :
'only under :focus-visible, which the guard blurs away on purpose; forced the pseudo via CDP -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))' ,
'control-disabled-opacity' :
'no disabled stepper in the demo; forced data-disabled -> reaches (0.48 -> 0.137)' ,
'transition-duration' :
'frozen by the guard by design; unfrozen pass -> reaches (0.12s -> 11.5s)' ,
'transition-ease' :
'same freeze; unfrozen pass -> reaches (cubic-bezier(0.4,0,0.2,1) -> steps(7))'
} ,
uix(meter): ADJUDICADO — 64 % → 75 %, su techo, y el guard medía la MITAD
CERO claves nuevas: su contrato de 28 ya cubría todo lo que es suyo. Dos
literales firmados, cuatro adjudicaciones medidas y tres filas que se quedan
fuera A PROPÓSITO. Diff de computed 0 (416 valores, 7 estados), capturas
antes/después idénticas.
EL GUARD MEDÍA LA MITAD DEL COMPONENTE
La demo arranca en shape='linear', así que los CATORCE tokens del anillo —doce
pasos de tamaño y grosor, más su pista y su centro— no tenían nodo que pintar:
12 de 28. Con el barrido de data-shape sube a 24/28.
LOS DOS border-radius DEL CÍRCULO NO SE ACUÑAN
Son lo que hace circular a shape="circular": el exterior del anillo y el agujero
del centro. Un --meter-circular-radius dejaría que un tema descirculara una
forma que el consumidor pidió POR NOMBRE — identidad, no knob, la misma lectura
que los dos 50 % de cropper.
Nota de coherencia con badge, donde SÍ acuñé dot-radius para un --radius-full:
allí el punto es un adorno cuya forma es estética (y knob.indicator-radius vale
--radius-sm, que lo prueba); aquí el redondeo ES el valor del atributo. La
prueba sigue siendo qué DECIDE el token, no qué valor tiene.
--_meter-value-pct es CANAL DE VALOR: el wrapper lo escribe inline con el
porcentaje, y de él salen la anchura de la barra y la parada del conic-gradient.
Y los dos 100 % son identidad (la barra lineal ES el ancho de su contenedor; el
indicador llena el alto de la pista), firmados en su declaración.
Adjudicado con medida: las dos tintas de zona fuera de `optimum` (forzadas
alcanzan) y el par de transición (sin congelar: 0,18s → 11,5s).
VERIFICACIÓN
Sonda antes/después 0 diffs · R-5.4 24/28, exit 0 · component:audit PASS · censo
75 % con las tres filas explicadas · eidos-lint 4 morfo-backed / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo
conocido) · capturas 2× miradas · el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
meter : {
// 2026-08-23. The demo sits on the OPTIMUM zone, so the two off-zone inks
// have no node to paint; forced on the real root, transitions frozen.
'indicator-bg-below' :
"only under [data-state='below']; forced -> reaches (oklch(0.6908 0.1909 45.02) -> rgb(1,2,3))" ,
'indicator-bg-above' :
"only under [data-state='above']; forced -> reaches (oklch(0.6256 0.1933 23.03) -> rgb(1,2,3))" ,
'transition-duration' :
'frozen by the guard by design; unfrozen pass -> reaches (0.18s -> 11.5s)' ,
uix(meter): ADJUDICADO — 64 % → 75 %, su techo, y el guard medía la MITAD
CERO claves nuevas: su contrato de 28 ya cubría todo lo que es suyo. Dos
literales firmados, cuatro adjudicaciones medidas y tres filas que se quedan
fuera A PROPÓSITO. Diff de computed 0 (416 valores, 7 estados), capturas
antes/después idénticas.
EL GUARD MEDÍA LA MITAD DEL COMPONENTE
La demo arranca en shape='linear', así que los CATORCE tokens del anillo —doce
pasos de tamaño y grosor, más su pista y su centro— no tenían nodo que pintar:
12 de 28. Con el barrido de data-shape sube a 24/28.
LOS DOS border-radius DEL CÍRCULO NO SE ACUÑAN
Son lo que hace circular a shape="circular": el exterior del anillo y el agujero
del centro. Un --meter-circular-radius dejaría que un tema descirculara una
forma que el consumidor pidió POR NOMBRE — identidad, no knob, la misma lectura
que los dos 50 % de cropper.
Nota de coherencia con badge, donde SÍ acuñé dot-radius para un --radius-full:
allí el punto es un adorno cuya forma es estética (y knob.indicator-radius vale
--radius-sm, que lo prueba); aquí el redondeo ES el valor del atributo. La
prueba sigue siendo qué DECIDE el token, no qué valor tiene.
--_meter-value-pct es CANAL DE VALOR: el wrapper lo escribe inline con el
porcentaje, y de él salen la anchura de la barra y la parada del conic-gradient.
Y los dos 100 % son identidad (la barra lineal ES el ancho de su contenedor; el
indicador llena el alto de la pista), firmados en su declaración.
Adjudicado con medida: las dos tintas de zona fuera de `optimum` (forzadas
alcanzan) y el par de transición (sin congelar: 0,18s → 11,5s).
VERIFICACIÓN
Sonda antes/después 0 diffs · R-5.4 24/28, exit 0 · component:audit PASS · censo
75 % con las tres filas explicadas · eidos-lint 4 morfo-backed / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo
conocido) · capturas 2× miradas · el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'transition-ease' :
'same freeze; unfrozen pass -> reaches (cubic-bezier(0.4,0,0.2,1) -> steps(7))'
} ,
uix(progress): temable — 69 % → 87 %, y el instrumento medía UNA de sesenta caras
Dos claves acuñadas, seis literales FIRMADOS y cuatro knobs adjudicados por
escrito. El default no se mueve: diff de computed 0 sobre 60 configuraciones y
capturas 2x idénticas byte a byte.
Censo: 69 % → 87 % (35 knobs → 30; público 24 → 26, global 3 → 2, literal 6 → 0,
excepción firmada 0 → 6). Contrato 41 → 43 claves. Global 67 % (no baja).
QUÉ ENTRA (2 claves)
row-gap (--space-2) + column-gap (--space-3). Era el ÚNICO knob de apariencia
que iba a un primitivo a pelo, y se parte en dos porque el atajo llevaba DOS
valores distintos: el hueco de fila separa la línea de texto de la barra, el
de columna separa el rótulo del valor. Un gap único habría nombrado un knob
para dos. El catálogo tiene las dos palabras (row-gap x10, column-gap x4) y el
molde exacto es chart.legend-row-gap / -column-gap.
QUÉ SE FIRMA EN VEZ DE ACUÑARSE (6 literales, fuera del ratio)
Cuatro 100 % y dos max-content: la geometría de la barra, no una decisión de
tema — llena la fila que le dan, o se encoge a su propio grosor / diámetro.
Cada uno con su anotación en la declaración (válvula de recipe-contract §3).
QUÉ SE RECHAZA DE LA PROPUESTA §4 (dos de sus tres filas)
indicator-radius — el --radius-full del anillo. Su GEMELO meter ya dejó el
veredicto escrito EN EL CSS: es lo que hace circular a shape='circular', y un
token dejaría que un tema des-redondease una forma que el consumidor pidió por
su nombre. Identidad, no knob; se copia el comentario del gemelo. Son los dos
únicos global que quedan, y el censo no puede sacarlos del ratio: la válvula de
la anotación sólo reclasifica la clase literal, nunca un global (mismo techo
que meter).
width: max-content — la §4 fundía en UN nombre dos nodos distintos (la raíz
vertical y la raíz circular) y los nombraba por el primer selector. Es el
literal de identidad de arriba.
EL TECHO HONESTO ES 87 %, NO 100 %
Los dos knobs private que quedan son el CANAL DE VALOR:
--_progress-value-pct lo escribe soma en el estilo inline del provider en cada
render (0-100 saturado) y la receta lo lee para el ancho del relleno lineal y
el alto del vertical. Un público encima mentiría — un tema no puede fijar el
progreso de una tarea. Misma clase que el z-index inline de drag-drop.
GUARD R-5.4: 40/43, TRES ADJUDICADOS CON SU MEDIDA
indicator-bg-loaded — sólo pinta bajo [data-state='loaded'], y la corrida
mantiene indeterminate encendido para alcanzar los tokens del barrido; forzado
sobre el nodo real: oklch(0.6406 0.1329 157.68) -> rgb(1,2,3).
transition-duration / -ease — los congela el guard por diseño; pasada sin
congelar: 0.18s -> 11.5s y cubic-bezier(0.4, 0, 0.2, 1) -> steps(7).
EL INSTRUMENTO MEDÍA UNA DE SESENTA CARAS
La sonda estándar ve 4 nodos — que son TODAS las partes del morfo — pero una
sola configuración: lineal, horizontal, loading, md. Medio contrato (el anillo,
el eje vertical, el barrido indeterminado, la tinta de completado) queda fuera
del diff, y el gate pasa en verde sin haberlo mirado. El guard lo dijo antes:
22 de 41 en la página por defecto. Con sweepAttr data-shape — el interruptor
que su gemelo meter YA tenía — sube a 34; con el interruptor indeterminate de
la demo, a 37; con el chip vertical, a 38. Cada control se comprobó por
separado, antes y después, porque montar más puede medir menos: aquí ninguno
tapó nada.
Y una trampa NUEVA, medida aquí: una sonda que CACHEA los nodos y luego mide en
bucle da 8.352 falsos diffs si el HMR reemplaza el subárbol a mitad de corrida.
getComputedStyle sobre un nodo DESACOPLADO devuelve la cadena vacía en TODAS
las propiedades, y el diff lee cada vacío como un cambio. Se arregla
re-consultando los nodos en cada instantánea, y se detecta haciendo fallar la
sonda ante un computed vacío. Pariente de hmr-stale-tab-phantom-findings.
ARTEFACTOS
Sonda estándar: 4 nodos, 800 valores x 7 estados -> 0 diffs.
Sonda de variantes (2 formas x 2 orientaciones x 3 estados x 5 tallas, elemento
y ::before): 17.280 valores -> 0 diffs.
Capturas 2x antes/después, lineal y circular: idénticas byte a byte.
Censo --only progress 87 % · component:audit PASS 1/1 · eidos-lint 0 invalid
(13 morfo-backed, 12 eidos-only) · vitest src/uix/eidos 439/440 (el único rojo
es el conocido skin-media-player en lint.test.ts) · rtl:check 0 · docs:check 0
· npm run check sin errores atribuibles a progress · prettier limpio en lo
tocado (progress.css estaba limpio en HEAD y sigue limpio; los dos ficheros del
centinela ya venían sucios de antes y mis líneas no añaden nada).
README de eidos con su sección «Talla y tema» y pestaña Tokens en la demo,
verificada en el navegador: 43 filas resueltas en vivo, 0 errores de consola.
NOTA DE ÁRBOL COMPARTIDO
Otra sesión trabaja sobre form en los mismos ficheros (base.ts,
generated/base.css, los dos del centinela y el índice de fichas). Lo indexado
se construyó a mano desde HEAD + sólo mis hunks, así que el commit no arrastra
su trabajo en curso; docs/audit/theming/README.md lleva sólo el delta de
progress y conserva la fila de form tal como está en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
progress : {
// 2026-08-23. The guard now sweeps `data-shape` (like its twin meter) and
// switches the demo's indeterminate + vertical controls on, which takes it
// from 22/41 to 40/43. What is left needs a state the demo cannot hold at
// the same time as the ones already forced, or a property the guard freezes.
'indicator-bg-loaded' :
"only under [data-state='loaded'], and the run holds `indeterminate` to reach the sweep tokens; forced on the real root -> reaches (oklch(0.6406 0.1329 157.68) -> rgb(1,2,3))" ,
'transition-duration' :
'frozen by the guard by design; unfrozen pass -> reaches (0.18s -> 11.5s)' ,
'transition-ease' :
'same freeze; unfrozen pass -> reaches (cubic-bezier(0.4,0,0.2,1) -> steps(7))'
} ,
uix(emoji-picker): temable — 61 % → 100 %, y el cromo del trigger NO era suyo
Siete claves nuevas, TRES RETIRADAS por muertas, tres literales firmados y cinco
adjudicaciones medidas. Contrato 17 → 21. Diff de computed 0 sobre 6.080 valores
en 8 estados (176 nodos con el panel abierto), capturas 2× idénticas.
LOS NOMBRES LOS DIO EL CATÁLOGO
`open-trigger-fg` (accordion lo nombra igual), grid-padding-inline +
grid-padding-block-end (el eje lógico completo, como card-group / combobox /
command) y las tres del marcador de tono, la familia
timeline.marker-{size,radius,bg}. Se llama tone-marker-* y no marker-* porque el
pie lleva además un selector de TONO: dentro del propio componente, `marker` a
secas sería ambiguo.
TRES CLAVES RETIRADAS — EL CROMO DE REPOSO DEL TRIGGER NO ES SUYO
El trigger lleva también data-popover-trigger, y
[data-popover-trigger]:not([data-archetype='field-trigger']) es (0,2,0) contra
el (0,1,0) de esta receta: el radio, la tinta y el tamaño de letra los pinta
popover.css. Medido: mover --emoji-picker-trigger-{fg,radius,font-size} no
cambiaba NADA. Retiradas con sus declaraciones, 0 diffs — la prueba de que
estaban muertas. Es el defecto que gradient-picker documentó (36 de sus 45
tokens) y la misma respuesta. Sobreviven dos por razones distintas:
trigger-size, porque popover dimensiona con `height` y no con el par lógico, y
open-trigger-fg, porque su selector añade [data-state='open'] y gana por orden.
trigger-fg no llegó a existir: se acuñó y se midió muerta en el mismo pase.
CINCO ADJUDICACIONES, TODAS SOBRE NODOS REALES
Las tres del marcador de tono (la demo monta CERO celdas data-tone-capable;
forzado el atributo sobre una celda real, el ::after alcanza: 4px → 37px,
9999px → 37px, y el fondo), preview-name-fg (los hijos de la vista previa son
<span> desnudos, sin atributo del componente, fuera del conjunto medido) y
open-trigger-fg.
⚠ open-trigger-fg OSCILA ENTRE CORRIDAS: 17/21, 16/21, 16/21 con el mismo
código. Medido vivo CUATRO veces —la última replicando paso a paso el staticPass
del propio guard, que movió a la primera talla—, así que el token no miente: lo
que falla es el instrumento. La causa: el panel se cierra en algún punto de la
pasada y, durante la animación de SALIDA, el content sigue en el DOM mientras el
trigger ya perdió data-state='open', que es lo que la regla selecciona;
reopen() mira el CONTENT, cree que está abierto y no reabre. Probé un openMarker
sobre el trigger y NO basta (el clic de reapertura cae en esa misma ventana), así
que lo retiré en vez de dejar un override que no arregla. El arreglo real es que
reopen() espere a la CONDICIÓN en vez de dormir un número fijo — §13. Su entrada
de ledger saldrá STALE en las corridas donde alcanza, y está escrito en la razón
para que nadie la borre por eso.
Y EL INSTRUMENTO DE CAPTURA ESTABA ROTO DESDE SIEMPRE
__shot.ts tenía el puerto 5173 CABLEADO, así que cualquier sesión en otro puerto
moría con ERR_EMPTY_RESPONSE. Es probablemente por lo que los pasos 6 y 13 del
protocolo se saltaron en 33 componentes sin que nadie lo notara. Ahora toma la
URL como argumento —como la sonda y el guard— y un --open opcional para los
componentes cuya superficie sólo existe abierta. Estrenado aquí: capturas antes
y después, miradas y idénticas.
VERIFICACIÓN
Sonda antes/después 0 diffs (6.080 valores · 8 estados) · R-5.4 17/21 con las
cinco adjudicadas, exit 0 · component:audit PASS · censo --only 100 % ·
eidos-lint 17 morfo-backed / 0 invalid / 0 class-hooks · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) ·
el ledger sigue en sus 63 hunks de prettier preexistentes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'emoji-picker' : {
// 2026-08-23. Four keys of the PORTALED panel. The guard writes the
// sentinel on :root and on the host, and :root does cross the portal
// (verified) — what it cannot do is measure a node the demo never mounts
// or one that carries no attribute of the component.
'tone-marker-size' :
'the corner dot lives on [data-tone-capable]::after and the demo mounts ZERO tone-capable cells; forced the attribute on a real option -> reaches (4px -> 37px)' ,
'tone-marker-radius' : 'same pseudo, same absent state; forced -> reaches (9999px -> 37px)' ,
'tone-marker-bg' : 'same pseudo, same absent state; forced -> reaches (-> rgb(1,2,3))' ,
'preview-name-fg' :
'the preview children are bare <span>s stamped inline, with no data-emoji-picker-* attr, so they are outside the measured set; read on the last child -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
// NON-DETERMINISTIC, and that is the reason: three runs of the same code
// gave 17/21, 16/21, 16/21 — the panel closes somewhere in the pass and the
// trigger loses [data-state=open], which is what this rule selects on.
// Measured alive FOUR times, the last one replicating the guard's own
// staticPass step by step (moved on the first size): oklch(0.2435 0 0) ->
// rgb(1,2,3), from :root and from the host. An openMarker on the trigger was
// tried and did NOT fix it (still oscillates), so the cause is the reopen
// timing, not the marker. EXPECT THIS ENTRY TO BE FLAGGED STALE on the runs
// where it does reach — do not delete it on that alone. next-features 13.
'open-trigger-fg' :
'oscillates run to run (17/21 vs 16/21 on the same code); measured alive four times, including a step-by-step replica of the guard staticPass -> reaches (oklch(0.2435 0 0) -> rgb(1,2,3))'
} ,
uix(color-field): temable — 56 % → 93 %, y sus TRECE tokens nunca se habían medido
Cuatro claves nuevas (22 → 26), un literal firmado, un préstamo adjudicado y
trece tokens PREEXISTENTES medidos uno a uno: el componente nunca había pasado
por R-5.4. Diff de computed 0 sobre 2.336 valores en 8 estados.
LAS CUATRO CLAVES
color-field IS-A Field y no realiasa nada suyo, así que lo que faltaba era
pequeño y concreto:
- separator-bg + separator-thickness — los separadores de canal («,», « / ») se
pintan como líneas verticales de un pelo, y ni su tinta (--color-border-default)
ni su grosor (1px) eran alcanzables. El 1px ES --border-width: es un filete, y
dropdown-menu.separator-thickness lee la misma familia. El MISMO nodo
renderizado como glifo («#», «%») ya tenía su literal-fg — una parte, dos
tratamientos, ahora los dos con mando.
- segment-min-width (3ch) + hex-segment-min-width (6ch) — el ancho fijo por
número de caracteres, el que impide el reflujo al teclear (99 → 100). Los
gemelos date-field y time-field cargan el mismo hecho DENTRO de un
calc(2ch + 2 * var(--…-segment-padding-inline)), así que el suyo ya viajaba con
un token; aquí iba a pelo. Precedentes de `ch` en el catálogo:
editable.area-min-inline-size (12ch) y tags-input.input-min-inline-size (8ch).
EL 7 % QUE QUEDA ES UNA FILA, Y ES DOCTRINA
El chip lee var(--field-control-radius) porque IGUALA el radio del control —el
comentario del CSS lo dice: «so the chip tracks the control»—. Qué DECIDE ese
token: el radio del control de campo. Un swatch-radius propio daría un segundo
mando para una decisión ajena y permitiría desincronizar el chip del campo que
lo contiene. Préstamo con dueño: se tema en la ficha de field — misma lectura
que el --slider-disabled-opacity de waveform, hoy mismo.
TRECE ADJUDICACIONES, TODAS PROBADAS SOBRE NODOS REALES
La demo arranca con valor, en md, editable y con tres formatos: cada estado y
cada paso de talla que no monta no tiene nodo que pintar. Seis de estado
(placeholder, invalid, los tres de readonly, disabled), cuatro de la escala del
chip (14→77, 16→77, 20→77, 32→77 px), la etiqueta del formato BLOQUEADO —que
sólo existe con un único formato permitido, medida desmarcando rgb y hsl CON LOS
CHIPS DE LA PROPIA DEMO, no fabricando el nodo— y las dos de transición.
LAS DOS DE TRANSICIÓN, DOS DIAGNÓSTICOS FALSOS Y EL SEGUNDO ES NUEVO
El primero es la ley ya escrita: el guard congela `transition` para hacer
medible todo lo demás, así que los tokens que SON la transición no se mueven
bajo él. El segundo fue mío: midiéndolas a mano leí 0s y sospeché del
@media (prefers-reduced-motion: reduce) del final del fichero — lo comprobé
corriendo con reducedMotion: 'no-preference' y SEGUÍA dando 0s, porque el nodo
que había cogido (querySelector('[data-color-field-segment]')) es el PRIMER
segmento, que es un `literal` y declara transition: none de su propia cosecha.
Sobre un segmento editable: 0.12s → 11.5s y cubic-bezier(0.4,0,0.2,1) →
steps(7). Mide el nodo que PINTA, no el primero que casa el selector.
VERIFICACIÓN
Sonda antes/después 0 diffs (2.336 valores · 8 estados · 10 nodos) · R-5.4 13/26
con las trece restantes adjudicadas y medidas, cero STALE · component:audit PASS
· censo 93 % · eidos-lint 14 morfo-backed / 14 eidos-only / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es
skin-media-player, el conocido) · Chrome: los dos glifos con tinta y sin fondo,
el divisor a 1px con el fondo del borde, hex 64.4px (6ch), chip 18px.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'color-field' : {
// 2026-08-23. Thirteen tokens, none of them a lie: the demo boots with a
// value, in `md`, editable, with three formats allowed — so every state
// and every size step but the mounted one has no node to paint. All
// forced on the REAL nodes (never a fabricated one), transitions frozen.
'placeholder-fg' :
'no empty field in the demo; forced data-placeholder → reaches (oklch(0.61 0 0) → rgb(1,2,3))' ,
'segment-invalid-fg' :
'invalid is off in the demo; forced data-invalid → reaches (oklch(0.5855 0.1743 42.74) → rgb(1,2,3))' ,
'segment-readonly-bg' :
'readonly is off in the demo; forced data-readonly → reaches (oklch(0.931 0 0) → rgb(1,2,3))' ,
'segment-readonly-fg' :
'readonly is off in the demo; forced data-readonly → reaches (oklch(0.5032 0 0) → rgb(1,2,3))' ,
'segment-readonly-decoration' :
'readonly is off in the demo; forced data-readonly → reaches (underline dotted → overline wavy)' ,
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'disabled-opacity' : 'disabled is off in the demo; forced data-disabled → reaches (0.4 → 0.123)' ,
uix(color-field): temable — 56 % → 93 %, y sus TRECE tokens nunca se habían medido
Cuatro claves nuevas (22 → 26), un literal firmado, un préstamo adjudicado y
trece tokens PREEXISTENTES medidos uno a uno: el componente nunca había pasado
por R-5.4. Diff de computed 0 sobre 2.336 valores en 8 estados.
LAS CUATRO CLAVES
color-field IS-A Field y no realiasa nada suyo, así que lo que faltaba era
pequeño y concreto:
- separator-bg + separator-thickness — los separadores de canal («,», « / ») se
pintan como líneas verticales de un pelo, y ni su tinta (--color-border-default)
ni su grosor (1px) eran alcanzables. El 1px ES --border-width: es un filete, y
dropdown-menu.separator-thickness lee la misma familia. El MISMO nodo
renderizado como glifo («#», «%») ya tenía su literal-fg — una parte, dos
tratamientos, ahora los dos con mando.
- segment-min-width (3ch) + hex-segment-min-width (6ch) — el ancho fijo por
número de caracteres, el que impide el reflujo al teclear (99 → 100). Los
gemelos date-field y time-field cargan el mismo hecho DENTRO de un
calc(2ch + 2 * var(--…-segment-padding-inline)), así que el suyo ya viajaba con
un token; aquí iba a pelo. Precedentes de `ch` en el catálogo:
editable.area-min-inline-size (12ch) y tags-input.input-min-inline-size (8ch).
EL 7 % QUE QUEDA ES UNA FILA, Y ES DOCTRINA
El chip lee var(--field-control-radius) porque IGUALA el radio del control —el
comentario del CSS lo dice: «so the chip tracks the control»—. Qué DECIDE ese
token: el radio del control de campo. Un swatch-radius propio daría un segundo
mando para una decisión ajena y permitiría desincronizar el chip del campo que
lo contiene. Préstamo con dueño: se tema en la ficha de field — misma lectura
que el --slider-disabled-opacity de waveform, hoy mismo.
TRECE ADJUDICACIONES, TODAS PROBADAS SOBRE NODOS REALES
La demo arranca con valor, en md, editable y con tres formatos: cada estado y
cada paso de talla que no monta no tiene nodo que pintar. Seis de estado
(placeholder, invalid, los tres de readonly, disabled), cuatro de la escala del
chip (14→77, 16→77, 20→77, 32→77 px), la etiqueta del formato BLOQUEADO —que
sólo existe con un único formato permitido, medida desmarcando rgb y hsl CON LOS
CHIPS DE LA PROPIA DEMO, no fabricando el nodo— y las dos de transición.
LAS DOS DE TRANSICIÓN, DOS DIAGNÓSTICOS FALSOS Y EL SEGUNDO ES NUEVO
El primero es la ley ya escrita: el guard congela `transition` para hacer
medible todo lo demás, así que los tokens que SON la transición no se mueven
bajo él. El segundo fue mío: midiéndolas a mano leí 0s y sospeché del
@media (prefers-reduced-motion: reduce) del final del fichero — lo comprobé
corriendo con reducedMotion: 'no-preference' y SEGUÍA dando 0s, porque el nodo
que había cogido (querySelector('[data-color-field-segment]')) es el PRIMER
segmento, que es un `literal` y declara transition: none de su propia cosecha.
Sobre un segmento editable: 0.12s → 11.5s y cubic-bezier(0.4,0,0.2,1) →
steps(7). Mide el nodo que PINTA, no el primero que casa el selector.
VERIFICACIÓN
Sonda antes/después 0 diffs (2.336 valores · 8 estados · 10 nodos) · R-5.4 13/26
con las trece restantes adjudicadas y medidas, cero STALE · component:audit PASS
· censo 93 % · eidos-lint 14 morfo-backed / 14 eidos-only / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es
skin-media-player, el conocido) · Chrome: los dos glifos con tinta y sin fondo,
el divisor a 1px con el fondo del borde, hex 64.4px (6ch), chip 18px.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// The swatch scale: only the step in force paints. Forced each size on the
// real root and measured the swatch — every step reaches.
'swatch-size-xs' : 'only the size in force paints; forced data-size=xs → reaches (14px → 77px)' ,
'swatch-size-sm' : 'only the size in force paints; forced data-size=sm → reaches (16px → 77px)' ,
'swatch-size-lg' : 'only the size in force paints; forced data-size=lg → reaches (20px → 77px)' ,
'swatch-size-xl' : 'only the size in force paints; forced data-size=xl → reaches (32px → 77px)' ,
// The locked-format LABEL only renders when a single format is allowed
// (`isLocked`). Driven through the demo's own chips — not a hand-made node.
'format-select-fg' :
'the static label only renders with ONE allowed format; unchecked rgb+hsl in the demo → reaches (oklch(0.5032 0 0) → rgb(1,2,3))' ,
// The known law: the guard freezes `transition` to make everything else
// measurable, so the tokens that ARE the transition can never move under
// it. Measured in a separate unfrozen pass, on an EDITABLE segment — the
// first segment is a `literal`, which declares `transition: none` of its
// own and reads 0s whatever the token says.
'transition-duration' :
'frozen by the guard by design; unfrozen pass on the hex segment → reaches (0.12s → 11.5s)' ,
'transition-ease' :
'frozen by the guard by design; unfrozen pass on the hex segment → reaches (cubic-bezier(0.4,0,0.2,1) → steps(7))'
} ,
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía
Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada.
Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una
adjudicación escrita. Global 65 % (al 100 % 25 → 26).
LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO
Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %,
context-menu 87 %) dan content-{min-width,padding,radius,fg} y
chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg.
Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL
(0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio.
Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y
dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por
esa familia.
LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA
Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer
selector de la regla ([…-trigger]::after) y colisionando con trigger-height,
que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos
con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la
FILA, y el padding real del panel a «sin nombre mecánico». Llamaba
content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la
capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los
CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra
(LAYER_VOCABULARY sólo registra calendar-surface).
LAS FILAS DEL PANEL NO SON item- NI link-
En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte
del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o
--…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan
content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo
trigger-fg, como venía.
EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR
--_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por
data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el
bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se
retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja.
indicator-x ERA DECLARACIÓN MUERTA
Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador
(-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra
que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía
nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el
re-point de --command-radius, el trío item-gap* de carousel). -w se queda:
vive, y es canal de valor, no superficie de tema.
EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO
Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a
la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería
muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en
menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la
BARRA (trigger-font-family), que no es overlay y alcanza.
TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13)
1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del
mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia:
el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9
de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa
a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES.
2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter
y pointerleave programa el cierre; el guard abría con clic y luego aparcaba
el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo
código discrepaban. Y daba las DOS caras del error: falso negativo en los
tokens de la fila, falso positivo en content-link-padding-inline, que leía
«vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos
corridas idénticas.
3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el
acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo
mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro
componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0).
Y una de contabilidad, del instrumento y no del componente: el censo decide el
eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar
esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le
pasa a sidebar y nav-tree. En §13.
VERIFICACIÓN
Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel
abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única
(disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y
link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30
morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido)
· check sin error nuevo en los ficheros tocados · Chrome con el panel abierto:
panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px.
Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni
STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'navigation-menu' : {
// 2026-08-23. The rule is `[…-trigger][data-disabled], […-link][data-disabled]`
// and the demo mounts neither — measured with transitions FROZEN, because
// `color` is transitioned on this part and an immediate read returns the
// OLD value (the eyedropper trap: three false diagnoses before freezing).
'disabled-trigger-fg' :
'no disabled trigger or link in the demo; forced data-disabled → reaches on BOTH parts (rgb(1,2,3))'
} ,
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía 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 (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
command : {
'focus-input-border' :
'consumed only under :focus-visible, which the guard blurs away ON PURPOSE (the click-focus poisoning fix); focused by hand → reaches (rgb(9,9,9))' ,
'loading-height' : 'the loading bar is conditional and the demo does not mount it' ,
'loading-fg' : 'the loading bar is conditional and the demo does not mount it' ,
'viewport-padding' : 'the non-ScrollArea viewport variant is not mounted in the demo' ,
'empty-padding-block' : 'the empty state is conditional and not mounted at measurement' ,
'empty-fg' : 'the empty state is conditional and not mounted at measurement'
} ,
table : {
'header-z' :
'consumed only under [data-sticky-header], which the demo does not set; forced → reaches (2 → 4321)' ,
'row-detail-fg' :
'detail rows are closed (display:none) in the demo initial state; forced data-state=open → reaches (rgb(1,2,3))' ,
'row-detail-padding-block' :
'detail rows are closed (display:none) in the demo initial state; forced data-state=open → reaches (12 → 1234)'
} ,
'media-player' : {
'accent-strong' : 'consumed by the live indicator, which only paints under [data-live]' ,
'buffered-bg' :
'forwarded into the composed Slider (SecondaryRange) — outside this component attribute space' ,
'captions-bg' : 'captions are conditional and the demo video carries no track' ,
'captions-fg' : 'captions are conditional and the demo video carries no track' ,
'captions-radius' : 'captions are conditional and the demo video carries no track' ,
'captions-padding-block' : 'captions are conditional and the demo video carries no track' ,
'captions-padding-inline' : 'captions are conditional and the demo video carries no track' ,
'captions-line-height' : 'captions are conditional and the demo video carries no track' ,
'volume-vertical-length' : 'the volume float is interaction-mounted; not open at measurement' ,
'volume-float-padding-block' :
'the volume float is interaction-mounted; not open at measurement' ,
'volume-float-padding-inline' :
'the volume float is interaction-mounted; not open at measurement' ,
'rate-button-min-width' : 'the rate control is conditional and the demo does not mount it' ,
'audio-title-padding-block' : 'audio variant — the video demo does not mount the audio title' ,
'audio-title-padding-inline' : 'audio variant — the video demo does not mount the audio title'
} ,
'tree-grid' : {
'header-z' :
'consumed only under [data-sticky-header], which the demo does not set; forced → reaches (2 → 4321)' ,
'disabled-row-fg' : 'no disabled row in the demo; forced data-disabled → reaches (rgb(1,2,3))'
} ,
'gradient-picker' : {
'chip-radius' :
'the swatch is a CLASS hook (.gradient-picker-trigger-swatch) outside the attribute-filtered node set; direct read → reaches (§13 class-hook debt)' ,
'chip-border' :
'the swatch is a CLASS hook outside the attribute-filtered node set; direct read → reaches (§13 class-hook debt)' ,
'content-width-sm' :
"sized by data-picker-size (picker-shell's axis, not data-size); forced → reaches (288 → 512)" ,
'content-width-lg' :
"sized by data-picker-size (picker-shell's axis, not data-size); forced → reaches (352 → 512)" ,
'stoplist-max-block' :
"consumer node lives in the composed ScrollArea's attribute space (--scroll-area-auto-max-block on the viewport); direct read → reaches (132 → 1234)" ,
'stoplist-scrollbar-size' :
'the design scrollbar only mounts on overflow; the demo list is short' ,
'stoplist-scrollbar-inset' :
"consumer node lives in the composed ScrollArea's attribute space; direct read → reaches (12 → 1234)" ,
'presets-panel-width' :
"the presets popover needs a second click the guard's open step does not perform; forced → reaches (240 → 1234)" ,
'presets-gap' : 'presets popover not mounted by the open step; forced → reaches (8 → 1234)' ,
'presets-min-column' :
'presets popover not mounted by the open step; forced → reaches (66px columns → 1234)' ,
'preset-height' : 'presets popover not mounted by the open step; forced → reaches (32 → 1234)' ,
'preset-radius' : 'presets popover not mounted by the open step; forced → reaches (6 → 1234)' ,
'preset-border' : 'presets popover not mounted by the open step; forced → reaches (rgb(1,2,3))' ,
'hover-preset-border' :
'presets popover not mounted by the open step; forced + real hover → reaches (rgb(1,2,3))' ,
'selected-preset-border' :
'presets popover not mounted by the open step; forced data-selected → reaches (rgb(1,2,3))' ,
'selected-preset-outline-offset' :
'presets popover not mounted by the open step; forced data-selected → reaches (2 → 1234)'
} ,
listbox : {
'invalid-border' : 'no invalid listbox in the demo; forced data-invalid → reaches (rgb(1,2,3))'
} ,
carousel : {
'vertical-block-size' :
'vertical orientation not mounted in the demo; forced → reaches (256 → 1234)'
} ,
feed : {
'sentinel-spinner-size' :
'the spinner only exists while [data-busy], and the guard WAITS for the busy window to close (the settle fix); measured live during the busy window (::before, 12 → 1234)' ,
'sentinel-spinner-width' :
'the spinner only exists while [data-busy]; measured live during the busy window (::before border)' ,
'sentinel-spinner-gap' :
'the spinner only exists while [data-busy]; measured live during the busy window (::before margin)' ,
'sentinel-spinner-duration' :
'the spinner only exists while [data-busy]; measured live during the busy window (animation-duration 0.7s → 11.5s)' ,
'article-title-font-size-5' :
'the demo mounts heading levels 3– 4 only; forced aria-level=5 → reaches (14 → 1234)' ,
'article-title-font-size-6' :
'the demo mounts heading levels 3– 4 only; forced aria-level=6 → reaches (12 → 1234)' ,
'sentinel-spinner-reduced-duration' :
'behind prefers-reduced-motion; emulated reduce context → reaches (4s → 11.5s)'
uix(grid-list): temable — 0 % → 85 %, y una selección que nunca pintó
38 claves nuevas en el bloque `grid-list` de `recipes/base.ts` (que ya existía
con los dos forwards de paleta: se FUSIONA, no se añade un segundo). Alcance
del censo 0 % → 85 % (22 de 26 puntuables); global 45 % → 46 %, sin contrato
49 → 48.
El eje `size` se estampa en el PROVIDER —el wrapper de eidos no tiene parte
`root`—, así que los tres nombres resueltos van SIN `parts`: molde `command`,
no `table`/`tree-grid`. Fuera los cuatro bloques `[data-size]` del CSS; los
emite el TSC. Altura de fila 1:1 con el bundle, `row-padding-inline` con ritmo
propio, y la tipografía un paso por debajo de md en adelante — verbatim, como
table y tree-grid.
LO QUE LA MEDICIÓN CAMBIÓ (la §4 generada tenía un error de doctrina y dos
defectos que sólo aparecieron midiendo; el §5 Veredicto se escribió con la
sonda delante, porque estaba vacío):
1. La selección de fila NUNCA HA PINTADO. La receta seleccionaba
`[data-grid-list-row][data-selected]`, un atributo que ni el morfo declara
ni soma estampa (estampa `data-state='selected'` + `aria-selected`): la
regla no casaba jamás y su acento `--_grid-list-palette-element` no llegó
nunca al píxel — una fila seleccionada muestra el velo NEUTRO del
arquetipo, indistinguible del hover (capturado). Retirada la declaración
muerta y su forward huérfano: 0 diffs, que es la prueba. Repararlo mueve
píxel y choca con §12.5 (el arquetipo fija también `color`) → §13, espera
firma. El acento del checkbox (`_palette-solid`) sí vive y se conserva.
2. NO se acuña `hover-row-bg` aunque la propuesta lo pedía. La fila es
`archetype: 'item'`: el velo del sistema la pinta a especificidad plena y
el plano de la receta pierde — medido, el hover pinta DOS VECES sobre el
MISMO nodo (a diferencia de table/tree-grid, donde velo y plano caen en
nodos distintos). Sería un token que no puede ganar; mismo criterio que
listbox.highlighted → §12.7. Y fila Y celda llevan ambas `item`, así que
bajo el puntero se apilan tres capas.
3. «Consume la capa compartida `menu-indicator`» es FALSO. Sale de
`SHARED_LAYERS` (`theming-census.ts:432`), pero esa capa no tiene un solo
selector de grid-list, la receta no la importa y soma no renderiza
indicator: el checkbox ESPEJA el visual pintándolo por su cuenta. Entrada
desviada del censo → §13 (listbox / menubar / navigation-menu están en la
misma entrada y tampoco aparecen en el fichero).
Nombres, sobre la propuesta: `selection-checkbox-{width,height}` son UN knob →
`selection-checkbox-size`; `on-selection-checkbox-bg` → `checked-selection-checkbox-fg`
(es la tinta del glifo, y `on-` es prefijo reservado); `border` partido en
`border-width` + color (molde `command.input-border*`), que el checkbox
comparte; `padding` uniforme se queda `padding` (precedente
`command.viewport-padding`); `max-block` entra al contrato (molde
`command.viewport-max-block`).
Artefactos y gates:
- Sonda antes/después = 0 diffs en 6.090 valores × 7 estados. Determinismo
verificado: dos corridas del mismo código, 0 diffs.
- R-5.4: 37/38 tokens mueven un computed. `invalid-border` adjudicado EN EL
LEDGER con su razón medida (la demo arranca con su control en off; activado
a mano alcanza: oklch(0.9555 0.0207 13.86) → rgb(1,2,3)).
- censo --only 85 % · component:audit PASS (R-5.3 incluida) · eidos-lint
invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el único es el
conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero
limpio (72 globales, ninguno mío).
- README con su «Talla y tema» y tab `Tokens` en la demo (38 claves, leídas en
vivo). El tab `Recipe` documentaba la regla retirada: corregido.
Las fichas de calendar / carousel / command / month-grid / range-calendar /
table / year-grid entran porque `--report` regenera el informe entero: sólo
actualizan NÚMEROS DE LÍNEA de commits ya hechos que no lo regeneraron. Cero
cambios de alcance. Las otras 161, que sólo cambiaban la fecha, se revirtieron.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
} ,
uix(drag-drop): temable — 0 % → 89 %, casi todo en el fantasma del arrastre
10 claves en el bloque `drag-drop` de `recipes/base.ts` (fusionadas con
`preview-z`, que ya estaba, y los dos forwards de paleta). Censo 0 % → 89 %,
cero globales.
**Siete de sus dieciséis knobs YA eran sistema** y no entran al ratio (las
opacidades de fantasma y deshabilitado, los dos anillos de foco y el anillo
`inset` de los droppables), así que los puntuables eran nueve — seis de ellos
en el PREVIEW, el fantasma que sigue al puntero.
1. Los dos forwards de paleta siguen privados (THM-2): el fondo del
`[data-dragover]` los lee a pelo y no admite un público sin duplicar la
regla — es el 11 % que falta. En cambio el `border` y el `box-shadow` del
preview los COMBINAN con una anchura y una sombra que sí son knobs propios,
así que esas dos declaraciones entran al alcance.
2. El `--ring-inset-*` de los droppables no se toca: es vocabulario del anillo
del SISTEMA y la receta se limita a alimentarlo con su acento. Acuñar un
`droppable-ring-*` sería vocabulario paralelo sobre un transversal.
3. El `padding` del preview es un shorthand de dos valores → dos knobs lógicos.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.827 valores × 7 estados. Determinismo
verificado.
- R-5.4: 0/10 en vivo, y los DIEZ adjudicados con su medición. No es un
instrumento roto ni tokens muertos: el preview se crea sólo mientras hay un
arrastre en curso (0 nodos en reposo, contados), y forzando el nodo los diez
mueven su propiedad — padding 8→1234px, radio 6→1236px, borde 1→9px, fondo,
tinta, familia, tamaño 14→99px, sombra y z 100→4321.
- censo --only 89 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check
0 · docs:check 0 · check por fichero limpio.
- README con su «Talla y tema» y tab `Tokens` en la demo (10 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'drag-drop' : {
'preview-padding-block' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (8 → 1234px)' ,
'preview-padding-inline' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (12 → 1235px)' ,
'preview-radius' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (6 → 1236px)' ,
'preview-border-width' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (1 → 9px)' ,
'preview-bg' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (rgb(1,2,3))' ,
'preview-fg' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (rgb(4,5,6))' ,
'preview-font-family' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (Zapfino)' ,
'preview-font-size' :
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (14 → 99px)' ,
'preview-shadow' :
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA
10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese
techo bajo es lo correcto: **es un componente de TONO FIJO**.
Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas
de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN,
no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga»,
dice su cabecera. La pintura vive en el SVG bajo la excepción que
`recipe-css-contract.test.ts` ya reconoce para natural-time-picker,
color-picker y proof-of-human.
Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values:
retune here, in one place»:
- **El ancho base es POR SKIN**, no un número: un casete no mide lo que un
tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y
escribió un solo `base-width`; el assert del script lo cazó al encontrar dos
declaraciones, y la corrección salió de volver a leer el fichero entero.
- La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él
estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo
vocabulario.
- Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue
enseñando su vidrio») y el radio del área de pulsación de las teclas.
Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera) y los cuatro `100%` de layout.
EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE
no prueba nada contra una propiedad capada**. El ancho es
`min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor
y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores
PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE
(133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela
igual al real lee como no efecto»: el centinela tiene que ser imposible **en la
dirección en que la propiedad puede moverse**, y un guard que probara los dos
extremos cazaría ambas familias.
Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió
a romperse por comillas simples dentro de cadenas de comillas simples
(`data-skin='record'`). Reescrito con comillas dobles, verificado con un import
real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código
de salida REAL**: cero fallos.
Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente,
que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 ·
docs:check 0 · check por fichero limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (0 0 0 7px rgb(7,8,9))'
uix(drag-drop): temable — 0 % → 89 %, casi todo en el fantasma del arrastre
10 claves en el bloque `drag-drop` de `recipes/base.ts` (fusionadas con
`preview-z`, que ya estaba, y los dos forwards de paleta). Censo 0 % → 89 %,
cero globales.
**Siete de sus dieciséis knobs YA eran sistema** y no entran al ratio (las
opacidades de fantasma y deshabilitado, los dos anillos de foco y el anillo
`inset` de los droppables), así que los puntuables eran nueve — seis de ellos
en el PREVIEW, el fantasma que sigue al puntero.
1. Los dos forwards de paleta siguen privados (THM-2): el fondo del
`[data-dragover]` los lee a pelo y no admite un público sin duplicar la
regla — es el 11 % que falta. En cambio el `border` y el `box-shadow` del
preview los COMBINAN con una anchura y una sombra que sí son knobs propios,
así que esas dos declaraciones entran al alcance.
2. El `--ring-inset-*` de los droppables no se toca: es vocabulario del anillo
del SISTEMA y la receta se limita a alimentarlo con su acento. Acuñar un
`droppable-ring-*` sería vocabulario paralelo sobre un transversal.
3. El `padding` del preview es un shorthand de dos valores → dos knobs lógicos.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.827 valores × 7 estados. Determinismo
verificado.
- R-5.4: 0/10 en vivo, y los DIEZ adjudicados con su medición. No es un
instrumento roto ni tokens muertos: el preview se crea sólo mientras hay un
arrastre en curso (0 nodos en reposo, contados), y forzando el nodo los diez
mueven su propiedad — padding 8→1234px, radio 6→1236px, borde 1→9px, fondo,
tinta, familia, tamaño 14→99px, sombra y z 100→4321.
- censo --only 89 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check
0 · docs:check 0 · check por fichero limpio.
- README con su «Talla y tema» y tab `Tokens` en la demo (10 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
} ,
uix(link): temable — 0 % → 36 %, y ese techo bajo es la respuesta correcta
5 claves fusionadas en el bloque `link` de `recipes/base.ts` (donde ya vivían
los dos forwards de paleta). Censo 0 % → 36 %.
**Un enlace en línea hereda casi todo del texto que lo rodea**, así que su
superficie de tema es pequeña por diseño. Sus dos escotillas por instancia
(`--_link-font-size`, `--_link-line-height`) tienen como fallback `inherit`, y
ese `inherit` es SEMÁNTICO: el enlace toma la tipografía del párrafo en el que
vive. Convertirlo en token exigiría un público con valor `inherit`, que en
`:root` no significa lo mismo — se quedan privadas. Con los dos forwards de
paleta, ésos son los cuatro privados que forman el grueso del 64 % restante.
Lo tokenizable es la GEOMETRÍA: radio del anillo, hueco del glifo externo,
tamaño del glifo (`0.85em` en las dos dimensiones → UN knob) y el grosor y
desplazamiento del subrayado, cada uno declarado en las DOS reglas que lo
dibujan (`always` y `hover`) con el mismo valor. Las tres opacidades son
sistema; el `fill: currentColor` del glifo es identidad, sigue a la tinta del
enlace a propósito.
**La cascada no se toca.** El CSS lleva dos correcciones de especificidad ya
medidas y explicadas en su sitio (el `inherit` condicional, para que un `color`
pedido por el consumidor no se descarte en silencio; y el `:active` al final,
porque una pulsación es siempre también un hover). El diff de computed confirma
que tokenizar no las altera.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 203 valores × 7 estados. Determinismo
verificado.
- R-5.4: 2/5, los tres mudos adjudicados con su medición. Ninguno es deuda: el
glifo externo no se monta en la demo (0 nodos, contados) y el enlace arranca
en `data-underline='hover'` sin puntero encima, así que NO HAY subrayado que
mover (`text-decoration-line: none`, `thickness: auto`). Forzando el estado,
los tres alcanzan.
- En el mismo pase, `text-decoration-thickness` y `text-underline-offset`
entran en la lista de propiedades del guard, que no las miraba —
verificado que los otros DIECISÉIS componentes con ledger no cambian.
- censo --only 36 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por
fichero limpio · README con «Talla y tema» y tab `Tokens` (5 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
link : {
'external-icon-size' :
'the demo mounts no EXTERNAL link, so the glyph never renders (0 nodes, counted); forced → reaches (11.89 → 77px on both axes)' ,
'underline-width' :
"the demo link sits at data-underline='hover' with no pointer on it, so there IS no underline to move (text-decoration-line: none, thickness: auto). Forced 'always' → reaches (1 → 9px). textDecorationThickness was missing from the guard prop list and was added in the same pass" ,
'underline-offset' :
"same: no underline at rest under data-underline='hover'; forced → reaches (2 → 11px). textUnderlineOffset added to the guard prop list too"
} ,
uix(knob): temable — 29 % → 75 %, y un nombre que significaba dos cosas
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 29 % → 75 %.
El knob ya tenía siete públicos «de facto» —consumidos como
`var(--knob-x, <default>)`— pero **ninguno declarado**, así que un tema no los
veía en `getRecipeTokens()`. Tokenizar ha sido sobre todo DECLARAR lo que ya se
consumía, y separar dos cosas que compartían nombre.
1. **Cuatro `--knob-*` quedan FUERA a propósito**: `progress`, `angle`,
`start-angle` y `sweep` los escribe el provider INLINE desde el estado del
control (`knob-provider.svelte.ts:358`). Canal de valor, no superficie de
tema \u2014 ningún contrato gana a un estilo inline. Misma clase que
`carousel.item-gap` y que la `z` del preview de drag-drop que retiró la
revisión adversarial.
2. **⚠ `--knob-arc-width` significaba DOS cosas con DOS defaults distintos**: el
inset de la cara del dial (`--space-3`, líneas 80 y 113) y la separación
superior del puntero (`--space-2`, línea 165). Un tema que lo escribiera
movía las tres declaraciones a la vez, pero **sin tema cada una valía algo
distinto** \u2014 «un nombre que no distingue lo que debería», que es la
definición de colisión que usa el propio generador. Separado en `arc-width`
(la cara) y `pointer-inset` (el puntero), cada uno con su valor verbatim:
cambia la superficie de tema, **no el píxel**.
3. **Las coordenadas por talla SUBEN al TSC, y no era opcional.** El default por
talla vivía en un privado y el público era un override que iba DELANTE
(`var(--knob-gap, var(--_knob-gap))`): declarar `--knob-gap` en el contrato
**habría matado la escala**, porque ganaría siempre. Con el patrón del resto
del eje (coordenadas + resuelto por `data-size`) el default sigue vivo y el
tema alcanza. Eje sm|md|lg|xl, sin xs; el diámetro es `control-height × 2` en
cada talla, proporción del bundle conservada verbatim.
`--_knob-value-digits` sigue privado: es el número de dígitos del valor máximo,
dato del consumidor.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 754 valores × 7 estados. Determinismo
verificado.
- R-5.4: 25/33; los ocho mudos adjudicados con su medición (la demo no monta
etiqueta \u2014 0 nodos contados \u2014 ni estado de arrastre; forzados, los ocho
mueven).
- censo --only 75 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos 439/440 (el rojo conocido) · rtl:check 0 · docs:check 0 ·
check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (33 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
knob : {
'label-font-size-sm' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (per-size coordinate; the resolved name moves with it)' ,
'label-font-size-md' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (16 → 99px)' ,
'label-font-size-lg' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (per-size coordinate)' ,
'label-font-size-xl' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (per-size coordinate)' ,
'label-font-size' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (16 → 99px)' ,
'label-fg' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (rgb(1,2,3))' ,
'label-line-height' :
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (20px → 3.77em)' ,
'dragging-shadow' :
'consumed only under [data-dragging], and the demo knob is at rest (0 nodes, counted); forced → reaches (0 0 0 7px rgb(4,5,6))'
} ,
uix(label): temable — 0 % → 100 % con dos claves
2 claves fusionadas en el bloque `label` de `recipes/base.ts` (donde ya vivía
el forward de paleta). Censo 0 % → **100 %**.
**Seis de sus ocho knobs son SISTEMA y no se acuñan.** `label` es uno de los
seis primitivos tipográficos: sus seis ejes consumen `--style-label-*` a través
de la escotilla por instancia (`var(--_label-{eje}, var(--style-label-{eje}))`),
la forma que D-TH.2-b fijó y la que hace que un `<Label>` y un
`<Text style="label">` se lean idénticos. La capa los posee; re-declararlos
sería el vocabulario paralelo que la regla 2 prohíbe.
Quedan DOS knobs propios, los que el primitivo añade por su cuenta: el `gap` al
icono o indicador que puede acompañar al texto, y `disabled-fg`, la tinta
cuando el consumidor marca la etiqueta como deshabilitada junto a su control.
`--_label-color` sigue privado: conmutador de dos fuentes (eje de tinta en
línea / forward de paleta cuando llega `data-color`), el mismo patrón ya
resuelto igual en `code`, `textarea`, `spinner` y `skeleton`.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 174 valores × 7 estados.
- R-5.4: 1/2. `disabled-fg` adjudicado: la demo no monta ninguna etiqueta
deshabilitada (0 nodos, contados); forzada, alcanza (rgb(1,2,3)).
- censo --only 100 % · component:audit PASS · suite eidos sin rojos nuevos ·
rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
label : {
'disabled-fg' :
'the demo mounts no disabled label (0 [data-disabled] nodes, counted); forced → reaches (rgb(1,2,3))'
} ,
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'link-preview' : {
uix(theming): correcciones de la revisión de F2-B — el empate no se resuelve de oficio
Auditoría de la sesión con medición. Cinco correcciones; la primera toca
código y es la que importa.
1. **`link-preview`: RESTAURADAS `font-family` y `line-height`.** Las retiré
como «declaraciones muertas» apoyándome en los precedentes de
`table.selected-row-fg` y del trigger de `gradient-picker`, pero **no son el
mismo caso**: allí perdían por ESPECIFICIDAD, aquí empatan a (0,1,0) con el
plano `[data-depth='overlay']` y decide el ORDEN DE CARGA. Retirarlas no es
neutral — **fija el empate a favor del plano** y con él su 1.25 donde la
receta pide `--leading-normal` (1.5), que es justo la decisión que §12.9
tiene pendiente para VEINTE componentes. Un empate por orden de carga se
registra, no se resuelve dentro de un commit de tokenización.
Y la medición era más débil de lo que el commit daba a entender: estable en
cinco recargas **en DEV**, sin verificar en un bundle de producción.
Restauradas → **0 diffs contra el estado PRE-SESIÓN** (550 valores × 5
tallas), censo de vuelta a 100 %, y las dos adjudicadas en el ledger con esa
razón. §12.9 y el README dicen ahora que el par se deja declarado A
PROPÓSITO.
2. **Marcas `R-5.x exception:` en 16 READMEs.** El gate del plan es «censo al
100 % o excepción escrita», y la válvula que `component-audit` lee es esa
marca (`readmeException`), no la prosa. Tenía la razón documentada en
veredicto, README y commit, pero sin la marca: cuando F3 active R-5.1/5.2,
los 16 habrían dado error. Una marca por clase medida — forward de paleta
THM-2 · conmutador de dos fuentes · eje propiedad de una capa · identidad de
layout. Los tres al 100 % (`code`, `label`, `link-preview`) no la necesitan.
3. **Continuidad restaurada.** El handoff seguía diciendo «45 %» y «empieza por
grid-list», y `PLAN §8` no tenía una sola línea de la sesión pese a que el
protocolo lo pide por componente: una sesión nueva habría arrancado con el
mapa viejo. Reescrita la agenda de `CONTINUE-theming.md` (cifras, la
revisión adversarial pendiente COMO PRIMERA TAREA, la cola restante, las 8
firmas), registrado el bloque en `PLAN §8` con su tabla de 22 commits y sus
seis lecciones, y actualizada la memoria del eje.
4. **`recipe-contract §4`** documenta las SEIS capacidades que R-5.4 ganó en el
bloque, cada una nombrando la forma en que el instrumento mentía, más la
lección que generaliza: una sonda que inspecciona pocos nodos pasa en falso
— `picker-shell` medía CERO y reportaba éxito.
5. **«un paso por debajo desde lg» → «desde md»** en `tree-view`,
`virtual-list` y `virtual-grid` (README + ficha). El escalón empieza en md
(md→sm); los valores eran correctos (0 diffs), el texto no.
Lo que esta revisión NO arregla, y queda como primera tarea del eje: **F2-B
sigue sin su revisión adversarial §7.7**. Sus 21 componentes llevan sólo la
medición de quien los escribió, y el plan la exige antes de cerrar un bloque.
El handoff nombra las dos hipótesis concretas que hay que atacar.
Gates: censo global 50 % · `--names` DESVIADAS 0 sobre 3.993 claves ·
`component:audit` 162 PASS / 4 NEEDS-WORK (los cuatro preexistentes, ninguno
con un commit de esta sesión en su código) · R-5.4 verde en los 22 ·
suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero
limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'font-family' :
"declared but out-ranked: the panel stamps `data-depth='overlay'`, whose plane declares font-family too at an EQUAL (0,1,0) — the generated sheet loads later, so it wins. Kept on purpose: retiring it would fix that tie in the plane's favour, a pixel decision pending signature (next-features §12.9). Measured with CDP matched-rules 2026-08-21, stable across 5 dev reloads" ,
'line-height' :
'same load-order tie with the `overlay` plane, which imposes 1.25 where this recipe asks for --leading-normal (1.5). Declared and kept until the signature resolves who owns overlay typography' ,
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'trigger-fg' :
'the panel opens on HOVER, so measuring it REQUIRES the pointer on the trigger — and hover is exactly the state whose rule overrides the resting ink. Structural, not a demo gap: the hover token (`hover-trigger-fg`) reads live in the same run' ,
'trigger-underline' :
'same structural conflict: the hover rule sets `text-decoration-color: currentColor` over the resting mix, and the panel cannot be opened without hovering'
} ,
uix(menu-dial): temable — 0 % → 25 %, y las ranuras de la capa que NO se acuñan
3 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 25 %.
**La receta posee sólo el cromo del dial**, como dice su propia cabecera: las
acciones son `Button` compuestos y la colocación flotante es de la capa
`viewport-placement`. Lo suyo son tres cosas: el hueco del abanico, el paso del
escalonado de entrada y el tinte del velo.
**`--_viewport-placement-offset` y `-z` NO se acuñan, y ésa es la decisión del
commit.** Son las RANURAS DE OVERRIDE que la capa compartida publica, y esta
receta escribe en ellas su decisión — 20px en vez de los 16px por defecto de la
capa, y el comentario del CSS deja claro que es decisión y no deriva. Acuñar
`menu-dial-placement-offset` sería el vocabulario paralelo que la regla 2 de
capas compartidas prohíbe: el eje es de la capa, y un tema lo mueve ahí. Es el
mismo techo que `listbox` paga por consumir bien `list-surface` — el grueso del
75 % que no alcanza, y es correcto que no lo haga (§13).
**El escalonado es tema, no motion**: `stagger-step` no es una duración de
animación sino el INTERVALO entre hermanos (multiplica el índice de cada
acción), y es lo que da carácter al abanico. Por eso entra al contrato.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.856 valores × 8 estados.
- R-5.4: 1/3, los dos mudos adjudicados con su medición — el velo sólo existe
con el dial abierto (0 nodos en reposo, contados) y el escalonado sólo se
aplica bajo `[data-stagger]` con la lista abierta; forzando ambos atributos
con índice 3, alcanza (0.084s → 2.331s = 3 × 777ms).
- censo --only 25 % · component:audit PASS · suite eidos sin rojos nuevos ·
rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (3 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'menu-dial' : {
'backdrop-bg' :
'the backdrop only exists while the dial is open (0 nodes at rest, counted); forced → reaches (rgb(1,2,3))' ,
'stagger-step' :
"consumed only by `[data-stagger] [data-state='open'] .menu-dial-action`, and the demo dial is closed and un-staggered; forced both attrs with index 3 → reaches (0.084s → 2.331s = 3 × 777ms)"
} ,
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía
31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global
48 %, sin contrato 43 → 42.
La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta».
Mirarlo cambió tres cosas del encargo:
1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de
list-surface o calendar-surface no hay hook de capa ni vocabulario prestado
— color-picker, date-picker y chronos importan sus `.svelte` directamente.
No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien.
2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre
`[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa
cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`,
que no casa nunca), así que las coordenadas por talla quedan como públicos
PLANOS y la receta las consume desde sus propios selectores. Es el único
componente del eje donde el patrón de nombres resueltos no aplica; la regla
base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de
tiempo lleva un TERCER eje, `data-size` sobre su propia clase.
3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0
nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la
vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker
anfitrión y detrás de su popover), y sus partes se llaman
`data-picker-header` / `-body` / `-footer` en vez de
`data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so
every picker gets the same visual contract for free» —, así que todo filtro
por prefijo del componente ve sólo la raíz.
CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía:
- Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos
reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0
diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**.
- Los knobs que la demo no monta —el header entero y la fila de tiempo— se
midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del
header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su
picker-size).
- El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES`
(prefijo de atributo + selector de apertura) acotado a este componente.
Verificado que los otros CATORCE con ledger no se mueven ni una cifra.
Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en
el ledger; los 25 que siguen mudos llevan cada uno su razón medida.
Header y footer comparten la regla que los separa del cuerpo → UN par de knobs
(`border-width` + `border`), no dos.
Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en
next-features §13 junto con la pregunta de fondo — si el precio de los nombres
genéricos (un contrato visual común para todos los pickers) compensa la ceguera
del instrumental — y con el eje portalado como caso de prueba si el TSC llega a
ganar un scope configurable.
Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido
skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio ·
`component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'picker-shell' : {
'section-gap-xs' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'section-gap-sm' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'section-gap-lg' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'row-spacing-xs' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'row-spacing-lg' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'footer-gap-xs' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'footer-gap-lg' :
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)' ,
'header-font-size-xs' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)' ,
'header-font-size-sm' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)' ,
'header-font-size-md' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)' ,
'header-font-size-lg' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)' ,
'header-gap' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)' ,
'header-fg' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)' ,
'header-font-weight' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)' ,
'header-line-height' :
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)' ,
'time-row-gap' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-row-padding-block-start' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-row-padding-inline' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-row-border-width' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-row-border' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-label-fg' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-label-font-size-xs' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-label-font-size-sm' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-label-font-size-md' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)' ,
'time-label-font-size-lg' :
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)'
} ,
uix(scroll-frames): temable — 0 % → 33 %, porque es LAYOUT y no cromo
4 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 33 %.
**Es layout, no cromo** — la receta lo dice de entrada y la medición lo
confirma: una pista alta cuyo viewport pegajoso fija el medio a sangre mientras
la pista rueda. Lo único que pinta es el rótulo de carga, y ése es el contrato
entero: su separación del borde inferior, el tipo, el tracking y la tinta.
**`--_scroll-frames-viewport-block-size` se queda privado**: es la escotilla
que el consumidor sobrescribe para encajar el viewport en un contenedor
(`100%`) en vez de en la ventana (`100vh`), y su fallback es geometría de
LAYOUT, no un valor de tema — quien lo mueve es el sitio de uso, no el tema.
Con los `100%` del medio y de la pista (identidad, D-TH.2), el `line-height: 1`
del rótulo y el `opacity` del sistema, forman el 67 % que no alcanza, y es
correcto que no lo haga.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 580 valores × 7 estados.
- R-5.4: 0/4, los cuatro adjudicados con su medición — el rótulo sólo existe
mientras se decodifican los fotogramas (0 nodos una vez cargado, contados) y
forzándolo los cuatro mueven (24→1234px, 12→99px, 1.2→9px, rgb(1,2,3)).
- censo --only 33 % · component:audit PASS · suite eidos sin rojos nuevos ·
rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (4 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'scroll-frames' : {
'loader-inset-block-end' :
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (24 → 1234px)' ,
'loader-font-size' :
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (12 → 99px)' ,
'loader-letter-spacing' :
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (1.2 → 9px)' ,
'loader-fg' :
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (rgb(1,2,3))'
} ,
uix(skeleton): temable — 0 % → 45 %, y dos privados que son conmutadores
9 claves fusionadas en el bloque `skeleton` de `recipes/base.ts` (donde ya
vivía el forward de paleta). Censo 0 % → 45 %, cero globales.
Nueve de sus once knobs pasaban por privado, y la lectura correcta es que **dos
de esos privados NO son deuda: son conmutadores**. `--_skeleton-bg` y
`--_skeleton-highlight` tienen DOS fuentes cada uno según haya `data-color` —
la mezcla neutra cross-mode (un `color-mix` hacia `neutral-solid`, para que el
placeholder siga siendo un gris visible en los dos modos: el `neutral-track`
desnudo desaparece sobre una superficie clara) o el forward de paleta.
Aplanarlos exigiría duplicar cada regla por color, así que se quedan — mismo
criterio que el borde de foco de textarea y la tinta de spinner. Son el grueso
del 55 % que no alcanza, y es honesto.
- **Una coordenada por talla** (`size`) gobierna el alto del bloque, el diámetro
del círculo (vía `aspect-ratio`) y el hueco entre líneas de la forma `text`.
Ese hueco es `calc(size * 0.5)`: una PROPORCIÓN, construcción, no un knob.
- **El radio es por FORMA, no por talla**: `radius-md` en `rect` y `50%` en
`circle`, donde el `50%` es identidad (un círculo es redondo) — el knob es
sólo el del rectángulo. La línea de la forma `text` lleva el suyo.
- `duration` entra al contrato en vez de seguir en `1.4s` a pelo, igual que se
hizo con el spinner (§13).
Artefactos y gates:
- Sonda antes/después = 0 diffs en 203 valores × 7 estados.
- R-5.4: 8/9. `line-radius` adjudicado: sólo la forma `text` renderiza
`[data-skeleton-line]` y la demo monta `rect` (0 líneas, contadas); forzada
la forma, alcanza (4 → 1234px).
- censo --only 45 % · component:audit PASS · suite eidos sin rojos nuevos ·
rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (9 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
skeleton : {
'line-radius' :
"only the `text` shape renders [data-skeleton-line]; the demo mounts `rect` (0 lines, counted). Forced shape='text' → reaches (4 → 1234px)"
} ,
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA
10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese
techo bajo es lo correcto: **es un componente de TONO FIJO**.
Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas
de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN,
no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga»,
dice su cabecera. La pintura vive en el SVG bajo la excepción que
`recipe-css-contract.test.ts` ya reconoce para natural-time-picker,
color-picker y proof-of-human.
Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values:
retune here, in one place»:
- **El ancho base es POR SKIN**, no un número: un casete no mide lo que un
tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y
escribió un solo `base-width`; el assert del script lo cazó al encontrar dos
declaraciones, y la corrección salió de volver a leer el fichero entero.
- La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él
estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo
vocabulario.
- Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue
enseñando su vidrio») y el radio del área de pulsación de las teclas.
Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera) y los cuatro `100%` de layout.
EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE
no prueba nada contra una propiedad capada**. El ancho es
`min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor
y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores
PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE
(133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela
igual al real lee como no efecto»: el centinela tiene que ser imposible **en la
dirección en que la propiedad puede moverse**, y un guard que probara los dos
extremos cazaría ambas familias.
Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió
a romperse por comillas simples dentro de cadenas de comillas simples
(`data-skin='record'`). Reescrito con comillas dobles, verificado con un import
real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código
de salida REAL**: cero fallos.
Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente,
que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 ·
docs:check 0 · check por fichero limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'skin-media-player' : {
'record-base-width' :
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, with data-skin=record forced (the demo mounts turntable) -> reaches (111px exactly)' ,
'cassette-base-width' :
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-skin=cassette forced -> reaches (122px exactly)' ,
'deck-base-width' :
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-skin=deck forced -> reaches (133px exactly)' ,
'scale-sm' :
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, with the data-size=sm of the PLAYER forced -> reaches (133 x 0.11 = 14.625px)' ,
'scale-lg' :
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-size=lg forced -> reaches (133 x 0.22 = 29.25px)' ,
'scale-xl' :
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-size=xl forced -> reaches (133 x 0.33 = 43.875px)' ,
'lamp-opacity' :
'the lamp only dims while the player is paused and the deck still; forced -> reaches (0.18 -> 0.123)'
} ,
uix(spinner): temable — 0 % → 70 %, y 71 tokens propuestos que eran 18
18 claves en el bloque `spinner` de `recipes/base.ts` (fusionadas con los dos
forwards de paleta). Censo 0 % → 70 %, cero globales. Global 48 %, sin contrato
44 → 43.
**La §4 proponía 71 tokens para 23 knobs — la peor inflación que ha dado el
generador en todo el eje.** El spinner tiene UNA coordenada de talla
(`--spinner-size`) que gobierna el anillo, la pista de puntos y las barras, más
el trazo (`--spinner-thickness`). La propuesta multiplicaba esa única
coordenada por variante × parte × dimensión × talla y producía
`ring-track-width-xs`, `dots-track-gap-xs`, `dots-dot-width-xs`,
`bars-track-gap-xs`… todos con el mismo `0.75rem`, porque todos SON el tamaño
en xs. Un tema habría necesitado veinte claves para cambiar un número.
1. Las proporciones son CONSTRUCCIÓN, no knobs: `calc(size * 0.3)` es el punto,
`* 0.18` la barra, `* 0.2` y `* 0.12` los huecos. Definen la FORMA de cada
variante y se quedan en la receta — mismo reparto que los gradientes de las
guías de tree-grid o el damero de gradient-builder.
2. `fg` / `dots-dot-bg` / `bars-bar-bg` ⚠ no eran tres knobs con dos valores en
pugna: son UNA variable (`--_spinner-color`) leída en tres sitios, con el
conmutador `data-color='inherit'` que la cambia de forward de paleta a
`currentColor`. Se queda privada, igual que `--_spinner-track`, que además
envuelve su fuente en un color-mix distinto por rama (60 % / 25 %).
Aplanarlas exigiría duplicar cada regla por color.
3. La escala es PROPIA, no la del bundle de iconos: medido, 12/16/20/28/40 px
contra los 14/16/18/20/32 de `--icon-size-*`, y sólo coinciden en sm.
Verbatim con la desviación anotada — forzar el bundle habría movido el
default en cuatro tallas de cinco.
4. `duration` entra al contrato en vez de seguir en `0.9s` a pelo: el registro
ya tenía abierta la incoherencia de que el spinner de feed ganó tokens de
duración y el de media-player no (§13). Aquí se hace bien de entrada.
El 30 % que falta son los dos privados conmutadores, los dos
`border-radius: 50%` (identidad de forma) y los fotogramas de los @keyframes.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 377 valores × 7 estados. Determinismo
verificado.
- R-5.4: 14/18. Los cuatro mudos, medidos uno a uno y adjudicados: la demo
monta SÓLO la variante ring y sin etiqueta (0 barras, 0 puntos, 0 labels,
contados), y `track-opacity` sólo se declara dentro de
`prefers-reduced-motion` — forzando variante, etiqueta y contexto reduce,
los cuatro alcanzan.
- censo --only 70 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check
0 · docs:check 0 · check por fichero limpio.
- README con su «Talla y tema» y tab `Tokens` en la demo (18 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
spinner : {
'bar-radius' :
"the demo mounts the RING variant only (0 bars, 0 dots, counted); forced variant='bars' → reaches (1234px)" ,
'label-font-size' :
'the label is optional and the demo mounts none (0 nodes, counted); forced → reaches (1234px)' ,
'label-fg' : 'the label is optional and the demo mounts none; forced → reaches (rgb(1,2,3))' ,
'track-opacity' :
'its only declaration lives inside `prefers-reduced-motion`; emulated reduce context → reaches (0.6 → 0.123)'
} ,
uix(card-group): temable — 0 % → 90 %, y dos privados abreviados que mueren
20 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 90 %, cero
globales y cero privados. Global 47 %, sin contrato 45 → 44.
Es un CASCO de composición y su propia cabecera ya lo decía: compone
ToggleGroup, Collapsible, Card y Button, y la receta posee «ONLY the genuinely
CardGroup-level visual». Eso acota el contrato antes de nombrar nada — el cromo
de las tarjetas es de Card, el del título de disclosure es de Button.
1. **`--_cg-pad` y `--_cg-card-radius` eran privados ABREVIADOS** (§6 r5 los
prohíbe: `cg` por `card-group`), el mismo defecto que los `--_mp-*` de
media-player. Pasar a públicos con el nombre completo es a la vez el
renombrado que la regla pedía y la tokenización. La §3 de la ficha decía
«la receta no declara privados propios»: los declaraba en su bloque raíz
(`card-group.css:15-16`), y el escáner no los veía por buscarlos con el
prefijo completo del componente.
2. **`--shape-outer-radius` NO se acuña: se CALCULA.** La receta lo compone
como `card-radius + padding` para que las tarjetas aniden concéntricamente
(§30). Un tema mueve las dos coordenadas y el radio exterior se recompone
solo; el censo ya lo clasifica como sistema.
3. **El eje `size` es sm|md|lg, no las cinco tallas**
(`Extract<Size, 'sm' | 'md' | 'lg'>`): seis coordenadas por talla, no diez.
Escribir xs/xl habría sido vocabulario muerto.
4. Los tres `padding` son shorthand con una parte fija y otra variable; lo que
varía y no es el inset del grupo es el hueco inferior →
`title-padding-block-end` y `description-padding-block-end`.
5. El chevron es geometría propia (dos bordes girados 45°): `chevron-size`
cubre las dos dimensiones y `chevron-width` el trazo. Su `currentColor`
hereda del título a propósito y no es knob.
6. Los `font-size` van al BUNDLE (`--size-{k}-font-size`, alias 1:1) y no al
primitivo crudo — misma trampa que cayó en code-block: la propuesta copia el
CSS verbatim y hereda el incumplimiento cuando el valor de partida ya
violaba `recipe-css-contract`. Aquí se aplicó de entrada.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 2.581 valores × 7 estados. Determinismo
verificado: dos corridas, 0 diffs.
- R-5.4: 15/20. Los cinco mudos son los del encabezado ESTÁTICO, que la demo no
monta (0 nodos `[data-static]` contra 1 `[data-button]`, CONTADOS); forzado
`data-static` alcanzan los cinco, y cada uno lleva su medición en el ledger.
El hueco de demo queda registrado en next-features §13, misma clase que el
`kind='month'|'year'` de date-range-picker.
- censo --only 90 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check
0 · docs:check 0 · check por fichero limpio.
- README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'card-group' : {
'title-fg' :
'the STATIC heading form is not mounted in the demo (0 [data-static] nodes vs 1 [data-button], counted); forced → reaches (rgb(1,2,3))' ,
'title-font-family' :
'static heading not mounted in the demo; forced → reaches (Instrument Sans → Zapfino)' ,
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA
10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese
techo bajo es lo correcto: **es un componente de TONO FIJO**.
Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas
de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN,
no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga»,
dice su cabecera. La pintura vive en el SVG bajo la excepción que
`recipe-css-contract.test.ts` ya reconoce para natural-time-picker,
color-picker y proof-of-human.
Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values:
retune here, in one place»:
- **El ancho base es POR SKIN**, no un número: un casete no mide lo que un
tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y
escribió un solo `base-width`; el assert del script lo cazó al encontrar dos
declaraciones, y la corrección salió de volver a leer el fichero entero.
- La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él
estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo
vocabulario.
- Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue
enseñando su vidrio») y el radio del área de pulsación de las teclas.
Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera) y los cuatro `100%` de layout.
EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE
no prueba nada contra una propiedad capada**. El ancho es
`min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor
y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores
PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE
(133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela
igual al real lee como no efecto»: el centinela tiene que ser imposible **en la
dirección en que la propiedad puede moverse**, y un guard que probara los dos
extremos cazaría ambas familias.
Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió
a romperse por comillas simples dentro de cadenas de comillas simples
(`data-skin='record'`). Reescrito con comillas dobles, verificado con un import
real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código
de salida REAL**: cero fallos.
Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente,
que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 ·
docs:check 0 · check por fichero limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'title-font-size' : 'static heading not mounted in the demo; forced → reaches (16 → 1234px)' ,
'title-line-height' : 'static heading not mounted in the demo; forced → reaches (20px → 3.77em)' ,
uix(card-group): temable — 0 % → 90 %, y dos privados abreviados que mueren
20 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 90 %, cero
globales y cero privados. Global 47 %, sin contrato 45 → 44.
Es un CASCO de composición y su propia cabecera ya lo decía: compone
ToggleGroup, Collapsible, Card y Button, y la receta posee «ONLY the genuinely
CardGroup-level visual». Eso acota el contrato antes de nombrar nada — el cromo
de las tarjetas es de Card, el del título de disclosure es de Button.
1. **`--_cg-pad` y `--_cg-card-radius` eran privados ABREVIADOS** (§6 r5 los
prohíbe: `cg` por `card-group`), el mismo defecto que los `--_mp-*` de
media-player. Pasar a públicos con el nombre completo es a la vez el
renombrado que la regla pedía y la tokenización. La §3 de la ficha decía
«la receta no declara privados propios»: los declaraba en su bloque raíz
(`card-group.css:15-16`), y el escáner no los veía por buscarlos con el
prefijo completo del componente.
2. **`--shape-outer-radius` NO se acuña: se CALCULA.** La receta lo compone
como `card-radius + padding` para que las tarjetas aniden concéntricamente
(§30). Un tema mueve las dos coordenadas y el radio exterior se recompone
solo; el censo ya lo clasifica como sistema.
3. **El eje `size` es sm|md|lg, no las cinco tallas**
(`Extract<Size, 'sm' | 'md' | 'lg'>`): seis coordenadas por talla, no diez.
Escribir xs/xl habría sido vocabulario muerto.
4. Los tres `padding` son shorthand con una parte fija y otra variable; lo que
varía y no es el inset del grupo es el hueco inferior →
`title-padding-block-end` y `description-padding-block-end`.
5. El chevron es geometría propia (dos bordes girados 45°): `chevron-size`
cubre las dos dimensiones y `chevron-width` el trazo. Su `currentColor`
hereda del título a propósito y no es knob.
6. Los `font-size` van al BUNDLE (`--size-{k}-font-size`, alias 1:1) y no al
primitivo crudo — misma trampa que cayó en code-block: la propuesta copia el
CSS verbatim y hereda el incumplimiento cuando el valor de partida ya
violaba `recipe-css-contract`. Aquí se aplicó de entrada.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 2.581 valores × 7 estados. Determinismo
verificado: dos corridas, 0 diffs.
- R-5.4: 15/20. Los cinco mudos son los del encabezado ESTÁTICO, que la demo no
monta (0 nodos `[data-static]` contra 1 `[data-button]`, CONTADOS); forzado
`data-static` alcanzan los cinco, y cada uno lleva su medición en el ledger.
El hueco de demo queda registrado en next-features §13, misma clase que el
`kind='month'|'year'` de date-range-picker.
- censo --only 90 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check
0 · docs:check 0 · check por fichero limpio.
- README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'title-padding-block-end' :
'static heading not mounted in the demo; forced → reaches (8 → 1234px)'
} ,
uix(clipboard): temable — 0 % → 63 %, todo en la píldora de «Copied!»
6 claves fusionadas en el bloque `clipboard` de `recipes/base.ts` (donde ya
vivían los dos forwards de paleta). Censo 0 % → 63 %.
**Todo lo tokenizable es el INDICADOR.** El host va `display: contents` y el
trigger reusa la receta de `Button` por el patrón de consumidor, así que la
receta sólo posee la píldora que aparece un instante tras copiar.
- Los dos forwards de paleta siguen privados (THM-2): el fondo y la tinta del
chip los leen a pelo, sin sitio donde meter un público sin duplicar la regla
— son el 37 % que no alcanza.
- `font-family` queda fuera por la misma razón que en code-block, kbd y
announce: lo posee la capa tipográfica.
- El padding es un shorthand de dos valores → dos knobs lógicos.
- **Los `--motion-*` no son knobs de tema**: son los ganchos con que el
indicador afina el preset `scale-fade` del canal de motion (escala 0.85,
rápida). Pertenecen al canal, no al contrato, y la política de
`prefers-reduced-motion` la emite el generador.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.392 valores × 8 estados.
- R-5.4: 0/6, los seis adjudicados con su medición — el indicador sólo existe
un instante tras copiar (0 nodos en reposo, contados) y forzándolo los seis
mueven su propiedad (gap 4→1234px, padding 2/8→1235/1236px, radio
9999→1237px, tipo 12→99px, peso 500→123).
- censo --only 63 % · component:audit PASS · suite eidos sin rojos nuevos ·
rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (6 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
clipboard : {
'indicator-gap' :
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (4 → 1234px)' ,
'indicator-padding-block' :
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (2 → 1235px)' ,
'indicator-padding-inline' :
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (8 → 1236px)' ,
'indicator-radius' :
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (9999 → 1237px)' ,
'indicator-font-size' :
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (12 → 99px)' ,
'indicator-font-weight' :
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (500 → 123)'
} ,
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio
6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el
forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera
del contrato.
**Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo
tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la
escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la
forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su
capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`.
- El padding es em-relativo a propósito (la píldora crece con el código que
envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje,
no cuatro.
- `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido
en línea / forward de paleta cuando llega `data-color`), mismo patrón que el
borde de foco de textarea.
- El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la
base es RESET: la variante `plain` es texto desnudo por identidad.
EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado):
Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`,
`padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta
**(0,1,0)**. En la página de documentación el radio del componente es el del
sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que
el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso
aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de
code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los
dos componentes.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo
verificado.
- R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness
(CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin
cromo; forzando las variantes, los cinco alcanzan.
- censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 ·
check por fichero limpio · suite eidos sin rojos nuevos.
- README con «Talla y tema» y tab `Tokens` en la demo (6 claves).
Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS
inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por
bueno porque el `$?` capturado era el del `grep` de la tubería, no el del
guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando
el código de salida REAL: cero fallos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
code : {
radius :
'the DEMO HARNESS wins: `[data-uix-docs] code` (uix.css, 0-1-1) beats the recipe base rule `[data-code]` (0-1-0) and sets border-radius/padding/font on the inline code — confirmed with CDP matched-rules. The variant rules (0,2,0) DO win, which is why only this base-rule token reads dead. Same harness rule that hides the <pre> of code-block (next-features §13)' ,
'soft-bg' :
"the demo mounts the `plain` variant only (no chrome); forced variant='soft' → reaches (rgb(1,2,3))" ,
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA
10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese
techo bajo es lo correcto: **es un componente de TONO FIJO**.
Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas
de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN,
no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga»,
dice su cabecera. La pintura vive en el SVG bajo la excepción que
`recipe-css-contract.test.ts` ya reconoce para natural-time-picker,
color-picker y proof-of-human.
Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values:
retune here, in one place»:
- **El ancho base es POR SKIN**, no un número: un casete no mide lo que un
tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y
escribió un solo `base-width`; el assert del script lo cazó al encontrar dos
declaraciones, y la corrección salió de volver a leer el fichero entero.
- La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él
estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo
vocabulario.
- Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue
enseñando su vidrio») y el radio del área de pulsación de las teclas.
Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera) y los cuatro `100%` de layout.
EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE
no prueba nada contra una propiedad capada**. El ancho es
`min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor
y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores
PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE
(133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela
igual al real lee como no efecto»: el centinela tiene que ser imposible **en la
dirección en que la propiedad puede moverse**, y un guard que probara los dos
extremos cazaría ambas familias.
Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió
a romperse por comillas simples dentro de cadenas de comillas simples
(`data-skin='record'`). Reescrito con comillas dobles, verificado con un import
real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código
de salida REAL**: cero fallos.
Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente,
que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 ·
docs:check 0 · check por fichero limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'border-width' : "the demo mounts `plain` only; forced variant='outline' → reaches (1 → 9px)" ,
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio
6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el
forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera
del contrato.
**Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo
tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la
escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la
forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su
capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`.
- El padding es em-relativo a propósito (la píldora crece con el código que
envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje,
no cuatro.
- `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido
en línea / forward de paleta cuando llega `data-color`), mismo patrón que el
borde de foco de textarea.
- El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la
base es RESET: la variante `plain` es texto desnudo por identidad.
EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado):
Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`,
`padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta
**(0,1,0)**. En la página de documentación el radio del componente es el del
sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que
el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso
aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de
code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los
dos componentes.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo
verificado.
- R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness
(CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin
cromo; forzando las variantes, los cinco alcanzan.
- censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 ·
check por fichero limpio · suite eidos sin rojos nuevos.
- README con «Talla y tema» y tab `Tokens` en la demo (6 claves).
Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS
inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por
bueno porque el `$?` capturado era el del `grep` de la tubería, no el del
guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando
el código de salida REAL: cero fallos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
border : "the demo mounts `plain` only; forced variant='outline' → reaches (rgb(4,5,6))" ,
'padding-inline' :
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA
10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese
techo bajo es lo correcto: **es un componente de TONO FIJO**.
Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas
de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN,
no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga»,
dice su cabecera. La pintura vive en el SVG bajo la excepción que
`recipe-css-contract.test.ts` ya reconoce para natural-time-picker,
color-picker y proof-of-human.
Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values:
retune here, in one place»:
- **El ancho base es POR SKIN**, no un número: un casete no mide lo que un
tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y
escribió un solo `base-width`; el assert del script lo cazó al encontrar dos
declaraciones, y la corrección salió de volver a leer el fichero entero.
- La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él
estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo
vocabulario.
- Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue
enseñando su vidrio») y el radio del área de pulsación de las teclas.
Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto
dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es
madera) y los cuatro `100%` de layout.
EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE
no prueba nada contra una propiedad capada**. El ancho es
`min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor
y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores
PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE
(133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela
igual al real lee como no efecto»: el centinela tiene que ser imposible **en la
dirección en que la propiedad puede moverse**, y un guard que probara los dos
extremos cazaría ambas familias.
Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió
a romperse por comillas simples dentro de cadenas de comillas simples
(`data-skin='record'`). Reescrito con comillas dobles, verificado con un import
real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código
de salida REAL**: cero fallos.
Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente,
que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 ·
docs:check 0 · check por fichero limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'only the chromed variants pad; forced → reaches (4.12 → 77px), and the variant rule (0,2,0) beats the harness' ,
'padding-block' : 'only the chromed variants pad; forced → reaches (1.29 → 66px)'
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio
6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el
forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera
del contrato.
**Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo
tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la
escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la
forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su
capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`.
- El padding es em-relativo a propósito (la píldora crece con el código que
envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje,
no cuatro.
- `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido
en línea / forward de paleta cuando llega `data-color`), mismo patrón que el
borde de foco de textarea.
- El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la
base es RESET: la variante `plain` es texto desnudo por identidad.
EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado):
Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`,
`padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta
**(0,1,0)**. En la página de documentación el radio del componente es el del
sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que
el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso
aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de
code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los
dos componentes.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo
verificado.
- R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness
(CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin
cromo; forzando las variantes, los cinco alcanzan.
- censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 ·
check por fichero limpio · suite eidos sin rojos nuevos.
- README con «Talla y tema» y tab `Tokens` en la demo (6 claves).
Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS
inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por
bueno porque el `$?` capturado era el del `grep` de la tubería, no el del
guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando
el código de salida REAL: cero fallos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
} ,
uix(code-block): temable — 0 % → 81 %, y la demo que no enseña el componente
20 claves en una entrada NUEVA de `recipes/base.ts` (no tenía). Censo 0 % →
81 %. Global 46 % → 47 %, sin contrato 46 → 45.
El handoff lo llamaba «el más mecánico de los que quedan». No lo es: sus 26
knobs se parten en dos grupos con doctrinas distintas.
1. **El nodo del código NO acuña nada, y la §4 pedía que lo hiciera.**
`[data-code-block-code]` consume la capa tipográfica `--style-code-*` en sus
cinco declaraciones, y las dos que pasan por privado usan la forma canónica
de la escotilla por instancia
(`var(--_code-block-font-size, var(--style-code-font-size))`, escrita por el
wrapper desde la prop `size` y sólo si llega). Es el patrón que D-TH.2-b
fijó para heading/text/code. Acuñar `code-font-family` y compañía sería el
vocabulario paralelo que la regla 2 de capas prohíbe. Todo lo que entra al
contrato es el CASCO alrededor.
2. **Los tres `font-size` iban al primitivo crudo, y lo cazó un guard.** La
propuesta copia el valor del CSS verbatim, y ese valor ya violaba
`recipe-css-contract` («recipes consume the size bundle, not the raw
size-coordinate primitives»): `title/lang/copy-font-size` corregidos a
`var(--size-{k}-font-size)`. Los alias son 1:1 → 0 diffs tras aplicarlo.
Aviso general: cuando el CSS de partida ya incumple el contrato, la
propuesta generada hereda el incumplimiento.
3. Shorthand partidos: los dos `border` y el `border-bottom` de la cabecera
comparten anchura y color → `border-width` + `border`; el `padding` del
`<pre>` → `pre-padding`. Los `transparent` de outline/ghost y el `border: 0`
de ghost son identidad de variante.
El 19 % que falta son esas cinco de la capa, que el censo cuenta como `global`
porque `code-block` no está en `TYPOGRAPHIC_PRIMITIVES` — es un compuesto que
CONTIENE un primitivo tipográfico, no uno de ellos. Extender la clase `system`
del censo a las capas sigue pendiente de tu firma; la métrica penaliza hacer lo
correcto y no se fuerza de oficio.
DOS HALLAZGOS QUE NO SE ARREGLAN AQUÍ (→ next-features §13):
- **La demo de `code-block` no enseña el `code-block`.** `[data-uix-docs] pre`
(`web/routes/uix/uix.css:801`) pesa (0,1,1) contra los (0,1,0) de
`[data-code-block-pre]`, y `[data-uix-docs]` es el shell de TODAS las
páginas. Siete propiedades del `<pre>` las pinta el harness y no la receta:
padding (16px 20px contra 12px), border (1px contra ninguno), radius (8px
contra 0), background (gris contra transparente), font-family, font-size
(13 contra 14) y line-height (18.2 contra 20.3). El fondo, el borde y el
radio que se ven ahí son del sitio. Por eso `pre-padding` lee muerto en el
guard, adjudicado con esa medición. Acotar la regla toca el CSS del SITIO,
que sirve a ~162 páginas: decisión aparte.
- **El guard de claves duplicadas del handoff inspeccionaba el VACÍO.** El
comando documentado usa `grep -oE "^\t…"`, y `grep -E` lee `\t` como una `t`
literal: 0 coincidencias sobre 130 bloques reales, así que salía «vacío = OK»
sin mirar nada — el antipatrón que el proyecto tiene registrado, dentro del
propio handoff. Comprobado con un contador fiable: cero duplicados en el
árbol, así que no hay daño en los commits que confiaron en él. CONTINUE
queda corregido con una versión que CUENTA los bloques y avisa de que «0
bloques» significa guard roto, no fichero limpio.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.305 valores × 7 estados (dos veces: tras
la tokenización y tras la corrección del bundle). Determinismo verificado.
- R-5.4: 19/20; `pre-padding` adjudicado con las siete propiedades medidas.
- censo --only 81 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos de vuelta al único rojo conocido (skin-media-player) tras
corregir el bundle · rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'code-block' : {
'pre-padding' :
'the DEMO HARNESS outweighs the component: `[data-uix-docs] pre` (uix.css:801, 0-1-1) beats `[data-code-block-pre]` (0-1-0) and paints padding/border/radius/bg/font on the <pre>. Forced with !important → reaches (16px 20px → 12px). Seven properties measured, registered in next-features §13'
} ,
uix(tree-view): temable — 0 % → 92 %, y el velo que tiñe la carpeta entera
46 claves en el bloque `tree-view` de `recipes/base.ts` (fusionadas con el
forward de paleta). Censo 0 % → 92 %, cero globales. Global 46 %, sin contrato
47 → 46. Molde del hermano `tree-grid` allí donde el problema es el mismo.
LA PROPUESTA GENERADA FALLABA EN EL NOMBRE DE CASI TODO LO QUE IMPORTA: el
clasificador toma el PRIMER selector de cada regla como si fuera la parte
pintada, y aquí ese primero es el root o el branch-control cuando el nodo real
es otro.
1. `root-width: 1px` / `root-bg` NO son del root: son la GUÍA de indentación
(el `::before` del branch, una línea vertical). → `guide-width` + `guide-fg`,
literalmente los nombres de tree-grid. Un `root-bg` que tiñe una línea es la
clase de nombre que miente.
2. `branch-control-padding-inline-{k}` ⚠ fundía DOS ejes que se SUMAN en el
mismo calc: el ritmo de la fila y el PASO DE PROFUNDIDAD
(`--tree-depth × indent + padding`). Fundidos, tematizar la sangría habría
movido el padding. → `row-padding-inline-{k}` + `indent-{k}`.
3. El prefijo `branch-control-` es erróneo: la regla reza
`[data-tree-view-branch-control], [data-tree-view-item]` — las DOS filas. El
CSS ya llamaba `row` a sus privados. → `row-*`, como tree-grid y como la
lección de table. Y fuera el prefijo `root-` del chasis: el wrapper ES el
componente.
4. `row-padding-block` ⚠ no eran dos valores en pugna sino cinco por talla: xs
colapsa a `0`, las otras cuatro `--space-1`.
5. `branch-indicator-width` + `-height` son UN knob (`1em`) →
`branch-indicator-size`.
Bundle: la altura de fila casa 1:1; padding e indent llevan ritmo propio; la
tipografía un paso por debajo desde lg — verbatim, como table y tree-grid. Los
resueltos con `parts: ['root']` (el `data-size` va en `[data-tree-view-root]`,
wrapper de eidos, no parte del morfo). Los `transparent` de outline/ghost y el
`padding: 0` de ghost son identidad de variante y siguen literales.
`hover-row-bg` SÍ se acuña, al revés que en grid-list, y por medición: la misma
regla pinta dos nodos con arquetipo distinto, y `branch-control` NO lleva
archetype — ahí el plano de la receta es la única pintura (velo medido ausente)
y el token es toda la superficie de tema del hover. Tener un consumidor
legítimo es lo que lo distingue del caso grid-list.
EL DEFECTO QUE LA MEDICIÓN DESTAPÓ (preexistente, NO se toca aquí →
next-features §12.8): al pasar el ratón por la fila de una carpeta abierta se
tiñe LA CARPETA ENTERA, hijos incluidos. `archetype: 'item'` está declarado en
el `<li>` branch (`morfo/components/tree-view.ts:103`), que contiene el control
Y todo el `branch-content`, mientras la fila que el usuario señala es el
`branch-control` de dentro: el nodo velado mide 336 px frente a los 36 px de la
fila — medido y capturado. Un branch anidado acumula además el velo de sus
ancestros, así que sus filas se ven más oscuras que las hermanas. Mover el
arquetipo es morfo y mueve píxel en toda la demo. Es la TERCERA variante de la
misma familia: table lo tiene en fila Y celda, grid-list en fila Y celda sobre
el mismo nodo, y tree-view en un ANCESTRO del nodo señalado.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 8.062 valores × 7 estados.
- R-5.4: 45/46; `disabled-row-fg` adjudicado en el ledger (la demo monta CERO
filas deshabilitadas, contadas; forzada alcanza oklch(0.61 0 0) → rgb(1,2,3)).
- censo --only 92 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0
· suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0
· docs:check 0 · check por fichero limpio (72 globales, ninguno mío).
- README con su «Talla y tema» y tab `Tokens` en la demo (46 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'tree-view' : {
'disabled-row-fg' :
'no disabled row in the demo (0 mounted, counted); forced data-disabled → reaches (oklch(0.61 0 0) → rgb(1,2,3))'
} ,
uix(password-field): temable — 41 % → 62 %, y el guard queda EN VERDE
7 claves (23 → 30). Censo password-field 41 % → **62 %**; el centinela pasa a
verde con **las siete adjudicaciones medidas**, incluidas las tres que este
componente arrastraba sin medir.
Cosido: el tracking del input enmascarado —los puntos leen mejor algo sueltos—,
el chip de bloq-mayús (radio y peso; su peso llevaba un `500` muerto detrás de un
token que ya vale 500) y **los cuatro peldaños del medidor de fuerza**.
LOS CUATRO PELDAÑOS SON UNA ESCALA DE INTENCIÓN, no cuatro colores sueltos: cada
uno lee su rol —pérdida, riesgo, cumplimiento, afirmación— y ahora un tema puede
recorrer la escala entera sin tocar los roles del sistema. De paso muere otra
referencia con respaldo: el primer peldaño leía
`var(--color-loss-solid, var(--color-threat-solid))` y el primitivo SÍ existe, así
que el respaldo era ruido.
LO QUE NO SE ACUÑA, Y ES COMPOSICIÓN: el `padding-inline` del medidor y el
`font-size` de su rótulo leen privados de FIELD (`--_field-control-*`), porque
este componente monta un Field. Se teman en la ficha del dueño — mandato de
composición, `theming-audit` §5.3-3.
Siete adjudicaciones, todas medidas: los cuatro peldaños sólo pintan bajo su
`data-strength` y la demo arranca con el campo vacío (fuerza 0, ningún segmento
activo); las dos de transición las congela el guard; y la opacidad deshabilitada
vive en el disparador de visibilidad, no en la raíz — por eso mi primera medición
dio «sin efecto» y hubo que buscar el nodo que la regla señala.
Gates: sonda antes/después **0 diffs en 3.060 valores** (14 nodos × 8 estados) ·
centinela 23/30 con las siete adjudicadas, exit 0 · censo --only 62 % · `--names`
sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (campo,
medidor de cuatro segmentos y rótulo en su sitio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'password-field' : {
// The demo boots with an empty field: data-strength=0, so no segment is
// active and none of the four steps paints. Forced level by level
// 2026-08-22, transitions frozen.
'strength-1-bg' : 'the meter sits at strength 0 (empty field); forced -> reaches (oklch(0.5787 0.1877 322.11) -> rgb(1,2,3))' ,
'strength-2-bg' : 'same; forced -> reaches (oklch(0.6908 0.1909 45.02) -> rgb(1,2,3))' ,
'strength-3-bg' : 'same; forced -> reaches (oklch(0.6406 0.1329 157.68) -> rgb(1,2,3))' ,
'strength-4-bg' : 'same; forced -> reaches (oklch(0.6491 0.1136 181.96) -> rgb(1,2,3))' ,
// The visibility trigger owns both: the guard freezes transitions, and the
// demo boots enabled. Measured on it 2026-08-22.
'transition-duration' : 'the guard freezes transitions; measured without the freeze on the visibility trigger -> reaches (0.12s -> 11.5s)' ,
'transition-ease' : 'same freeze, same node -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(4))' ,
'disabled-opacity' : 'only under [data-disabled] on the visibility trigger; forced -> reaches (0.4 -> 0.123)'
} ,
uix(context-menu): temable — 53 % → 90 %, y el instrumento aprende a abrir un menú contextual
11 claves (18 → 29). Censo context-menu 53 % → **90 %** con `global` a CERO.
EL INSTRUMENTO NO SABÍA ABRIRLO. Un menú contextual se abre con clic DERECHO y
con nada más, así que sonda y guard medían UN nodo: el disparador. Los dos
aprenden el modo (`openBy: 'contextmenu'` en `COMPONENT_OVERRIDES`, clic derecho
en la sonda) y pasan a ver **14 nodos** en el estado abierto; el guard, de un
baremo ciego a **27/29**.
Cosido el cromo propio del panel y del disparador: tipografía y tinta del
contenido, tinta e interlineado de la fila, el galón del submenú (dos bordes
girados 45°, un solo trazo), el encabezado de grupo (tinta, peso y tracking de
versalitas) y el contorno DISCONTINUO que marca el disparador abierto — el estilo
`dashed` es identidad de la afordancia; el grosor y el color son knobs.
Dos limpiezas de paso: el `font-family` leía
`var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí
mismo— y el peso del encabezado llevaba un `500` muerto detrás de un token que ya
vale 500.
DOS ADJUDICACIONES MEDIDAS, y una es la TERCERA ocurrencia del mismo hallazgo:
`content-font-family` no puede ganar porque el plano `overlay` impone la
tipografía del panel (§12.9, pendiente de tu firma — ya medido en `popover` y en
`float-panel`). La otra es el ítem deshabilitado, que la demo no monta: forzado,
0.55 → 0.123.
Gates: sonda antes/después **0 diffs en 600 valores** (8 estados, 14 nodos en el
abierto) · centinela 27/29 · censo --only 90 % · `--names` sin desviadas ·
component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo
conocido · rtl:check 0 · docs:check 0 · captura del panel mirada (encabezado en
versalitas, filas, casillas, galón del submenú y separadores en su sitio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'context-menu' : {
// Third measurement of the same finding: the overlay DEPTH PLANE imposes the
// panel font and beats the recipe (section 12.9, pending signature, 20
// components). Writing the token moves nothing while the plane does.
'item-disabled-opacity' : 'no disabled item in the demo; forced data-disabled -> reaches (0.55 -> 0.123)'
} ,
uix(float-panel): temable — 59 % → 84 %, muere un fantasma y queda una anomalía registrada
12 claves (28 → 40). Censo float-panel 59 % → **84 %** con `global` a CERO.
Cosido: las dos VARIANTES que ajustan desde el default elevado (`outline-*`,
`ghost-*`), los botones de su barra (`action-*`, `hover-action-*`) y el asa de
redimensionado.
MUERE UNO DE LOS NUEVE FANTASMAS DEL BARRIDO de esta mañana: el asa leía
`var(--color-content-tertiary, var(--color-content-secondary))` y ese primitivo
NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad
pintaba, con lo que la referencia muerta desaparece sin mover un píxel.
ONCE ADJUDICACIONES MEDIDAS: la demo monta la variante por DEFECTO —ni outline ni
ghost— y no tiene botón de acción genérico, sólo el control de cierre; se forzó
el atributo de variante y se montó el botón donde el CSS lo selecciona, y el
radio del asa sólo pinta bajo `:focus-visible`. Las once alcanzan.
Y UNA ANOMALÍA REGISTRADA EN VEZ DE ESCONDIDA: `resize-grip-fg` **mueve** bajo
una réplica a mano de la secuencia del propio guard —congelar transiciones, blur,
aparcar el ratón, escribir en `:root` y en cada nodo con el prefijo, fotografiar
los mismos props con `::before`/`::after`/`::placeholder`— (oklch(0.5032 0 0) →
rgb(1,2,3)), y aun así el guard lo da por muerto. Descartado que el nodo no lleve
el atributo (lo lleva), que la regla necesite un estado (es la regla base), que
sea el valor centinela (es el mismo) y que sea HMR (dos corridas). Queda
adjudicado CON la medición y marcado como anomalía en §13: el token está vivo, lo
que está en duda es la lectura. Es por donde debe empezar la revisión adversarial
de este bloque — el precedente de F2-A dice que estos casos sí tienen causa.
Lo rojo que queda son 12 tokens anteriores, y seis de ellos —`bg`, `border`,
`border-width`, `shadow`, `font-family`, `line-height`— son el mismo conjunto que
en `popover`: el PLANO de profundidad gana sobre el panel (§12.9, pendiente de
firma).
Gates: sonda antes/después **0 diffs en 1.890 valores** (9 nodos × 7 estados) ·
censo --only 84 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0
invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 ·
captura mirada (cabecera, botón de cierre y asa de la esquina en su sitio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'float-panel' : {
// The demo mounts the DEFAULT variant and no generic action button (only the
// close control). Forced in place 2026-08-22, transitions frozen.
'outline-bg' : 'the demo runs the default variant; set data-variant=outline -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))' ,
'outline-border' : 'same variant; forced -> reaches (oklch(0.7921 0 0) -> rgb(1,2,3))' ,
'ghost-bg' : 'same, variant ghost; forced -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))' ,
'ghost-border' : 'same, variant ghost; forced -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))' ,
'ghost-shadow' : 'same, variant ghost; forced -> reaches (rgba(15,23,42,0.1) 0 4px 12px -> rgb(1,2,3) 0 0 0 7px)' ,
'action-fg' : 'no generic action button in the demo (only the close control); mounted in place -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
'action-radius' : 'same button; mounted -> reaches (4px -> 1234px)' ,
'action-font-size' : 'same button; mounted -> reaches (16px -> 1234px)' ,
'hover-action-bg' : 'same button, and only under :hover; same mounting pass' ,
'hover-action-fg' : 'same button, and only under :hover; same mounting pass' ,
fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde
El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.
LOS DOCE, MEDIDOS UNO A UNO
SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).
⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.
TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).
UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).
DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).
DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN
- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
al mismo scrollY). La causa real es que el guard NO fotografía translate ni
transform — registrado en §13 junto al hueco de mask-image, para entrar en
PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
`cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.
La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).
Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'resize-grip-radius' : 'only under :focus-visible; focused the grip -> reaches (4px -> 1234px)' ,
// 2026-08-23. The twelve the 2026-08-22 commit left written as "rojo que
// queda" and nobody measured. Measured now, one by one, on the real panel.
//
// SIX are the depth plane, exactly as that commit predicted: the content
// carries `data-depth='overlay'`, and `[data-depth='overlay']` declares
// background / border / box-shadow / font-family / line-height at the SAME
// specificity (0,1,0) and LATER in the cascade. Verified by moving the
// plane's own tokens on this node: surface, border, font and leading
// repaint it, and the plane's `box-shadow` rule is in its matched list.
// Same adjudication as `tooltip`, same open decision (PLAN 12.9).
bg : PLANE_SUPERSEDED ,
border : PLANE_SUPERSEDED ,
'border-width' : PLANE_SUPERSEDED + ' (the plane writes the `border` shorthand)' ,
shadow :
PLANE_SUPERSEDED +
'; `shadow-topmost` DOES reach — its [data-topmost] rule is (0,2,0) and beats the plane (-> rgb(4,5,6))' ,
'font-family' : PLANE_SUPERSEDED ,
'line-height' : PLANE_SUPERSEDED ,
// Three states / parts the demo does not mount.
'active-shadow' :
'only under [data-dragging] / [data-resizing]; forced -> reaches (rgba(15,23,42,0.22) 0 28px 72px -> rgb(1,2,3))' ,
'resize-size' :
'the CORNER handles (se/ne/sw/nw) carry it and the demo mounts only the edge ones (e, s)' ,
'button-size' :
'consumed by [data-float-panel-action] / [data-float-panel-stage-trigger], neither mounted in the demo — the only control is the close Button, which is a composed <Button> and sizes itself (same group as the action-* entries above)' ,
// The palette bridge (THM-2), not debt: with a colour stamped the forward
// redefines the private and this key is only the no-colour base.
accent :
'the content carries data-color, so --_float-panel-accent comes from the THM-2 forward (--_float-panel-palette-border -> rgb(7,8,9)); with data-color removed it reaches (-> rgb(1,2,3))' ,
// The known law: the guard freezes transitions to make everything else
// measurable. Unfrozen pass on the panel itself.
'transition-duration' :
'frozen by the guard by design; unfrozen pass -> reaches (0.12s -> 11.5s)' ,
'transition-ease' :
'frozen by the guard by design; unfrozen pass -> reaches (cubic-bezier(0.4,0,0.2,1) -> steps(7))'
uix(float-panel): temable — 59 % → 84 %, muere un fantasma y queda una anomalía registrada
12 claves (28 → 40). Censo float-panel 59 % → **84 %** con `global` a CERO.
Cosido: las dos VARIANTES que ajustan desde el default elevado (`outline-*`,
`ghost-*`), los botones de su barra (`action-*`, `hover-action-*`) y el asa de
redimensionado.
MUERE UNO DE LOS NUEVE FANTASMAS DEL BARRIDO de esta mañana: el asa leía
`var(--color-content-tertiary, var(--color-content-secondary))` y ese primitivo
NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad
pintaba, con lo que la referencia muerta desaparece sin mover un píxel.
ONCE ADJUDICACIONES MEDIDAS: la demo monta la variante por DEFECTO —ni outline ni
ghost— y no tiene botón de acción genérico, sólo el control de cierre; se forzó
el atributo de variante y se montó el botón donde el CSS lo selecciona, y el
radio del asa sólo pinta bajo `:focus-visible`. Las once alcanzan.
Y UNA ANOMALÍA REGISTRADA EN VEZ DE ESCONDIDA: `resize-grip-fg` **mueve** bajo
una réplica a mano de la secuencia del propio guard —congelar transiciones, blur,
aparcar el ratón, escribir en `:root` y en cada nodo con el prefijo, fotografiar
los mismos props con `::before`/`::after`/`::placeholder`— (oklch(0.5032 0 0) →
rgb(1,2,3)), y aun así el guard lo da por muerto. Descartado que el nodo no lleve
el atributo (lo lleva), que la regla necesite un estado (es la regla base), que
sea el valor centinela (es el mismo) y que sea HMR (dos corridas). Queda
adjudicado CON la medición y marcado como anomalía en §13: el token está vivo, lo
que está en duda es la lectura. Es por donde debe empezar la revisión adversarial
de este bloque — el precedente de F2-A dice que estos casos sí tienen causa.
Lo rojo que queda son 12 tokens anteriores, y seis de ellos —`bg`, `border`,
`border-width`, `shadow`, `font-family`, `line-height`— son el mismo conjunto que
en `popover`: el PLANO de profundidad gana sobre el panel (§12.9, pendiente de
firma).
Gates: sonda antes/después **0 diffs en 1.890 valores** (9 nodos × 7 estados) ·
censo --only 84 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0
invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 ·
captura mirada (cabecera, botón de cierre y asa de la esquina en su sitio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
uix(drawer): temable — 57 % → 72 %, el disparador gemelo del de popover
10 claves (44 → 54). Censo drawer 57 % → **72 %**; global del eje 61 %.
Mismo cromo que el disparador de `popover` y con la misma corrección:
`min-block-size` lee el BUNDLE de talla (`--size-md-control-height`) y no el
primitivo crudo que el CSS traía. Aquí no hay estado abierto que teñir, así que
el hover lleva una sola clave.
LO QUE QUEDA FUERA ES LA FIRMA DE SALIDA, y no se cose aquí: los cuatro
`border-{side}: var(--border-width-thick) solid transparent` son la base que las
reglas de `data-last-action` tiñen al cerrar. La misma matriz vive en `popover` y
`dialog`, ya ha derivado entre las tres, y es mapeo transversal según la tabla
morfo→eidos → §13.
Los ocho privados son canales de valor (los `*-override` que el wrapper escribe
desde sus props, y el par `panel-width/height` de la variante persistente) y los
cinco literales, identidad.
UNA ADJUDICACIÓN, MEDIDA, Y DE UNA CLASE NUEVA DEL INSTRUMENTO: el guard ABRE el
cajón antes de probar, y entonces el velo tapa el disparador — la pasada de hover
no puede posarse en él (Playwright se niega a hacer hover sobre un nodo que no
recibe puntero). Con el cajón cerrado y hover a mano, `hover-trigger-bg` alcanza
(oklch(0.931 0 0) → rgb(1,2,3)).
Lo rojo que queda son **24 tokens anteriores** a este commit —el asa de arrastre,
el anillo de arrastre, los anchos y altos por talla, los gaps de cabecera y pie—,
todos de partes o estados que la demo no monta. Este commit no los empeora.
Gates: sonda antes/después **0 diffs en 390 valores** (8 estados) · censo --only
72 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid ·
suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del
escenario mirada (disparador con su cromo intacto).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
drawer : {
// The guard OPENS the drawer before testing, and the overlay then covers the
// trigger: the hover pass cannot reach it (Playwright refuses to hover a
// node that does not receive the pointer). Hovered by hand with the drawer
// closed 2026-08-22 -> reaches.
uix(drawer): los 24 rojos medidos uno a uno — y uno era un DEFECTO REAL: el velo no pinta
Sin cambios de código: 24 adjudicaciones medidas y dos hallazgos. El guard queda
en VERDE.
EL VELO DEL CAJÓN NO PINTA. Su regla es
background: color-mix(in srgb,
var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent)
y **soma escribe `--drawer-overlay-opacity: 1` INLINE** en el nodo del velo — es
su progreso de arrastre, un número sin unidad. `color-mix` exige ahí un
PORCENTAJE: con `1` la función entera es inválida y el fondo cae a `transparent`.
Medido: el velo abierto computa `rgba(0, 0, 0, 0)`; escribiendo `80%` en el
propio nodo, pinta. Y VISTO en captura: el fondo tras el cajón sale desenfocado
pero **sin atenuar** — el `backdrop-filter` funciona y el tinte no existe. Un
cajón modal sin velo.
No se arregla aquí porque mueve píxel y toca soma (§7.8). Puede arreglarse de un
lado o del otro —que soma escriba un porcentaje, o que la receta no use ese slot
como porcentaje—, y la lección general queda escrita en §13: **un valor que soma
escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo es
silencioso.
SEGUNDO HALLAZGO: las tallas son canal de valor en el eje que manda. Soma escribe
`width` (o `height`, según el lado) inline en el contenido, así que
`content-width-{sm,md,lg}` no puede ganar desde `:root` en un cajón
izquierda/derecha; el eje cruzado sí alcanza (`height-sm` forzado: 260px →
976px). Misma clase que el `item-gap` de carousel y el `preview-z` de drag-drop,
que se retiraron — pero aquí el token sigue siendo la fuente del valor que soma
resuelve, así que retirarlo o no es firma tuya.
Los otros 22 son lo esperable, y quedan medidos con su valor: el asa —que la demo
no monta y cuyo tamaño INTERCAMBIA ejes según el lado (36px→1234px de largo,
4px→1234px de grosor)—, el anillo de arrastre, el padding `full`, la cabecera y
el pie opcionales, y la z de la variante inline.
Gates: centinela 29/54 con las 24 adjudicadas, exit 0 · docs:check 0 · captura de
la página con el cajón abierto mirada.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'hover-trigger-bg' : 'the open drawer overlay covers the trigger, so the hover pass cannot land on it; hovered by hand with the drawer closed -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))' ,
// ── The 24 the component carried from before the axis, measured one by one
// 2026-08-22 (transitions frozen, drawer open, each state forced).
// The GRABBER is not mounted in this demo and its size swaps with the side:
// length is the inline axis for top/bottom and the block axis for left/right.
'handle-bg' : 'no grabber in this demo; mounted in place -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))' ,
'active-handle-bg' : 'same grabber, and only while dragging; forced -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
'handle-radius' : 'same grabber; mounted -> reaches (9999px -> 1234px)' ,
'handle-length' : 'same grabber, and only under side=top/bottom; forced -> reaches (36px -> 1234px)' ,
'handle-thickness' : 'same grabber, same side; forced -> reaches (4px -> 1234px)' ,
'handle-active-scale' : 'same grabber, only while dragging (transform, which the guard does not read)' ,
'handle-transition-duration' : 'the guard freezes transitions; the grabber transitions background and transform on it' ,
'handle-transition-ease' : 'same freeze, same pair' ,
// The dragging ring only exists while a pointer drags the panel.
'content-ring-dragging-fg' : 'only under [data-dragging]; forced -> reaches (srgb 0.392 / 0.32 -> rgb(1,2,3))' ,
'content-ring-dragging-width' : 'same state; forced -> reaches (2px -> 1234px)' ,
'content-ring-dragging-offset' : 'same state, outline-offset (read set)' ,
'content-shadow-dragging' : 'same state; forced -> the box-shadow changes' ,
// Sizes: the demo runs md on the right side, and soma writes the resolved
// axis INLINE (width for left/right, height for top/bottom), so the token of
// the axis in play cannot win from :root — a value channel, like carousel
// item-gap. The cross axis does reach: height-sm forced 260px -> 976px.
'content-width-sm' : 'soma writes width INLINE on the content for a left/right drawer, so no cascade can beat it - value channel, registered in next-features 13' ,
'content-width-md' : 'same inline width channel' ,
'content-width-lg' : 'same inline width channel' ,
'content-height-sm' : 'the demo runs a right drawer, where the height is not the axis in play; forced side=bottom -> reaches (260px -> 976px)' ,
'content-height-md' : 'same cross axis, same pass' ,
'content-height-lg' : 'same cross axis, same pass' ,
'content-padding-full' : 'only under [data-size=full]; forced -> reaches (24px -> 1234px)' ,
// Header and footer are optional parts the demo does not render.
'header-gap' : 'the demo mounts no header; mounted in place -> reaches (12px -> 1234px)' ,
'footer-gap' : 'the demo mounts no footer; mounted in place -> reaches (12px -> 1234px)' ,
'footer-margin-top' : 'same footer; mounted -> reaches (8px -> 1234px)' ,
'inline-z' : 'only under [data-variant=inline]; forced -> reaches (50 -> 4321)' ,
// AND A REAL DEFECT, not an instrument limit: the veil paints NOTHING.
'overlay-bg' : 'the veil paints nothing today: soma writes --drawer-overlay-opacity INLINE as the unitless 1 (its drag progress) and the CSS consumes it as a color-mix PERCENTAGE, so the mix is invalid and the background falls to transparent. Writing 80% on the node paints it. Registered in next-features 13 - it moves pixel, so it is not fixed here'
uix(drawer): temable — 57 % → 72 %, el disparador gemelo del de popover
10 claves (44 → 54). Censo drawer 57 % → **72 %**; global del eje 61 %.
Mismo cromo que el disparador de `popover` y con la misma corrección:
`min-block-size` lee el BUNDLE de talla (`--size-md-control-height`) y no el
primitivo crudo que el CSS traía. Aquí no hay estado abierto que teñir, así que
el hover lleva una sola clave.
LO QUE QUEDA FUERA ES LA FIRMA DE SALIDA, y no se cose aquí: los cuatro
`border-{side}: var(--border-width-thick) solid transparent` son la base que las
reglas de `data-last-action` tiñen al cerrar. La misma matriz vive en `popover` y
`dialog`, ya ha derivado entre las tres, y es mapeo transversal según la tabla
morfo→eidos → §13.
Los ocho privados son canales de valor (los `*-override` que el wrapper escribe
desde sus props, y el par `panel-width/height` de la variante persistente) y los
cinco literales, identidad.
UNA ADJUDICACIÓN, MEDIDA, Y DE UNA CLASE NUEVA DEL INSTRUMENTO: el guard ABRE el
cajón antes de probar, y entonces el velo tapa el disparador — la pasada de hover
no puede posarse en él (Playwright se niega a hacer hover sobre un nodo que no
recibe puntero). Con el cajón cerrado y hover a mano, `hover-trigger-bg` alcanza
(oklch(0.931 0 0) → rgb(1,2,3)).
Lo rojo que queda son **24 tokens anteriores** a este commit —el asa de arrastre,
el anillo de arrastre, los anchos y altos por talla, los gaps de cabecera y pie—,
todos de partes o estados que la demo no monta. Este commit no los empeora.
Gates: sonda antes/después **0 diffs en 390 valores** (8 estados) · censo --only
72 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid ·
suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del
escenario mirada (disparador con su cromo intacto).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
uix(aura): temable — 43 % → 80 %, y doce silencios que son diseño
13 claves (24 → 37). Censo aura 43 % → **80 %**; global del eje 61 %.
Su texto de estado y su detalle bajan UN paso del bundle por talla, y las TRES
coordenadas viajan juntas (tamaño, interlineado, tracking): doce declaraciones,
doce valores distintos, doce claves — 1:1, sin inflación. El valor sigue siendo
del bundle; el knob pasa a ser del componente. La decimotercera es
`idle-opacity`: el reposo no desaparece, se retira a media opacidad.
Fuera por identidad: el `--radius-full` del anillo (redondo por definición), los
cuatro `100%` del canvas y del SVG, y el `1px` × 2 de la región viva — la técnica
sr-only, la misma de `text-blur`.
DOCE ADJUDICACIONES, TODAS MEDIDAS, Y NINGUNA ES DEUDA:
- **Las seis tintas de estado y el halo pintan el FONDO DEL INDICADOR**, y la
demo corre con el canvas WebGL montado: la regla
`[data-aura-indicator]:has(canvas)` pone `background: none` A PROPÓSITO — el
orbe ES la presencia, y un disco detrás enturbia su silueta. Quitando el canvas
en vivo, el gradiente vuelve y las siete siguen al token
(oklch(0.6434 0 0) → rgb(1,2,3) dentro del radial-gradient).
- **El arco y el halo son SLOTS del anillo que sólo existen en los estados de
trabajo**; la demo se queda en la pista. Montados como
`<circle data-aura-ring-slot>` dentro del SVG real —que es exactamente lo que
el CSS selecciona—: 2px → 1234px, 1px → 1234px, 0.5 → 0.123.
- Las dos de transición, congeladas por el guard como en todo el catálogo.
Y una ceguera más del instrumento, la tercera de la sesión: **el guard no veía el
SVG del anillo** porque sus `<circle>` no llevan atributo `data-aura*`.
`extraNodes` los mete en el conjunto medido.
Gates: sonda antes/después **0 diffs en 1.710 valores** (9 nodos × 7 estados) ·
centinela 25/37 con las doce adjudicadas · censo --only 80 % · `--names` sin
desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
aura : {
// The six state tints and the halo paint the INDICATOR background, and the
// demo runs with the WebGL canvas mounted — the rule
// [data-aura-indicator]:has(canvas) sets background:none ON PURPOSE (the orb
// IS the presence; a disc behind it muddies the silhouette). Removed the
// canvas in place 2026-08-22: the gradient returns and all six follow.
'orb-color-idle' : 'the canvas covers the disc by design; removed in place -> reaches (oklch(0.6434 0 0) -> rgb(1,2,3) inside the radial-gradient)' ,
'orb-color-offered' : 'same disc, same pass' ,
'orb-color-planned' : 'same disc, same pass' ,
'orb-color-reviewing' : 'same disc, same pass' ,
'orb-color-acting' : 'same disc, same pass' ,
'orb-color-escalated' : 'same disc, same pass' ,
'orb-halo-alpha' : 'the outer stop of the same gradient; removed the canvas -> the gradient changes' ,
// The arc and the halo are ring SLOTS that only exist in the working
// states; the demo sits on the track. Mounted as <circle data-aura-ring-slot>
// inside the real SVG 2026-08-22 — which is exactly what the CSS selects.
'ring-arc-width' : 'the arc slot is not mounted in the idle demo; mounted in place -> reaches (2px -> 1234px)' ,
'ring-halo-width' : 'the halo slot is not mounted; mounted in place -> reaches (1px -> 1234px)' ,
'ring-halo-opacity' : 'same slot; mounted in place -> reaches (0.5 -> 0.123)' ,
'transition-duration' : 'the guard freezes transitions on purpose; the orb tint and the arc stroke transition on it' ,
'transition-ease' : 'same freeze, same pair of properties'
} ,
uix(popover): temable — 46 % → 67 %, y un huérfano que el guard de huérfanos no veía
Once claves para el disparador + una RETIRADA (36 → 46). Global del eje 60 %.
EL DISPARADOR GENÉRICO ES CROMO PROPIO. El que no lleva el arquetipo
`field-trigger` —que tiene su tratamiento aparte— montaba alto, padding, borde,
radio, superficie y tinta sobre primitivos crudos. Dos notas: `trigger-height`
lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo que el CSS
traía, y hover y abierto llevan CADA UNO su par de claves aunque hoy compartan
valor — son dos estados, y un tema puede querer el abierto más marcado.
`bg` RETIRADO: ninguna regla CSS lo consumía. El único sitio del corpus que lo
nombraba era un COMENTARIO de `popover-arrow.svelte` («fill: var(--popover-bg)»),
y el test `does not leave declared public recipe variables orphaned` busca el
nombre como TEXTO en los ficheros del componente — así que un comentario mantiene
vivo un token para siempre. La superficie del panel la pinta el plano de
profundidad (`--depth-overlay-surface`), que es lo que un plano debe poseer; el
comentario queda corregido y el hueco del test, en §13.
LOS DOCE QUE SIGUEN FUERA SON LA FIRMA DE SALIDA, y no se cosen aquí:
`data-last-action` × 3 intents × 4 lados tiñendo el borde durante el cierre. La
misma matriz está repetida en `drawer` (22 reglas) y `dialog` (9) — y ya ha
derivado: dialog tiñe sólo `border-top-color` mientras popover y drawer usan el
shorthand con `--border-width-medium`, así que en dos de tres el GROSOR también
cambia al salir. La tabla morfo→eidos la sitúa como mapeo transversal
(«`events[].prewrite` → tinting the exit anim by cause»), misma fila que los
arquetipos: es canal visual de sema, no cromo de componente. Coserlo tres veces
sería triplicar un vocabulario que debe vivir una vez → §13.
DOS CEGUERAS DEL INSTRUMENTO, arregladas:
1. La página tiene CINCO disparadores y el guard abría el primero del documento
—un ejemplo de más abajo—, no el del escenario: las partes que el escenario
monta nunca entraban. `openWith` apuntando al escenario.
2. `content-z` se consume sobre el ENVOLTORIO FLOTANTE
(`:has(> [data-popover-content])`), un nodo sin atributo `data-popover*` que
el filtro no veía: leía muerto moviendo 80 → 4321.
TRECE ADJUDICACIONES, TODAS MEDIDAS: nueve son partes que la demo no monta
(Title, Description) y dos el overlay opcional —montados en el panel ABIERTO con
los atributos que el CSS selecciona, alcanzan—. **Las dos que no son de
montaje**: `font-family` y `line-height` no pueden ganar porque el plano
`overlay` impone la tipografía del panel — el hallazgo §12.9, que espera tu firma
y afecta a VEINTE componentes. Aquí queda medido otra vez.
Gates: sonda antes/después **0 diffs en 420 valores** (8 estados; el escenario
monta un disparador y el panel portala fuera, así que el estado `abierto` es el
que mide el panel: 7 nodos) · centinela 33/46 · censo --only 67 % · `--names` sin
desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
popover : {
// The demo renders neither Title nor Description. Mounted inside the OPEN
// panel with the attrs the CSS selects (2026-08-22): all nine reach; the two
// with a number are the sample that was read.
'title-fg' : 'the demo mounts no Popover.Title; mounted in place -> reaches (oklch(0.2435 0 0) -> rgb(1,2,3))' ,
'title-font-size' : 'same part; mounted in place -> reaches (16px -> 1234px)' ,
'title-font-family' : 'same part, same measurement pass' ,
'title-font-weight' : 'same part, same measurement pass' ,
'title-line-height' : 'same part, same measurement pass' ,
'description-fg' : 'the demo mounts no Popover.Description; mounted in place -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
'description-font-family' : 'same part, same measurement pass' ,
'description-font-size' : 'same part, same measurement pass' ,
'description-line-height' : 'same part, same measurement pass' ,
// The overlay is opt-in (modal popovers) and the demo boots with it off.
'overlay-bg' : 'the demo boots with showOverlay off; mounted in place -> reaches (rgba(28,25,23,0.42) -> rgb(1,2,3))' ,
'overlay-z' : 'same; mounted in place -> reaches (60 -> 4321)' ,
// The two that are NOT about mounting: the depth plane wins over both.
'font-family' :
'the overlay DEPTH PLANE imposes font-family on the panel and beats the recipe: the section 12.9 finding, pending signature (20 components). Measured 2026-08-22, writing the token moves nothing while the plane does.' ,
'line-height' :
'same plane, same finding: the token cannot win until the plane stops owning typography'
} ,
uix(radio-cards): 61 % es su techo — 21 de sus 23 knobs no son suyos
Una sola clave nueva (`invalid-border`, 39 → 40) y su veredicto §5.
RadioCards es un **skin sobre RadioGroup**: renderiza `<RadioGroup.Provider>` y
su indicador ES un radio. Por eso lee `--radio-group-item-size-*`,
`--radio-group-indicator-*` y el puente de paleta del anfitrión
(`--_radio-group-palette-*`, declarado en el host `[data-radio-group]` donde esta
tarjeta vive). El censo los marca «⤴ prestado» y el mandato de composición
(`theming-audit` §5.3-3) dice que **se teman en la ficha del dueño**: coserlos
aquí sería vocabulario paralelo y dejaría que un tema rompiera «un radio es un
radio» sólo dentro de las tarjetas.
Lo acuñado es lo que sí es suyo: el borde de la TARJETA en validación fallida.
Los dos `--radius-full` del indicador son identidad (un radio es redondo por
definición, misma clase que el `50%` de un círculo) y los cuatro literales son
geometría del glifo (el `46%` del punto interior, el `100%` del SVG).
LA LECCIÓN DE LA MEDICIÓN: la primera lectura forzada de `invalid-border` dio
«sin efecto». No era el token: la tarjeta transiciona `border-color`, y leer
justo después de escribir devuelve el valor de PARTIDA — exactamente el motivo
por el que el guard congela las transiciones. Con la congelación puesta,
`oklch(0.9555 0.0207 13.86)` → `rgb(1,2,3)`. **Una medición forzada a mano tiene
que congelar también.** Queda escrito en el veredicto y en el ledger.
Las otras cuatro adjudicaciones, con su medición: `horizontal-basis` sólo bajo
`[data-orientation=horizontal]` (192px → 1234px), `disabled-opacity` sólo bajo
`[data-disabled]` (0.4 → 0.123) y las dos de transición, que el guard congela
(0,12s → 11,5s · cubic-bezier → steps(4)).
Gates: sonda antes/después **0 diffs en 3.540 valores** (19 nodos × 7 estados) ·
centinela 35/40 con las cinco adjudicadas · censo --only 61 % · `--names` sin
desviadas · component-audit PASS · eidos-lint 0 invalid · docs:check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'radio-cards' : {
// All five forced by hand 2026-08-22, WITH transitions frozen: the card
// transitions `border-color`, and reading right after the write returns the
// starting value (the same trap the guard's own freeze exists for — the
// first measurement of invalid-border read "no effect" for that reason).
'invalid-border' :
'only paints under [data-invalid] and the demo boots valid; forced -> reaches (oklch(0.9555 0.0207 13.86) -> rgb(1,2,3))' ,
'horizontal-basis' :
'only under [data-orientation=horizontal]; forced -> reaches (192px -> 1234px of flex-basis)' ,
'disabled-opacity' :
'only under [data-disabled] on the item; forced -> reaches (0.4 -> 0.123)' ,
'transition-duration' :
'the guard freezes transitions on purpose; measured without the freeze -> reaches (0.12s -> 11.5s)' ,
'transition-ease' :
'same freeze; measured without it -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(4))'
} ,
uix(prose): temable — 1 % → 83 %, la costura por ELEMENTO y dos cegueras del instrumento
35 claves nuevas (2 → 37). Censo prose 1 % → **83 %** con `global` a CERO;
global del eje **59 % → 60 %** (1.064 → 1.021 knobs `global` en el catálogo).
Prose estiliza HTML CRUDO (`h1`, `code`, `table`, `kbd`…) a través de
`:where([data-prose] el)`, así que sus «partes» son elementos y la costura va
POR PARTE, como en `result`: `heading-*` (+ `h1-font-weight`, lo único que
distingue al h1 de sus hermanos), `link-*`, `strong-`/`dt-`/`th-font-weight`,
`mark-*`, `blockquote-*`, `code-*`, `pre-*`, `kbd-*`, `media-radius`, `hr-*`,
`table-*`, más `fg`, `fg-muted` y el eje `font-size-{sm,md,lg}`. Los valores
siguen siendo los del canon; lo que faltaba era el knob (PLAN §2-A).
LA ESCALA EDITORIAL NO ENTRA, Y ES DOCTRINA FIRMADA: los `2.25em`, `1.5em`,
`0.9em`, sus interlineados y su tracking son `em`-relativos a propósito —un
`font-size` raíz re-deriva encabezados y márgenes, por eso NO hay una escala
duplicada por talla como en tailwind-typography (su README: «No 5× duplicated
scale»)— y van anotados `/* literal: */`, la válvula de recipe-contract §3. Son
las 21 excepciones firmadas que el censo aprendió a leer esta misma sesión.
Dos literales se cambiaron por su paso de escala, con el mismo valor: el `3px`
del borde-acento del blockquote ES `--border-width-thick`, y los `1px` de kbd,
`hr` y la tabla son `--border-width`.
EL INSTRUMENTO ESTABA CIEGO DE DOS MANERAS, arregladas ANTES de medir:
1. **Sonda y centinela filtran por `data-{c}-*`** y aquí NINGUNA parte lo lleva:
la sonda medía **1 nodo** y el guard daba **27 de 37** tokens por muertos.
Los dos ganan un selector de nodos extra (`EXTRA_NODES` en la sonda,
`extraNodes` en `COMPONENT_OVERRIDES`) — con él, 63 nodos y un gate que
significa algo.
2. **El centinela no leía el color de un borde que no fuera el de arriba.** El
blockquote tiñe su `border-inline-start` y la tabla sus bordes de bloque:
tres tokens vivos leían muertos. Añadidos los cuatro lados lógicos y
`borderBottomColor`.
OCHO ADJUDICACIONES, TODAS MEDIDAS: cinco las tapa el harness de la
documentación —`[data-uix-docs] code|pre` (0,2,0) gana a `:where(…)` (0,0,0),
que es la incidencia ya escrita para `code-block` un piso más arriba— y tres son
elementos que el HTML de ejemplo no usa (`<mark>`, `<dl>`), montados DENTRO del
contenedor real: un `<mark>` a pelo es exactamente lo que esta receta estiliza,
así que el nodo es fiel (la lección de `drag-drop` cumplida).
Gates: sonda antes/después **0 diffs en 11.910 valores** (63 nodos × 7 estados) ·
centinela 29/37 con las ocho adjudicadas · censo --only 83 % · `--names` 37/37
conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados. Componentes del ledger re-verificados tras
tocar el guard (text-gradient, chart, result, tree-view, textarea): sin
regresiones ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
prose : {
// Prose styles bare HTML at ZERO specificity on purpose (`:where([data-prose] el)`,
// so an embedded component keeps its own look). That is exactly what the docs
// harness beats: `[data-uix-docs] code|pre` (0,2,0) wins over (0,0,0). Removing
// the harness attribute in place 2026-08-22 -> the five reach. Same §13 entry as
// code-block's, one floor down.
'code-bg' : 'the harness rule [data-uix-docs] code (0,2,0) beats :where(…) (0,0,0); without it -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))' ,
'code-radius' : 'same harness rule; without it -> reaches (4px -> 1234px)' ,
'code-font-family' : 'same harness rule; without it -> reaches (Azeret Mono -> Zapfino)' ,
'pre-bg' : 'the harness rule [data-uix-docs] pre (0,2,0) beats :where(…); without it -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))' ,
'pre-radius' : 'same harness rule; without it -> reaches (6px -> 1234px)' ,
// Two elements the demo's sample HTML does not use. Mounted INSIDE the real
// [data-prose] container 2026-08-22 — and a bare <mark>/<dt> IS what this
// recipe styles (it selects raw HTML), so the node is faithful.
'mark-bg' : 'the sample HTML has no <mark>; mounted in place -> reaches (oklch(0.9923 0.0025 48.72) -> rgb(1,2,3))' ,
'mark-radius' : 'same; mounted in place -> reaches (4px -> 1234px)' ,
'dt-font-weight' : 'the sample HTML has no <dl>; mounted in place -> reaches (600 -> 123)'
} ,
uix(result): temable — 14 % → 23 %, la receta posee su CAJA y nada más
Cuatro claves nuevas (8 → 12). Censo result 14 % → **23 %**; global 57 %;
alcance <20 % 18 → 17.
Aplicación directa de la doctrina que cerró `chart`: lo que la receta posee es
su **caja** —el inset de página (`padding-block`/`-inline`) y el ritmo entre
bloques (`actions-margin-block-start`, `extra-margin-block-start`)—, más los
cuatro acentos de estado que ya tenía. Ni una clave más.
LO QUE NO SE ACUÑA, Y POR QUÉ:
Su tipo ya leía el BUNDLE DE TALLA (`var(--size-{xl,md,sm}-font-size)`), que es
literalmente lo que recipe-contract §1 prescribe; la tinta lee las ranuras de
contenido, el peso y el interlineado sus escalas, y el código grande la familia
mono del tema. Acuñar `--result-title-font-size` habría sido tipografía por
componente, prohibida hoy. El censo cuenta esas trece lecturas de canon como
`global`: es la nota de `listbox` y `chart` (next-features §13), **tercera
ocurrencia**, no deuda de este componente.
Los cuatro literales son identidad documentada: el `calc(var(--size-xl-font-size)
* 3)` del glifo de estado (el display es TRES veces el paso xl), el `1em` del
código grande que hereda esa escala a propósito, su `0.04em` de tracking óptico
y el `100%` del extra.
Higiene de paso: el privado `--_result-media-color` pasa a `--_result-media-fg`
— la gramática firmada (D-TH.6) dice `fg` y R-5.3 no mira los privados.
CINCO ADJUDICACIONES, TODAS SOBRE NODOS REALES:
El guard da 7/12. Las cinco que callan se midieron con los CONTROLES DE LA DEMO,
no fabricando nodos (la lección de `drag-drop`): los tres acentos sólo pintan
bajo su `data-status` —se pulsó su chip: oklch → rgb(1,2,3) en los tres— y el
bloque `extra` es opcional —se marcó su casilla: 16px → 1234px y 559,44px →
1234px—. Las cinco alcanzan.
Gates: sonda antes/después **0 diffs en 990 valores** (5 nodos × 7 estados) ·
censo --only 23 % · --names 12/12 conformes · component-audit PASS · eidos-lint
0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 ·
`check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
result : {
// All five measured on the demo's REAL nodes (its own controls drive them),
// not on a node built by hand — the drag-drop lesson: a "forced -> reaches"
// adjudication is worth what the forced node is worth.
'extra-margin-block-start' :
'the extra block is optional and the demo boots without it; ticked its checkbox 2026-08-22 -> reaches (16px -> 1234px)' ,
'extra-measure' :
'same optional block; ticked 2026-08-22 -> reaches (559.44px -> 1234px)' ,
'error-fg' :
'only paints under [data-status=error] and the demo boots on success; clicked its chip 2026-08-22 -> reaches (oklch(0.6256 0.1933 23.03) -> rgb(1,2,3))' ,
'warning-fg' :
'only paints under [data-status=warning]; clicked its chip 2026-08-22 -> reaches (oklch(0.6908 0.1909 45.02) -> rgb(1,2,3))' ,
'info-fg' :
uix(result): la COSTURA del texto — 23 % → 86 %, y `global` a cero
Quince claves. Misma corrección que en `chart`: el valor sigue siendo el del
canon —bundle de talla, ranuras de contenido, escalas de peso e interlineado,
familia mono— pero el knob pasa a ser del componente, que es lo que PLAN §2-A
exige y lo que permite retocar esta superficie sin mover el sistema entero.
POR PARTE, NO POR ROL — y es la diferencia con `chart`:
title-{font-size,font-weight,line-height,fg}
description-{font-size,line-height,fg}
extra-{font-size,fg}
code-{font-family,font-weight,letter-spacing,fg}
media-{font-size,fg}
Cinco partes con tipografía distinta y ningún estilo con nombre que juegue
«estado terminal de página» (`h2` está en el tamaño del título pero significa
otra cosa). Es la forma dominante del catálogo — `description-fg` ×8,
`title-font-size`… — mientras que en chart dos roles se repetían sobre 19 nodos
y por eso allí la costura fue por rol.
El `calc(var(--size-xl-font-size) * 3)` del glifo entra en su costura con el
multiplicador DENTRO del valor: el 3× es identidad documentada, y así el glifo
sigue a la escala en vez de congelarse.
DOCE ADJUDICACIONES, TODAS SOBRE NODOS REALES (la lección de `drag-drop`): con
los controles de la propia demo, no fabricando nodos. Los tres acentos de estado
sólo pintan bajo su `data-status` —chip—; el bloque `extra` es opcional
—casilla—; el código grande sólo existe en los estados http —chip `not-found`:
Azeret Mono → Zapfino, 600 → 123, 3,36px → 4,5px—; y la tinta POR DEFECTO del
glifo sólo gana SIN `data-status`, porque cada estado re-apunta el privado a su
acento por diseño —se quitó el atributo: oklch(0.61 0 0) → rgb(1,2,3)—.
Con la costura, `global` cae a CERO y el alcance a 86 %: los tres knobs que
faltan para el 100 % son literales de identidad (el `1em` del código grande, que
hereda la escala del glifo; el `line-height: 1` del glifo; el `100%` del extra).
Gates: sonda antes/después **0 diffs en 990 valores** (5 nodos × 7 estados) ·
centinela 15/27 con las doce adjudicadas y medidas · censo --only **86 %** con
`global` 13 → 0 · --names 27/27 conformes · component-audit PASS · eidos-lint 0
invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0.
Global del eje tras las dos costuras: **57 % → 58 %** (2.677 → 2.727 públicos).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'only paints under [data-status=info]; clicked its chip 2026-08-22 -> reaches (oklch(0.5556 0.1829 305.86) -> rgb(1,2,3))' ,
'extra-font-size' :
'same optional block; ticked its checkbox 2026-08-22 -> reaches (14px -> 1234px)' ,
'extra-fg' : 'same optional block; ticked 2026-08-22 -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
'media-fg' :
'the DEFAULT glyph ink: with a [data-status] the private wins by design (each status re-points --_result-media-fg to its accent) and the demo always sets one. Removed the attribute 2026-08-22 -> reaches (oklch(0.61 0 0) -> rgb(1,2,3))' ,
'code-font-family' :
'the big HTTP code only renders for the http statuses and the demo boots on success; clicked the not-found chip 2026-08-22 -> reaches (Azeret Mono -> Zapfino)' ,
'code-font-weight' : 'same http-only node; clicked not-found 2026-08-22 -> reaches (600 -> 123)' ,
'code-letter-spacing' :
'same http-only node; clicked not-found 2026-08-22 -> reaches (3.36px -> 4.5px)' ,
'code-fg' :
'same http-only node; clicked not-found 2026-08-22 -> reaches (oklch(0.61 0 0) -> rgb(1,2,3))'
uix(result): temable — 14 % → 23 %, la receta posee su CAJA y nada más
Cuatro claves nuevas (8 → 12). Censo result 14 % → **23 %**; global 57 %;
alcance <20 % 18 → 17.
Aplicación directa de la doctrina que cerró `chart`: lo que la receta posee es
su **caja** —el inset de página (`padding-block`/`-inline`) y el ritmo entre
bloques (`actions-margin-block-start`, `extra-margin-block-start`)—, más los
cuatro acentos de estado que ya tenía. Ni una clave más.
LO QUE NO SE ACUÑA, Y POR QUÉ:
Su tipo ya leía el BUNDLE DE TALLA (`var(--size-{xl,md,sm}-font-size)`), que es
literalmente lo que recipe-contract §1 prescribe; la tinta lee las ranuras de
contenido, el peso y el interlineado sus escalas, y el código grande la familia
mono del tema. Acuñar `--result-title-font-size` habría sido tipografía por
componente, prohibida hoy. El censo cuenta esas trece lecturas de canon como
`global`: es la nota de `listbox` y `chart` (next-features §13), **tercera
ocurrencia**, no deuda de este componente.
Los cuatro literales son identidad documentada: el `calc(var(--size-xl-font-size)
* 3)` del glifo de estado (el display es TRES veces el paso xl), el `1em` del
código grande que hereda esa escala a propósito, su `0.04em` de tracking óptico
y el `100%` del extra.
Higiene de paso: el privado `--_result-media-color` pasa a `--_result-media-fg`
— la gramática firmada (D-TH.6) dice `fg` y R-5.3 no mira los privados.
CINCO ADJUDICACIONES, TODAS SOBRE NODOS REALES:
El guard da 7/12. Las cinco que callan se midieron con los CONTROLES DE LA DEMO,
no fabricando nodos (la lección de `drag-drop`): los tres acentos sólo pintan
bajo su `data-status` —se pulsó su chip: oklch → rgb(1,2,3) en los tres— y el
bloque `extra` es opcional —se marcó su casilla: 16px → 1234px y 559,44px →
1234px—. Las cinco alcanzan.
Gates: sonda antes/después **0 diffs en 990 valores** (5 nodos × 7 estados) ·
censo --only 23 % · --names 12/12 conformes · component-audit PASS · eidos-lint
0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 ·
`check` con 72 errores globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
chart : {
'transition-duration' :
"the guard FREEZES transitions on purpose (a transitioned property returns its starting value), which is exactly what hides a token whose whole job IS the transition — same class as text-circular.char-transition. Measured without the freeze 2026-08-22 on [data-chart-line]: 0.12s -> 11.5s" ,
'transition-ease' :
"same freeze, same measurement 2026-08-22 on [data-chart-line]: cubic-bezier(0, 0, 0.2, 1) -> steps(4)"
} ,
uix(text-circular): temable — 83 % → 100 %, declarando lo que ya consumía
6 claves en una entrada NUEVA de `recipes/base.ts`. Censo 83 % → **100 %**.
Ya consumía CINCO públicos «de facto» (`var(--text-circular-x, <default>)`)
pero **sin declarar ninguno**: un tema no los veía en `getRecipeTokens()` ni el
panel de la demo los listaba. Tokenizar ha sido sobre todo DECLARAR lo que ya
se consumía, más el `radius` que faltaba.
- **Aquí declarar es seguro, al revés que en `knob`**: no hay privado por talla
detrás del fallback, así que el público no puede matar una escala.
- `color: currentColor` es correcto como token: una custom property no resuelve
`currentColor` hasta el punto de uso, así que sigue tomando la tinta del nodo
y no la del `:root` — verificado con el diff.
- `char-transition` guarda una transición COMPLETA
(`all 0.5s cubic-bezier(0, 0, 0, 1)`): el gesto del carácter al girar,
verbatim, no cuatro ejes sueltos.
EL HALLAZGO (→ next-features §13): **el guard congela las transiciones, y eso
esconde los tokens cuyo trabajo ES la transición.** La congelación existe por
una razón buena y documentada —una propiedad transicionada devuelve su valor
inicial justo tras escribir el token, y un token vivo parecería muerto— pero
deja ciego al guard ante `char-transition`. Medido sin congelar: alcanza
(0.5s cubic-bezier → 9.5s linear). Si aparecen más tokens de esta clase
—transición, animación, easing— merecen un pase propio sin congelación, como
el que ya tiene el hover.
Gates: sonda antes/después = 0 diffs en 5.916 valores × 7 estados · censo
--only 100 % · R-5.4 5/6 con el mudo adjudicado · rtl:check 0 · docs:check 0 ·
check por fichero limpio · ledger verificado con import real (27 componentes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'text-circular' : {
'char-transition' :
"the guard FREEZES transitions on purpose (a transitioned property returns its starting value), which is exactly what hides a token whose whole job is the transition. Measured without the freeze 2026-08-22 -> reaches (0.5s cubic-bezier -> 9.5s linear)"
} ,
uix(textarea): temable — 0 % → 80 %, y el contador que casi pierde su 0.85
37 claves en el bloque `textarea` de `recipes/base.ts` (fusionadas con el
forward de paleta que ya estaba). Censo 0 % → 80 %, con **cero globales y cero
literales de valor**: los 16 privados leen ya su público. Global 46 %, sin
contrato 48 → 47.
LO QUE LA MEDICIÓN CAMBIÓ. La §4 generada proponía 35 tokens; cuatro eran
colisiones y uno habría movido el default:
1. `count-font-size-{k}` × 5 BORRABA el factor 0.85. El CSS calcula
`calc(var(--_textarea-font-size) * 0.85)` y la propuesta ponía el tipo del
input crudo en las cinco tallas: el contador habría crecido un 18 %.
D-TH.5 lo prohíbe. Queda en UN token con el `calc` sobre el público del
input — la escala se escribe una vez y la talla la resuelve el input. Pide
`scope: 'host'`: emitido en `:root` su dependencia no existe, y el contrato
de scope de la receta lo caza (lo cazó).
2. `input-fg` ⚠ eran DOS knobs (texto y placeholder) → `input-fg` +
`input-placeholder-fg`. Igual `count-fg` → `count-fg` + `overflow-count-fg`.
3. `input-border` ⚠ NO eran dos knobs sino UNA variable con su swap de paleta,
y el nombre estaba mal: es el borde de FOCO → `focus-input-border`. El de
reposo es otro token, que la propuesta ni listó por viajar en el atajo.
4. `ghost-input-border` se RETIRA: la regla ghost lee ese mismo conmutador —
sería un valor con dos nombres.
Bundle, medido: la tipografía casa 1:1 en las cinco (lee `--size-{k}-font-size`);
el spacing NO — `padding-inline` casa salvo en xs (8px vs 6px) y `padding-block`
sólo en xs. Verbatim y documentado: un campo multi-línea respira más, y el
spacing queda fuera del guard del bundle. Resueltos sin `parts` (el `data-size`
va en `[data-textarea]` y el input hereda); fuera los cuatro `[data-size]`.
DOS ARREGLOS DEL INSTRUMENTO, los dos por medición y no por sospecha:
- **El guard medía con el PUNTERO encima.** `reopen()` corre antes de CADA
token y hace clic en `[data-{c}-input]` cuando el componente no tiene parte
`content`; el `blur()` de la revisión adversarial quitaba el foco pero no el
puntero, así que `:hover` casaba toda la corrida y toda regla de hover pisaba
a sus vecinas. Costaba TRES falsos negativos aquí. Con `mouse.move(0,0)`
tras cada blur, `input-border` revive solo (31 → 32).
- **`::placeholder` SÍ se lee con `getComputedStyle`.** La nota de §13 que
decía lo contrario nunca se comprobó; medido, el color centinela vuelve tal
cual. Entra en el snapshot (32 → 33) y deja STALE la entrada
`command.input-placeholder-fg`, que el guard señaló y se ha borrado.
Corridos los DIEZ componentes con ledger tras los dos arreglos: ningún otro
cambia, un único STALE, el previsto.
Y un hallazgo de píxel que NO se toca aquí (→ next-features §13): el borde de
foco sólo se ve con Tab. Las tres reglas de `border-color` del input están
ordenadas al revés de lo que significan — hover (0,4,0) > foco (0,3,0) >
invalid (0,2,0) — y hacer clic deja el puntero encima por definición, así que
gana el hover; un campo inválido pierde su borde rojo al enfocarlo o al pasar
el ratón. El anillo de foco del sistema sí se ve siempre, así que no es fallo
de accesibilidad. Arreglarlo mueve píxel.
El 20 % que falta son las dos declaraciones que consumen
`--_textarea-border-focus`, privado a propósito: es el conmutador de una
variable con dos fuentes (el público y el forward THM-2), y aplanarlo exigiría
duplicar la regla por color. Mismo techo que `listbox` paga por consumir bien
su capa. Mi propio veredicto predecía ~100 % y la medición lo corrigió a 80 %.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.247 valores × 8 estados (el 0.85 intacto,
que era el riesgo). Determinismo verificado: dos corridas, 0 diffs.
- R-5.4: 33/37; los 4 mudos adjudicados en el ledger con su medición (foco,
invalid y las dos del contador rebasado — estados que la demo no monta).
- censo --only 80 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0
· suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0
· docs:check 0 · check por fichero limpio (72 globales, ninguno mío).
- README con su «Talla y tema» y tab `Tokens` en la demo (37 claves; el panel
muestra el contador computando `calc(calc(1rem * 1) * 0.85)`).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
textarea : {
'focus-input-border' :
'consumed only under [data-focused], which the guard blurs away ON PURPOSE (the click-focus poisoning fix); focused by hand with the pointer parked → reaches (rgb(4,5,6))' ,
'invalid-input-border' :
'consumed only under [data-invalid]; the demo boots valid. Forced by hand 2026-08-21 → reaches (rgb(7,8,9))' ,
'overflow-count-fg' :
'the count only carries [data-overflow] past maxLength (280 chars); forced → reaches (rgb(13,14,15))' ,
'overflow-count-font-weight' :
'the count only carries [data-overflow] past maxLength (280 chars); forced → reaches (font-weight 333)'
} ,
uix(grid-list): temable — 0 % → 85 %, y una selección que nunca pintó
38 claves nuevas en el bloque `grid-list` de `recipes/base.ts` (que ya existía
con los dos forwards de paleta: se FUSIONA, no se añade un segundo). Alcance
del censo 0 % → 85 % (22 de 26 puntuables); global 45 % → 46 %, sin contrato
49 → 48.
El eje `size` se estampa en el PROVIDER —el wrapper de eidos no tiene parte
`root`—, así que los tres nombres resueltos van SIN `parts`: molde `command`,
no `table`/`tree-grid`. Fuera los cuatro bloques `[data-size]` del CSS; los
emite el TSC. Altura de fila 1:1 con el bundle, `row-padding-inline` con ritmo
propio, y la tipografía un paso por debajo de md en adelante — verbatim, como
table y tree-grid.
LO QUE LA MEDICIÓN CAMBIÓ (la §4 generada tenía un error de doctrina y dos
defectos que sólo aparecieron midiendo; el §5 Veredicto se escribió con la
sonda delante, porque estaba vacío):
1. La selección de fila NUNCA HA PINTADO. La receta seleccionaba
`[data-grid-list-row][data-selected]`, un atributo que ni el morfo declara
ni soma estampa (estampa `data-state='selected'` + `aria-selected`): la
regla no casaba jamás y su acento `--_grid-list-palette-element` no llegó
nunca al píxel — una fila seleccionada muestra el velo NEUTRO del
arquetipo, indistinguible del hover (capturado). Retirada la declaración
muerta y su forward huérfano: 0 diffs, que es la prueba. Repararlo mueve
píxel y choca con §12.5 (el arquetipo fija también `color`) → §13, espera
firma. El acento del checkbox (`_palette-solid`) sí vive y se conserva.
2. NO se acuña `hover-row-bg` aunque la propuesta lo pedía. La fila es
`archetype: 'item'`: el velo del sistema la pinta a especificidad plena y
el plano de la receta pierde — medido, el hover pinta DOS VECES sobre el
MISMO nodo (a diferencia de table/tree-grid, donde velo y plano caen en
nodos distintos). Sería un token que no puede ganar; mismo criterio que
listbox.highlighted → §12.7. Y fila Y celda llevan ambas `item`, así que
bajo el puntero se apilan tres capas.
3. «Consume la capa compartida `menu-indicator`» es FALSO. Sale de
`SHARED_LAYERS` (`theming-census.ts:432`), pero esa capa no tiene un solo
selector de grid-list, la receta no la importa y soma no renderiza
indicator: el checkbox ESPEJA el visual pintándolo por su cuenta. Entrada
desviada del censo → §13 (listbox / menubar / navigation-menu están en la
misma entrada y tampoco aparecen en el fichero).
Nombres, sobre la propuesta: `selection-checkbox-{width,height}` son UN knob →
`selection-checkbox-size`; `on-selection-checkbox-bg` → `checked-selection-checkbox-fg`
(es la tinta del glifo, y `on-` es prefijo reservado); `border` partido en
`border-width` + color (molde `command.input-border*`), que el checkbox
comparte; `padding` uniforme se queda `padding` (precedente
`command.viewport-padding`); `max-block` entra al contrato (molde
`command.viewport-max-block`).
Artefactos y gates:
- Sonda antes/después = 0 diffs en 6.090 valores × 7 estados. Determinismo
verificado: dos corridas del mismo código, 0 diffs.
- R-5.4: 37/38 tokens mueven un computed. `invalid-border` adjudicado EN EL
LEDGER con su razón medida (la demo arranca con su control en off; activado
a mano alcanza: oklch(0.9555 0.0207 13.86) → rgb(1,2,3)).
- censo --only 85 % · component:audit PASS (R-5.3 incluida) · eidos-lint
invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el único es el
conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero
limpio (72 globales, ninguno mío).
- README con su «Talla y tema» y tab `Tokens` en la demo (38 claves, leídas en
vivo). El tab `Recipe` documentaba la regla retirada: corregido.
Las fichas de calendar / carousel / command / month-grid / range-calendar /
table / year-grid entran porque `--report` regenera el informe entero: sólo
actualizan NÚMEROS DE LÍNEA de commits ya hechos que no lo regeneraron. Cero
cambios de alcance. Las otras 161, que sólo cambiaban la fecha, se revirtieron.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'grid-list' : {
'invalid-border' :
'consumed only under [data-invalid]; the demo boots with its invalid control off, so the rule never matches at measurement. Toggled by hand 2026-08-21 → reaches (oklch(0.9555 0.0207 13.86) → rgb(1,2,3))'
uix(theming): los dos rojos que quedaban — y el eje `size` del picker MUERE EN EL PORTAL
Medidos uno a uno los 18 de `metrics` y los 25 de `color-picker`, los últimos
del bloque viejo que el guard R-5.4 arrastraba sin adjudicar. Los dos quedan en
verde. Sin cambios de código: esto es medición y registro.
`metrics`: los 18 están VIVOS. Ninguno miente; lo que faltaba era el escenario.
Seis piden el paso `xxl` (la demo llega a `xl`), dos el icono SIN `data-color`
(su defecto sólo pinta cuando no hay color, y todos los de la demo llevan uno),
dos las formas `circle`/`square`, cuatro la fila de acciones, uno el gráfico en
`end`, uno el borde de la variante `outline`, y dos el sello de sema durante el
hold (`data-event-family=signal` + `phase=active`): el destello del valor sólo
se mide estampando el sello a mano, porque cuando el guard lee ya se ha ido.
`color-picker`: 23 de 25 vivos, y los otros dos son un DEFECTO REAL.
EL EJE `size` MUERE EN EL PORTAL, dos veces. `--_color-picker-content-width`
se declara TRES veces y no lo consume NADIE: la anchura del panel la fija
`--_popover-content-width-override`, cableada al paso `md`. Un picker `sm` y
uno `lg` abren el MISMO panel de 344 px, y `content-width-sm`/`-lg` son dos
públicos que no mueven nada. Lo mismo con las muestras dentro del panel
(`--_color-picker-swatch-size` clavado a `md`): fuera del portal los tres
pasos alcanzan (20/24/28 px → 1234 px), dentro sólo el `md`.
La causa es una y vale como ley: el portal es una FRONTERA DE CASCADA — un
privado declarado en la raíz del componente no la cruza. Misma familia que la
capa `list-surface` inalcanzable desde `:root`. El arreglo honesto es una
escala por talla en el ámbito del CONTENIDO (que sí lleva su `data-size`);
mueve píxel en `sm` y `lg`, así que es firma → §13.
Tres cegueras del instrumento, medidas:
- El guard CONGELA las transiciones antes de medir —el arreglo que hizo
medibles radio-cards y compañía— y `transition-duration`/`transition-ease`
SON la transición: sin congelar alcanzan (0.12s → 11.5s). Es genérico:
cualquier componente con esos dos públicos los reportará muertos.
- Una lectura inmediata de una propiedad EN TRANSICIÓN devuelve el valor
VIEJO. Es lo que hacía ilegible el fondo del cuentagotas: ni con
`!important` en línea se movía, porque la transición aún no había corrido.
- La DEMO pisa el cuentagotas con su regla de ámbito (`.cp-tools-row` le pone
`inline-size:auto` y `background`), así que sus dos tokens sólo se miden
devolviéndole el consumo del token.
Gates: `theming:sentinel metrics` y `color-picker` en verde (54/72 y 46/71,
el resto adjudicado por escrito) · `check` sin errores propios · `docs:check`
0/0 · formato sin regresión (los tres ficheros ya venían avisados en HEAD).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
metrics : {
// Measured one by one 2026-08-23 (transitions frozen, each condition forced).
// Every one of the 16 is ALIVE: the demo mounts neither the xxl step, nor the
// icon without a data-color, nor the actions row, nor an end-placed chart.
'gap-xxl' : 'the size sweep of the guard does not reach the xxl step; forced data-size=xxl -> reaches (12px -> 1234px)' ,
'icon-gap-xxl' : 'same xxl step, read on the padding-inline-start of an icon-start root; forced -> reaches (92px -> 1306px)' ,
'icon-box-xxl' : 'same xxl step; forced -> reaches (72px -> 1234px)' ,
'label-font-size-xxl' : 'same xxl step; forced -> reaches (28px -> 1234px)' ,
'value-font-size-xxl' : 'same xxl step; forced -> reaches (80px -> 1234px)' ,
'description-font-size-xxl' : 'same xxl step; forced -> reaches (28px -> 1234px)' ,
'icon-border-width' : 'only the outline variant draws a stroke and the demo runs soft; forced data-variant=outline -> reaches (1px -> 1234px)' ,
'icon-bg' : 'the no-data-color default, and every demo icon carries a colour that overrides the hook; removed data-color -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))' ,
'icon-fg' : 'same no-data-color default; removed data-color -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
'actions-gap' : 'the actions row is conditional and the demo mounts none; mounted in place -> reaches (12px -> 1234px)' ,
'actions-margin-block-start' : 'same row; mounted -> reaches (12px -> 1234px)' ,
'actions-padding-block-start' : 'same row; mounted -> reaches (12px -> 1234px)' ,
'actions-border-fg' : 'same row; mounted -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))' ,
'chart-right-width' : 'only an end-placed chart reserves the gutter and the demo places them full; forced data-placement=end -> reaches (112px -> 1234px)' ,
'flash-duration' : 'the flash rule needs the sema stamp of the hold (data-event-family=signal + phase=active), which is gone by the time the guard reads; stamped by hand -> reaches (0.32s -> 11.5s)' ,
'flash-ease' : 'same stamp; forced -> reaches (cubic-bezier(0, 0, 0.2, 1) -> steps(7))' ,
'icon-circle-radius' : 'the demo mounts only the rounded shape; forced data-shape=circle -> reaches (9999px -> 1234px)' ,
'icon-square-radius' : 'same, the demo mounts only rounded; forced data-shape=square -> reaches (6px -> 1234px)'
} ,
'color-picker' : {
// Measured one by one 2026-08-23 (picker open, transitions frozen unless the
// token IS the transition). 21 of the 23 are alive; the two width steps are a
// real reach defect, registered in next-features 13.
'content-width-sm' : 'the private --_color-picker-content-width is declared three times and consumed ZERO times: the portalled panel hardwires calc(content-width-md + 2rem), so no sm step can reach it. Registered in next-features 13 - wiring or retiring it moves pixel' ,
'content-width-lg' : 'same orphan private; the panel width has no size axis today. Registered in next-features 13' ,
'swatch-size-sm' : 'inside the portalled content the private is pinned to swatch-size-md with no size variants; on a swatch outside the portal the step reaches (20px -> 1234px). Same entry in next-features 13' ,
'swatch-size-md' : 'the demo mounts no swatch of any kind; mounted in place -> reaches (24px -> 1234px)' ,
'swatch-size-lg' : 'same pinning inside the portal; outside it reaches (28px -> 1234px)' ,
'swatch-radius' : 'presets and saved swatches are conditional and the demo mounts none; mounted -> reaches (4px -> 1234px)' ,
'swatch-border' : 'same swatches; mounted -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))' ,
'swatch-checked-ring' : 'same swatches, and only under [data-checked]; mounted and checked -> reaches (the 3px ring turns rgb(1,2,3))' ,
'swatch-gap' : 'the swatch group is conditional and the demo mounts none; mounted -> reaches (6px -> 1234px)' ,
'swatch-border-width' : 'same swatches; mounted -> reaches (1px -> 1234px)' ,
'swatch-ring-gap' : 'same swatches, and only under [data-checked] or :focus-visible; mounted and checked -> reaches (the 1px inner ring turns rgb(1,2,3))' ,
'transparency-cell-alt' : 'the standalone transparency grid utility is not mounted in the demo; mounted -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))' ,
'eye-dropper-size' : 'the DEMO route overrides the eye dropper with its own scoped rule (.cp-tools-row sets inline-size:auto + aspect-ratio); with the token consumption restored -> reaches (30px -> 1234px)' ,
'eye-dropper-bg' : 'same demo override (background:transparent); restored and frozen -> reaches (transparent -> rgb(1,2,3))' ,
'hue-0' : 'the rainbow ramp needs data-x-channel=hue and the demo area runs saturation/brightness; forced -> reaches (rgb(255,0,0) -> rgb(1,2,3))' ,
'hue-60' : 'same hue ramp; forced data-x-channel=hue -> reaches' ,
'hue-120' : 'same hue ramp; forced data-x-channel=hue -> reaches' ,
'hue-180' : 'same hue ramp; forced data-x-channel=hue -> reaches' ,
'hue-240' : 'same hue ramp; forced data-x-channel=hue -> reaches' ,
'hue-300' : 'same hue ramp; forced data-x-channel=hue -> reaches' ,
'hue-360' : 'same hue ramp; forced data-x-channel=hue -> reaches' ,
'saturation-floor' : 'the ::before ramp needs x-channel=hue AND y-channel=saturation; forced -> reaches (rgb(128,128,128) -> rgb(1,2,3))' ,
'transition-duration' : 'the guard freezes transitions before measuring (the forced-measurement fix) and these two tokens ARE the transition; measured unfrozen on the eye dropper -> reaches (0.12s -> 11.5s)' ,
'transition-ease' : 'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))' ,
'disabled-opacity' : 'no disabled trigger or eye dropper in the demo; forced data-disabled -> reaches (0.4 -> 0.137)'
uix(menubar): temable — 15 % → 88 %, y el plano `overlay` GANA a la receta
30 claves nuevas (5 → 35): la barra (superficie, marco, radio —uno solo, la
receta lo desacopla de la talla a propósito y el motor de forma deriva el del
disparador de él— y las tres escalas `gap`/`padding`/`height`), el disparador
(tinta, tipografía, escala de `font-size` y de `padding-inline`, el par de la
entrada ABIERTA y la tinta deshabilitada) y el Panel `role=dialog` (tinta +
escala). Global 62 % → 63 %.
Los pasos leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`),
no el primitivo crudo que el CSS venía consumiendo; y el
`calc(var(--space-1) * 0.5)` del paso `sm` es `var(--space-0-5)` exacto.
EL HUECO DEL PANEL DEJA DE REPETIR EL VALOR DEL PADDING. Las tres reglas de
`--floating-gap` compensan el sangrado del disparador dentro de la barra con lo
que era una COPIA del padding; ahora leen `--menubar-padding-{k}`, su fuente —
si un tema ensancha el padding, el panel deja de quedar a ras de la barra. No
pueden leer el privado `--_menubar-padding`: esas superficies viajan POR PORTAL
y un privado declarado en la raíz no cruza esa frontera.
⚠ EL PLANO `overlay` GANA A LA RECETA — cuarta prueba de §12.9 y la primera con
MECANISMO. Acuñé `panel-font-family` y `panel-line-height` sobre declaraciones
que la receta YA tenía, y el centinela las dio muertas. Medido:
`[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y
más tarde en la cascada, así que gana; del mismo bloque, `color` y `font-size`
—que el plano no declara— sí alcanzan. Retiradas del contrato y devueltas a su
fuente literal, con la razón escrita en el CSS. Mientras §12.9 no se firme, un
token de tipografía en CUALQUIER superficie `overlay` miente, y son veinte.
EL HOVER NO SE ACUÑA: ES DEL EJE navigation-menu. `background:
var(--color-surface-overlay)` sobre el disparador mata la capa del sistema
—medido: `background-image` computa `none` en hover con `--state-hover` vivo—,
por las dos razones que `navigation-menu.css` ya documenta al retirar esta
MISMA regla: el shorthand resetea `background-image`, y la regla del arquetipo
va en `:where()`, especificidad CERO. Acuñar un `hover-trigger-bg` habría
fosilizado la invención.
Instrumento: la barra suelta DOS superficies y el Panel —el que lleva sus
knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`)
y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó
`openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`,
que aquí no existe, así que volvía a pulsar el disparador antes de cada token y
CERRABA el panel — media corrida contra una superficie cerrada.
Gates: sonda 0 diffs en 1.470 valores × 8 estados · centinela 34/35 (la
deshabilitada, adjudicada) · censo 88 % · `--names` 0 desviadas ·
`component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el
rojo conocido ajeno (`skin-media-player`) · `check` sin errores propios ·
capturas de barra, tallas sm/lg, menú abierto y panel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
uix(dropdown-menu): temable — 57 % → 96 %, y la tipografía del gemelo se RETIRA
9 claves nuevas (20 → 28) y 18 respaldos muertos retirados. Y una corrección a la
baja, a propósito: `context-menu` 90 % → 87 %.
Es el GEMELO de `context-menu`: mismos nombres, misma doctrina, mismas dos
limpiezas de paso —el `font-family` era respaldo de SÍ MISMO y el peso del
encabezado llevaba un `500` muerto detrás del token que ya vale 500—. Cosidos la
tinta del panel y de la fila, su interlínea, el encabezado entero (tinta, peso y
tracking) y el galón del submenú, que ahora tiene sus dos knobs: trazo y caja.
EL FILO DEL SEPARADOR DEJA DE SER UN `1px` SUELTO y pasa a ser el trazo fino del
sistema (`--border-width`, idéntico hoy): un tema que engorde el hairline lo
engorda también aquí.
Los 18 respaldos: cada `var(--dropdown-menu-x, …)` llevaba detrás un valor que NO
puede ocurrir —las 18 claves están en el contrato— y sólo servía para envejecer
contra él. Ninguno movía un píxel; dos ya discrepaban del contrato en la FUENTE.
⚠ `content-font-family` NO SE ACUÑA, Y LA DEL GEMELO SE RETIRA. El panel lleva
`data-depth='overlay'` y el PLANO declara la familia con la misma especificidad
y más tarde en la cascada: gana él. Quinta prueba de §12.9. `context-menu` la
tenía acuñada desde esta misma sesión CON una adjudicación escrita, y eso era
media doctrina: un público que no alcanza es un token que miente (R-5.4). Las
dos recetas vuelven a su fuente literal con la razón encima.
Un tercer caso —`combobox`, misma clave, mismo plano— queda medido y registrado
en §13; no se toca aquí porque no es el componente en curso, y cuando §12.9 se
firme la decisión será la misma para las cuatro.
Gates: sonda 0 diffs en 928 valores × 8 estados (22 nodos con el panel abierto) ·
centinela 27/28 con la deshabilitada adjudicada · censo 96 % · `component:audit`
PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 ·
captura del panel abierto.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'dropdown-menu' : {
'item-disabled-opacity' :
'no disabled item in the demo menu; forced data-disabled 2026-08-23 -> reaches (0.55 -> 0.137)'
} ,
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página
Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de
paleta del placeholder de color, que no es deuda.
Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el
error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen
CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón.
Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a
pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar
todas las recetas que brillan.
LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su
estado de error y el navegador reintenta, así que sonda y guard morían con un
`TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load`
si la espera de red se agota — la ociosidad era una comodidad, no una garantía.
UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO:
`placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano.
Emulando la preferencia alcanza, y vale para cualquier token detrás de una media
query de preferencia.
Y una lectura fantasma cazada por repetición: el primer diff acusó
`opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo
falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la
medida: 0 diffs. Un comentario CSS no puede mover un computado.
Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación
aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las
tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo
conocido ajeno · `rtl:check` 0 · `docs:check` 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
image : {
'fade-duration' : 'the guard freezes transitions and this token IS the fade; measured unfrozen 2026-08-23 -> reaches (0.18s -> 11.5s)' ,
'fade-ease' : 'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0, 0, 0.2, 1) -> steps(7))' ,
'placeholder-base' : 'consumed ONLY inside @media (prefers-reduced-motion: reduce), where the shimmer is replaced by a flat base; the guard runs with motion on. Measured with reducedMotion emulated -> reaches (oklch(0.9821 0 0) -> rgb(1,2,3))'
} ,
uix(checkbox): temable — 59 % → 72 %, y un HUÉRFANO que el test no puede ver
3 claves nuevas, 3 literales anotados y un token del contrato CABLEADO. El 28 %
que queda es el puente de paleta (`--_checkbox-palette-*`, THM-2): ocho filas
que no son deuda.
EL HALLAZGO: `indicator-fg` ERA UN HUÉRFANO QUE EL TEST DE HUÉRFANOS NO PUEDE
VER. La clave llevaba en el contrato desde siempre, pero la receta pintaba la
tinta de la casilla marcada leyendo `--color-content-on-solid` DIRECTAMENTE, sin
nombrarla nunca — y el test busca el NOMBRE en los ficheros del componente, así
que un token que nadie menciona pasa por vivo. Cableado
(`--_checkbox-palette-text: var(--checkbox-indicator-fg)`), computado idéntico, y
el propio guard lo confirmó marcando su adjudicación como STALE: ahora mueve. Es
la clase inversa del `--popover-bg` que vivía de un COMENTARIO.
Acuñado el estado INVALID, que gana a todas las variantes y leía el primitivo:
el borde y el DOBLE ANILLO DE FOCO (hueco + anillo), que era una sombra con dos
colores cableados. La geometría (2px/4px) se queda dentro de la declaración: es
la forma del anillo, no una perilla.
Tres literales pasan a excepción por identidad: el glifo llena su caja (`100 %`
× 2) y la etiqueta de grupo es su texto (`fit-content`).
Los 26 rojos del guard, medidos — y dos son trampas de ALCANCE del instrumento:
- El glifo SÓLO existe dentro de una casilla MARCADA, y la primera de la demo
está vacía: medir el trazo por talla sobre «el primer indicador del documento»
mide otro nodo. Con el path dentro de la misma casilla, los cinco pasos
alcanzan.
- La etiqueta de grupo lleva su PROPIO `[data-disabled]`: no lo hereda del
grupo, así que forzar el grupo no pinta nada.
El resto es lo esperable (invalid, disabled, los cuatro de transición que el
guard congela) y los ocho de TONO, que son la supersesión de la cascada de
paleta medida hoy en `button`: adjudicados con el patrón compartido.
Gates: sonda 0 diffs en 2.688 valores × 7 estados (13 nodos) · centinela 22/47
con todo lo demás adjudicado · censo 72 % · `component:audit` PASS · suite eidos
con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura de marcada,
invalid, disabled, xl y tono affirm.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
checkbox : {
// Measured one by one 2026-08-23. Two scoping traps of the instrument: the
// glyph only exists inside a CHECKED box, and the group label carries its
// own [data-disabled] (it is not inherited from the group).
'size-xs-indicator-stroke' : 'the glyph exists only inside a CHECKED box and the first checkbox of the demo is empty; measured on a checked one with data-size forced -> reaches (2px -> 7px)' ,
'size-sm-indicator-stroke' : 'same, forced -> reaches (2px -> 7px)' ,
'size-md-indicator-stroke' : 'same, forced -> reaches (2px -> 7px)' ,
'size-lg-indicator-stroke' : 'same, forced -> reaches (2px -> 7px)' ,
'size-xl-indicator-stroke' : 'same, forced -> reaches (2px -> 7px)' ,
'invalid-border' : 'only under [data-invalid]; forced -> reaches (oklch(0.9555 0.0207 13.86) -> rgb(1,2,3))' ,
'invalid-ring' : 'the invalid focus ring: needs [data-invalid] AND :focus-visible, which the guard blurs on purpose; focused by hand -> reaches (the 4px ring turns rgb(1,2,3))' ,
'invalid-ring-gap' : 'same double ring; forced -> reaches (the 2px gap turns rgb(4,5,6))' ,
'disabled-bg' : 'only under [data-disabled]; forced -> reaches' ,
'disabled-border' : 'only under [data-disabled]; forced -> reaches' ,
'disabled-opacity' : 'only under [data-disabled]; forced -> reaches (0.4 -> 0.137)' ,
'disabled-indicator-fg' : 'needs [data-disabled] AND a checked box (the glyph); forced on a checked one -> reaches' ,
'disabled-group-label-fg' : 'the label carries its OWN [data-disabled] - it is not inherited from the group, so forcing the group does nothing; set on the label -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))' ,
'transition-duration' : 'the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s)' ,
'transition-ease' : 'same instrument limit; measured unfrozen -> reaches' ,
'stroke-duration' : 'same, on the check path dash animation; measured unfrozen -> reaches (0.12s -> 11.5s)' ,
'stroke-ease' : 'same, measured unfrozen -> reaches'
} ,
uix(anchor-nav): temable — 20 % → 100 %, y el censo aprende los bordes POR LADO
11 claves nuevas (2 → 13). Tenía dos knobs y todo lo demás a pelo: la escala del
índice (que ahora lee el bundle, no `--font-size-sm` suelto), el relleno y el
radio del enlace, sus tres tintas —reposo, hover y activo—, el peso del activo y
LAS DOS TINTAS DEL RAÍL, que es lo que da nombre al componente.
EL RAÍL ACTIVO ESTABA INVISIBLE PARA EL CENSO. Su color vive en un
`border-inline-start-color`, y `KNOB_PROPS` sólo conocía `border-color` y los
shorthands: los ocho longhands por lado (`border-{top,bottom,left,right,
block-start,block-end,inline-start,inline-end}-{color,width}`) no eran knobs para
la medida. El centinela los mira desde el 2026-08-22 —los añadió cuando prose y
nav-tree leyeron muertos—; el censo, no. Corregido: +30 FILAS EN EL CATÁLOGO
(5.101 → 5.131 knobs), y esta ficha baja de 20 % a 18 % antes de coserse, que es
lo honesto: el censo promete sobre-reportar, nunca infra-reportar.
La tinta de reposo del raíl estaba escondida de otra manera: dentro del shorthand
`border-inline-start: var(--anchor-nav-rail-width) solid var(--color-border-subtle)`,
que el censo clasifica por el PRIMER token que casa —el trazo, ya público— y da
la fila por alcanzable. No hizo falta partir el shorthand: basta con nombrar el
color dentro.
Un susto útil: una coma que faltaba en el contrato rompió el build de tokens, y
lo que lo delató fue la SONDA midiendo 0 nodos — la página no renderizaba. El
recuento de nodos es el canario, tal como dice el handoff.
Gates: sonda 0 diffs en 1.760 valores × 7 estados (8 nodos) · centinela 11/12 con
el radio del foco adjudicado (el guard desenfoca a propósito) · censo 100 % ·
`component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 ·
`docs:check` 0 · captura del raíl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'anchor-nav' : {
'link-radius' :
'the link only rounds under :focus-visible (the ring corner), and the guard blurs on purpose; focused from the keyboard 2026-08-23 -> reaches (4px -> 1234px)'
} ,
uix(tooltip): temable — 52 % → 75 %, y el plano ANULA SUS TRES VARIANTES
3 claves nuevas y 4 literales anotados. Subiría más, pero medir destapó que cinco
de sus claves no pintan: el plano `overlay` le gana la superficie ENTERA.
Acuñados el desenfoque del velo `ghost` y las dos tintas de la flecha `outline`,
que se pintan por selector HERMANO desde el contenido.
⚠⚠ EL PLANO NO SÓLO SE QUEDA LA TIPOGRAFÍA: SE QUEDA EL FONDO, EL BORDE Y LA
SOMBRA. `[data-depth='overlay']` declara las tres con la misma especificidad que
`[data-tooltip-content]` y más tarde en la cascada. Medido: escribir
`--tooltip-bg`, `--tooltip-border` o `--tooltip-shadow` no mueve NADA, mientras
`--depth-overlay-surface` / `-border` / `-shadow` repintan el panel. Con
`font-family` y `line-height`, son cinco públicos que no alcanzan.
Y LA CONSECUENCIA VISUAL ES PEOR QUE LA CONTABLE: LAS TRES VARIANTES SON LA
MISMA. Medidas `solid`, `outline` y `ghost` con el panel abierto: fondo, borde y
sombra IDÉNTICOS en las tres. `outline` es indistinguible de `solid`; `ghost`
sólo se diferencia por un `backdrop-filter` que no se ve porque la superficie que
tiene delante es opaca. La máquina de variantes existe, alimenta privados, y el
plano la anula entera. → §13.
Por eso no se acuñó `ghost-bg`: lo intenté, el centinela lo delató y se retiró
antes de commitear. Las cinco que ya estaban quedan ADJUDICADAS, no retiradas:
son la FUENTE de esa máquina de variantes, y borrarlas dejaría inválidos los
privados sin arreglar nada. La corrección de fondo es la precedencia del plano —
la misma firma §12.9 que arrastran menubar, dropdown-menu y combobox.
`content-z` sí está vivo, pero en otro nodo: aterriza en el ENVOLTORIO flotante
(90 → 4321), que no lleva ningún `data-tooltip-*` y queda fuera del conjunto
medido.
Instrumento: un tooltip abre por HOVER, no por clic (`openBy: 'hover'`, como
link-preview) — con eso pasó de 0/24 a 14/24. El resto se midió con el
interruptor `open` de la demo: un panel abierto por puntero exige el ratón quieto
sobre el disparador toda la pasada, y no es medida fiable.
Gates: sonda 0 diffs en 256 valores × 8 estados · centinela 15/23 con las ocho
adjudicadas · censo 75 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0
· `docs:check` 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
tooltip : {
// Measured 2026-08-23 with the demo's `open` switch (a hover-opened panel is
// not reliable enough to measure: it needs the pointer parked on the trigger
// for the whole pass).
'content-z' : 'it lands on the floating WRAPPER, which carries no data-tooltip-* attr and so is outside the measured set; read on the wrapper -> reaches (90 -> 4321)' ,
'bg' : 'SUPERSEDED by the overlay plane: [data-depth=overlay] declares background at the same specificity and later in the cascade, so the recipe loses. Measured: --tooltip-bg moves nothing, --depth-overlay-surface repaints the panel. Registered in next-features 13 with the variant fallout' ,
'border' : 'same supersession: --depth-overlay-border repaints it, --tooltip-border does not' ,
'shadow' : 'same supersession: --depth-overlay-shadow repaints it, --tooltip-shadow does not' ,
'font-family' : 'same plane, the typography half of it (PLAN 12.9, unsigned)' ,
'line-height' : 'same plane, same open decision' ,
'outline-arrow-bg' : 'the arrow is painted through a SIBLING selector that needs the content carrying data-variant=outline AND the panel open; measured with the demo switch -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))' ,
'outline-arrow-border' : 'same sibling rule; measured -> reaches (oklch(0.7921 0 0) -> rgb(1,2,3))'
} ,
uix(theming): cola pequeña — seis componentes, y una receta que TAPABA sus claves
`section` (cuatro claves resucitadas), `sticky` 0 → 100 %, `button-group` 40 →
100 %, `qr-code` 43 → 100 %, `toggle-group` 33 → 50 % (su techo) y `split-button`
adjudicado en 50 % (el suyo). Cero claves nuevas. Global 65 %, 25 componentes al
100 %, y los de alcance <20 % bajan de 14 a 12.
EL HALLAZGO ESTÁ EN `section`: LA RECETA TAPABA SUS PROPIAS CLAVES PÚBLICAS.
Declaraba los cuatro pasos de relleno en el host
(`[data-box][data-section] { --section-padding-block-sm: … }`), y esa declaración
gana a la que el contrato emite en `:root`: un tema que escribiera
`--section-padding-block-lg` perdía contra la receta. Borrado el bloque —mismos
valores, una sola fuente—, computado idéntico (48px antes y después), y las
cuatro claves alcanzan.
Lo demás es IDENTIDAD: once `100 %` entre las seis recetas y el centinela de 2px
del sticky —el hilo invisible que el observador vigila—, todos con su razón
escrita. Más un respaldo muerto retirado en `toggle-group`, sobre un token
PRESTADO de `toggle`: el velo de un grupo deshabilitado ES el del toggle, y se
tema en su ficha (§5.3-3).
Y UNA CLASE DE ADJUDICACIÓN NUEVA, QUE SE REPETIRÁ: UN PASO POR INSTANCIA.
`section` y `qr-code` mapean su prop de talla a `var(--{c}-{eje}-{k})` y la
escriben INLINE, así que sólo el paso ACTIVO alcanza; los otros leen muertos
hasta que la prop cambia. No es deuda ni ceguera del instrumento: es cómo
funciona una escala resuelta por prop.
Gates: los seis guards en verde · censo 65 % · `component:audit` PASS · suite
eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
section : {
// ONE STEP PER INSTANCE: the wrapper maps the `padding` prop to
// var(--section-padding-block-{k}) and writes it INLINE, so only the active
// step is reachable at a time. Forcing another step reaches (2026-08-23).
'padding-block-sm' : 'one step per instance; the demo runs md. Forced the step inline -> reaches' ,
'padding-block-lg' : 'same, one step per instance' ,
'padding-block-xl' : 'same; forced -> reaches (96px -> 123px)'
} ,
'qr-code' : {
'size-xs' : 'one step per instance: the wrapper writes var(--qr-code-size-{k}) inline from the size prop and the demo runs lg. Forced -> reaches (96px -> 123px)' ,
'size-sm' : 'same, one step per instance' ,
'size-md' : 'same, one step per instance' ,
'size-xl' : 'same, one step per instance' ,
'fg' : 'painted as the SVG fill of the module path, not as color on the root; read on the path -> reaches (rgb(24,24,27) -> rgb(1,2,3))' ,
'bg' : 'painted as the fill of the backing rect, not as background on the root; read on the rect -> reaches (white -> rgb(1,2,3))'
} ,
'toggle-group' : {
gap : 'the demo group is ATTACHED (segmented), where the gap collapses to 0; removed data-attached -> reaches (4px -> 123px)'
} ,
'button-group' : {
'divider-strength' :
'the seam only draws between ATTACHED buttons of a filled variant (solid/soft/ghost/plain) and the demo runs surface; forced -> reaches (0.18 -> 0.9 alpha)'
} ,
uix(audio-player): temable — 37 % → 56 %, y el instrumento medía CERO nodos
3 claves nuevas (5 → 8) y un literal anotado. El 44 % restante es PRÉSTAMO CON
DUEÑO, no deuda: ocho filas que leen `--media-player-*` porque este componente ES
la piel de audio de ese chasis, y una ficha nunca propone duplicar un token
prestado con prefijo propio (§5.3-3).
Acuñado lo único que ESTE reproductor añade sobre el chasis: el peso del título y
la insignia LIVE en versalitas espaciadas (peso + tracking). Los dos `600` pasan
a `var(--font-weight-semibold)`: el mismo valor, con nombre.
Y EL INSTRUMENTO ESTABA CIEGO DEL TODO: MEDÍA 0 NODOS. Tres razones encadenadas:
1. `/uix/components/audio-player` es un 404 — el CUARTO canónico sin ruta propia
(picker-shell, mockup, surface y éste). Se mide en la demo del chasis.
2. Sus partes no llevan `data-audio-player-*`: son `data-media-player-*`. Con
`extraNodes`, la sonda pasa de 0 a 48 NODOS.
3. Sólo existe tras el chip `media: audio` de esa demo: sonda (`DEMO_VARIANTS`) y
guard (`openWith` + `openMarker`) lo pulsan ahora, y el guard barre además las
cuatro variantes (card/row/bar/inline).
Con eso, 0/8 → 6/8. Las dos que quedan son la insignia LIVE, que sólo existe
mientras el proveedor refleja un directo: montada con `[data-live]`, alcanza
(600 → 333 y 1,28px → 7px).
Gates: sonda con 48 nodos (antes 0) · centinela 6/8 con las dos adjudicadas ·
censo 56 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 ·
`docs:check` 0 · equivalencia comprobada en el computado (título 600, tracking
1,28px).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'audio-player' : {
'live-font-weight' : 'the LIVE badge only exists while the provider reflects a live stream (display:none otherwise) and the demo plays a file; mounted with [data-live] 2026-08-23 -> reaches (600 -> 333)' ,
'live-letter-spacing' : 'same badge; mounted -> reaches (1.28px -> 7px)'
} ,
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
button : {
// Measured one by one 2026-08-23. The icon scale needs a real <svg> child,
// the radius scale is an OPT-IN prop the demo does not set, and the four
// palette-* slots are RESOLVED names: a :root write loses by design.
'icon-size-xs' : 'the demo button carries no icon; a real <svg> child mounted and the size forced -> reaches (14px -> 123px)' ,
'icon-size-sm' : 'same, no icon in the demo; mounted -> reaches (16px -> 123px)' ,
'icon-size-md' : 'same, no icon in the demo; mounted -> reaches (18px -> 123px)' ,
'icon-size-lg' : 'same, no icon in the demo; mounted -> reaches (20px -> 123px)' ,
'icon-size-xl' : 'same, no icon in the demo; mounted -> reaches (32px -> 123px)' ,
'radius-sm' : 'the radius scale is an opt-in prop ([data-rounded]) and the demo does not set it; forced -> reaches (4px -> 1234px)' ,
'radius-lg' : 'same opt-in attribute; forced -> reaches (10px -> 1234px)' ,
'radius-xl' : 'same opt-in attribute; forced -> reaches (16px -> 1234px)' ,
'radius-full' : 'same opt-in attribute; forced data-rounded=full -> reaches (9999px -> 1234px)' ,
'transition-duration' : 'the guard freezes transitions to measure and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s, five properties)' ,
'transition-ease' : 'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))' ,
'disabled-opacity' : 'no disabled button in the demo; forced data-disabled -> reaches (0.4 -> 0.137)' ,
'primary-track' : 'the FALLBACK of the palette forward: it acts only on an instance with NO data-color, and the demo stamps data-color=primary. Removed the attribute -> reaches. See the pattern entry below for the other seven tones' ,
'primary-element' : 'same fallback, same condition' ,
'primary-border' : 'same fallback, same condition' ,
'primary-solid' : 'same fallback: without data-color it repaints (oklch(0.5556 0.1829 305.86) -> rgb(1,2,3)); with data-color=primary the generic forward rule supersedes it' ,
'primary-solid-hover' : 'same fallback, same condition' ,
'primary-text' : 'same fallback, same condition' ,
'primary-contrast' : 'same fallback, same condition' ,
'palette-track' : 'a RESOLVED name, declared on [data-button] by the forward, so a :root write loses BY DESIGN - a theme moves the tone or the scale. Measured on a soft button: from :root nothing, from the node oklch(0.9932 0.0034 325.6) -> rgb(4,5,6)' ,
'palette-element' : 'same resolved name; the theme moves its coordinate' ,
'palette-border' : 'same resolved name; the theme moves its coordinate' ,
'palette-text' : 'same resolved name; the theme moves its coordinate'
} ,
uix(menubar): temable — 15 % → 88 %, y el plano `overlay` GANA a la receta
30 claves nuevas (5 → 35): la barra (superficie, marco, radio —uno solo, la
receta lo desacopla de la talla a propósito y el motor de forma deriva el del
disparador de él— y las tres escalas `gap`/`padding`/`height`), el disparador
(tinta, tipografía, escala de `font-size` y de `padding-inline`, el par de la
entrada ABIERTA y la tinta deshabilitada) y el Panel `role=dialog` (tinta +
escala). Global 62 % → 63 %.
Los pasos leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`),
no el primitivo crudo que el CSS venía consumiendo; y el
`calc(var(--space-1) * 0.5)` del paso `sm` es `var(--space-0-5)` exacto.
EL HUECO DEL PANEL DEJA DE REPETIR EL VALOR DEL PADDING. Las tres reglas de
`--floating-gap` compensan el sangrado del disparador dentro de la barra con lo
que era una COPIA del padding; ahora leen `--menubar-padding-{k}`, su fuente —
si un tema ensancha el padding, el panel deja de quedar a ras de la barra. No
pueden leer el privado `--_menubar-padding`: esas superficies viajan POR PORTAL
y un privado declarado en la raíz no cruza esa frontera.
⚠ EL PLANO `overlay` GANA A LA RECETA — cuarta prueba de §12.9 y la primera con
MECANISMO. Acuñé `panel-font-family` y `panel-line-height` sobre declaraciones
que la receta YA tenía, y el centinela las dio muertas. Medido:
`[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y
más tarde en la cascada, así que gana; del mismo bloque, `color` y `font-size`
—que el plano no declara— sí alcanzan. Retiradas del contrato y devueltas a su
fuente literal, con la razón escrita en el CSS. Mientras §12.9 no se firme, un
token de tipografía en CUALQUIER superficie `overlay` miente, y son veinte.
EL HOVER NO SE ACUÑA: ES DEL EJE navigation-menu. `background:
var(--color-surface-overlay)` sobre el disparador mata la capa del sistema
—medido: `background-image` computa `none` en hover con `--state-hover` vivo—,
por las dos razones que `navigation-menu.css` ya documenta al retirar esta
MISMA regla: el shorthand resetea `background-image`, y la regla del arquetipo
va en `:where()`, especificidad CERO. Acuñar un `hover-trigger-bg` habría
fosilizado la invención.
Instrumento: la barra suelta DOS superficies y el Panel —el que lleva sus
knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`)
y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó
`openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`,
que aquí no existe, así que volvía a pulsar el disparador antes de cada token y
CERRABA el panel — media corrida contra una superficie cerrada.
Gates: sonda 0 diffs en 1.470 valores × 8 estados · centinela 34/35 (la
deshabilitada, adjudicada) · censo 88 % · `--names` 0 desviadas ·
`component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el
rojo conocido ajeno (`skin-media-player`) · `check` sin errores propios ·
capturas de barra, tallas sm/lg, menú abierto y panel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
menubar : {
'disabled-trigger-fg' : 'no disabled entry in the demo bar; forced data-disabled 2026-08-23 -> reaches (oklch(0.61 0 0) -> rgb(1,2,3))'
uix(field): temable — 73 % → 98 %, y sus 18 rojos estaban VIVOS
8 claves nuevas (60 → 68) y 8 literales ANOTADOS. Lo que falta es el PUENTE DE
PALETA (`_palette-text`, THM-2), que no es deuda. Global 63 %.
Field es el componente más COMPUESTO del sistema: lo que otras fichas llaman
«préstamo con dueño» se cobra aquí, así que su techo pesa más que el de nadie.
Acuñados el DISPARADOR DE CONTROL —el icono que abre el calendario, el reloj o
la muestra, y revela la contraseña: `size`, `radius`, `fg` y sus dos estados—,
el indicador `optional` (gemelo del `required`, que ya tenía token) y el lateral
de la etiqueta flotante, que es lo que CORTA el borde bajo el texto. La caja del
disparador se mide en `em` a propósito, así que el token guarda la RAZÓN
(`1.75em`) y no un píxel por talla.
OCHO LITERALES PASAN A EXCEPCIÓN FIRMADA POR IDENTIDAD, no para inflar el ratio:
cuatro `inline-size: 100%` (el campo, el control, el input y el control flotante
llenan su fila), dos `line-height: 1` (un glifo suelto no tiene interlínea) y
dos `1em` del icono dentro del disparador (el glifo ES el tamaño del texto).
LOS 20 ROJOS DEL GUARD ESTABAN VIVOS, uno por uno, y tres enseñan algo:
- `disabled-opacity` vive en el CONTROL, no en la raíz: el guard leía el nodo
equivocado.
- `control-placeholder-fg` no leía porque el input de la demo NO TIENE
`placeholder`: sin el atributo no existe la caja `::placeholder`, y
`getComputedStyle(nd, '::placeholder')` devuelve entonces el estilo del
ELEMENTO. Con un placeholder puesto, alcanza. Es genérico → §13.
- `segment-height` es un token de FAMILIA que field posee y los pickers
consumen —en las rutas de field no lo lee nadie— y además es RESUELTO
(declarado por `[data-field][data-size]`), así que escribirlo en `:root`
pierde POR DISEÑO: el tema mueve la coordenada. Medido en `/time-picker`:
desde `:root` nada, desde el host del campo 28px → 1234px, y moviendo
`--field-control-height-md` 28px → 992px.
El resto son estado (invalid, disabled, readonly, foco), variante ghost,
orientación horizontal, la franja de segmentos, el texto de error —que sólo se
RENDERIZA con el interruptor de la demo: forzar el atributo no hace que Svelte
monte el nodo— y los dos de transición, que el guard congela.
Instrumento: el disparador de control no lo monta NINGUNA demo de field, así que
el guard mide ahora `field` sobre DOS rutas (`/field` + `/date-picker`).
Gates: sonda 0 diffs en 2.280 valores × 8 estados · equivalencia del disparador
comprobada aparte en `/date-picker` (28px = 1.75em con fuente 16px · 4px =
`--radius-sm` · misma tinta) · centinela 48/68 con las 20 adjudicadas · censo
98 % · `--names` 0 · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 ·
suite eidos con el rojo conocido ajeno · `check` sin errores propios · capturas
de reposo, invalid, disabled, etiqueta flotante y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
field : {
// Measured one by one 2026-08-23 on /field, /date-field, /date-picker and
// /time-picker. The 20 are alive; every one needed a state, a variant, a
// route or an instrument the guard does not reach on its own.
'invalid-label-fg' : 'only under [data-invalid] and the demo boots valid; forced -> reaches (oklch(0.5855 0.1743 42.74) -> rgb(1,2,3))' ,
'disabled-label-fg' : 'only under [data-disabled]; forced -> reaches (oklch(0.7921 0 0) -> rgb(1,2,3))' ,
'invalid-control-border' : 'only under [data-invalid]; forced -> reaches (oklch(0.8059 0.1123 59.96) -> rgb(1,2,3))' ,
'disabled-control-bg' : 'only under [data-disabled]; forced -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))' ,
'control-bg-readonly' : 'only under [data-readonly]; forced -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))' ,
'disabled-opacity' : 'it lives on the CONTROL, not on the field root, so the guard read the wrong node; forced data-disabled and read on the control -> reaches (0.4 -> 0.137)' ,
'control-ghost-border' : 'the ghost variant and the demo boots surface; forced data-variant=ghost -> reaches (transparent -> rgb(1,2,3))' ,
'control-ghost-bg' : 'same ghost variant; forced -> reaches (transparent -> rgb(1,2,3))' ,
'layout-column-gap' : 'only in the horizontal orientation; forced data-orientation=horizontal -> reaches (16px -> 1234px)' ,
'focus-control-border' : 'only under :focus-within, and the guard blurs ON PURPOSE (the click-focus poisoning fix); focused from the keyboard -> reaches (the focus ring colour -> rgb(1,2,3))' ,
'focus-control-trigger-fg' : 'only under :focus-visible on the picker trigger, which the guard blurs; focused from the keyboard on /date-picker -> reaches (oklch(0.2435 0 0) -> rgb(1,2,3))' ,
'floating-label-top' : 'floatingLabel is a demo switch that boots OFF, and the label must also be floated (focused or filled); switch on + focus -> reaches (0px -> 1234px)' ,
'floating-label-padding-inline' : 'same switch; turned on -> reaches (4px -> 1234px)' ,
'error-fg' : 'the error text only RENDERS when the field is invalid, and it is the demo switch that mounts it - forcing the data-invalid attribute does not make Svelte render the node; switch on -> reaches (oklch(0.5855 0.1743 42.74) -> rgb(1,2,3))' ,
'control-placeholder-fg' : 'the ::placeholder box exists only when the input HAS a placeholder attribute, and the demo input has none: getComputedStyle then returns the ELEMENT style and the token reads dead. With a placeholder set -> reaches (oklch(0.61 0 0) -> rgb(1,2,3))' ,
'segment-active-bg' : 'the ACTIVE segment being edited: no segment on the field route and none focused; measured on /date-field with the segment clicked -> reaches (the 40% mix -> rgb(1,2,3))' ,
'segment-active-text' : 'same segment; measured on /date-field -> reaches (oklch(0.5168 0.1733 305.88) -> rgb(1,2,3))' ,
'segment-height' : 'a FAMILY token that field OWNS and the pickers CONSUME - nothing on the field routes reads it - and it is RESOLVED (declared per [data-field][data-size]), so a :root write loses BY DESIGN: a theme moves the coordinate. Measured on /time-picker: from :root nothing, from the field host 28px -> 1234px, and moving --field-control-height-md 28px -> 992px' ,
'transition-duration' : 'the guard freezes transitions to measure and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s)' ,
'transition-ease' : 'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))'
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA
7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES
filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en
una regla inválida.
Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el
propio comentario del CSS dice que el hover usa «el mismo color que el anillo de
selección, más claro» y lo que los separa es la opacidad, que ya tenía token—,
la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos
piezas del glifo `+`→`×`. Dos limpiezas de paso: un
`var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí
mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)`
con un respaldo que no puede ocurrir.
Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN
un atenuado (sin atenuar es 1 por definición) y la caja del icono.
⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de
SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta
la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el
`70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el
centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito
encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL.
Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa
—la lección estaba escrita y se repitió—. → §13.
Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el
motor de color, como los rellenos de cada sector. No es superficie de tema.
TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas:
1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir»
lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya
casa, no se pulsa nada (8 → 14 de golpe).
2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden
con `extraNodes` — 37 nodos en reposo contra los 5 con atributo.
3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO:
`rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la
animación congelada computa 0,45 y sigue al token. → §13.
Los cuatro tokens del rim son del look `luminous`, que la demo del componente no
renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo
que forzar): medidos en `/active/docs/agnt`, que sí lo usa.
Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19
con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos
con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin
errores propios · capturas de reposo, sector en hover y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
'onion-menu' : {
// Measured 2026-08-23. The lift needs the transitions frozen; the four rim
// tokens belong to the `luminous` look, which the component demo does not
// render at all (it is a Svelte prop that emits DIFFERENT markup, so no
// attribute can be forced): measured on /active/docs/agnt, which uses it.
'trigger-lift' : 'the lift TRANSITIONS, so an immediate read after the write returns the old value; hovered with transitions frozen -> reaches (translate 0 -2px -> 0 -123px)' ,
'rim-width' : 'the luminous look is not rendered by the component demo; measured on /active/docs/agnt -> reaches (2px -> 17px)' ,
'rim-glow-width' : 'same look, same route -> reaches (9px -> 17px)' ,
'rim-glow-blur' : 'same look, same route -> reaches (blur(4px) -> blur(13px))' ,
'rim-glow-opacity' : 'same look, and the rim is under an ANIMATION: with only the transitions frozen the read returns the animated value (0.482956) and the token looks dead. With animation frozen too it computes 0.45 and follows -> reaches (0.45 -> 0.137)'
uix(switch): temable — 76 % → 81 %, y su tono NEUTRAL cae con los otros once
Una clave nueva (51 → 52) y veinticinco adjudicaciones medidas: trece propias y
doce por patrón. El default no se mueve: diff de computed 0 sobre 1.344 valores
en 36 estados y capturas 2x idénticas.
Censo: 76 % → 81 % (público 16 → 17, global 1 → 0; 22 knobs, 4 privados y 1
sistema fuera). Global 67 % (no baja).
QUÉ ENTRA (1 clave)
invalid-track-border (var(--color-threat-element)). Era el ÚNICO knob de
apariencia que iba a un primitivo global a pelo.
El nombre NO es el `invalid-border` que el catálogo repite seis veces
(checkbox, listbox, grid-list, radio-cards, date-range-field…): esta receta
tiene DOS partes con borde y las nombra — su hermana de estado es
`disabled-track-border` y el pulgar lleva `thumb-border`, así que un nombre
desnudo no diría cuál de las dos tiñe. La forma modificador + parte + ranura ya
está en el catálogo (`invalid-input-border`, `invalid-control-border`).
DOCE CLAVES DE TONO NO PINTAN — Y AQUÍ CAE TAMBIÉN «NEUTRAL»
Es la cascada de paleta ya medida en button / badge / callout: el forward emite
al FINAL un bloque genérico [data-switch][data-color] que resuelve desde el
--palette-* global, misma especificidad (0,2,0), gana el último.
Lo propio de este componente es que el fallback no salva ni al tono por
defecto: soma estampa data-color SIEMPRE (es el color RESUELTO, neutral por
defecto), así que el bloque genérico casa en toda instancia. Medido en checked:
--switch-neutral-solid no mueve nada, --palette-solid sobre el nodo repinta
(oklch(0.5556 …) -> rgb(4,5,6)). En button el neutral sobrevive; aquí no.
Adjudicadas POR PATRÓN, con esa diferencia escrita.
LAS OTRAS TRECE SON ESTADO O INSTRUMENTO
Cinco disabled-* y el invalid-track-border nuevo sólo existen bajo su atributo,
que ningún trigger alcanza (forzados: alcanzan, con su par medido).
Los cinco size-*-thumb-translate son DOS huecos a la vez: pintan sobre
`transform`, que el guard no fotografía, y valen 0px hasta que el interruptor
está encendido (forzado checked: 12/14/16/18/20px -> 1234px).
El par de transición ES la transición que el guard congela (sin congelar:
0,12s -> 11,5s en las cuatro propiedades).
FUERA POR DOCTRINA (los 4 privados que quedan)
--_switch-track-bg y --_switch-track-border-hover son CONMUTADORES: dos fuentes
cada uno (la clave pública del estado apagado, el forward de paleta del
encendido), y aplanarlos obligaría a duplicar cada regla por color.
Las dos lecturas directas de --_switch-palette-solid* (el fondo del track en
hover, la tinta del check dentro del pulgar) son el puente THM-2: un público
encima dejaría que un tema fijara el tono y matara el color= de cada instancia.
El techo honesto de este componente NO es el 100 %.
DEFECTO ANOTADO, NO ARREGLADO
El tinte inválido es --color-threat-element, el paso `element` de la escala:
oklch(0.9555 0.0207 13.86), casi blanco sobre superficie clara. Y en cuanto
entra el puntero, la regla de hover (0,4,0) lo tapa con el borde neutro fuerte.
Es el MISMO valor en checkbox, radio-group y toggle para el mismo estado, así
que es una decisión de escala del sistema (element contra border), no de esta
receta: cambiarlo mueve píxel en cuatro componentes y D-TH.5 lo deja fuera.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (416 valores · 7 estados) + sonda de
estados propia 0 diffs (928 valores · 29 estados: checked, invalid, readonly,
disabled, RTL y las cinco tallas, cada uno con hover y foco) · capturas 2x
antes/después idénticas · R-5.4 27/52, exit 0, sin STALE · censo --only 81 % ·
component:audit PASS · eidos-lint 13 morfo-backed / 0 invalid · vitest
src/uix/eidos 35/36 (el rojo conocido es skin-media-player) · rtl:check 0 ·
docs:check 0 · npm run check sin errores en ficheros del componente · pestaña
Tokens verificada en el navegador (52 filas, 0 errores de consola).
Índice construido a mano desde HEAD sobre los cuatro ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts): hay tres
sesiones más en vuelo sobre ellos. Fuera del commit, como en el de `form`,
docs/audit/theming/README.md — es un agregado global que mezcla ese trabajo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
uix(file-upload): temable — 76 % → 83 %, y una clave suya NO PINTABA en ninguno de sus tres botones
Una clave acuñada, UNA RETIRADA con su declaración muerta, cuatro literales
firmados y cuarenta y cuatro adjudicaciones medidas. El default no se mueve:
diff de computed 0 sobre 11.448 valores en 17 estados y capturas 2x idénticas
byte a byte.
Censo: 76 % → 83 % (público 45, global 1 → 0, literal 4 → 0, excepción 0 → 4;
knobs 61 → 56). Contrato 98 claves (una entra, otra sale). Global no baja.
QUÉ ENTRA (1 clave)
invalid-dropzone-border (var(--color-risk-border)). Era el único knob de
apariencia que iba a un primitivo global a pelo.
Lleva la PARTE en el nombre y no el `invalid-border` desnudo que el catálogo
repite seis veces (checkbox, listbox, grid-list, radio-cards…): aquí hay TRES
partes con borde —dropzone, botón e ítem— y el nombre desnudo no diría cuál
tiñe. La forma modificador + parte + ranura ya está en el catálogo
(invalid-input-border, invalid-control-border).
Su estado no lo alcanza esta ruta: isInvalid sale de un Field envolvente y la
demo no monta ninguno. Forzado: oklch(0.8059 0.1123 59.96) -> rgb(1,2,3).
QUÉ SALE (1 clave, y su declaración)
button-bg NO PINTABA. La regla compartida de los tres botones declaraba
`background: var(--file-upload-button-bg)` y los tres la pisan después: el
trigger con el sólido de la paleta, ItemRemove y ClearTrigger con transparent.
Medido sobre los tres nodos, uno a uno: el centinela no mueve NADA en ninguno.
Con UNA clave la respuesta de F2-A es retirar, no adjudicar — y retirarla da
diff 0, que es la prueba de que estaba muerta. La clave sale con ella: un
público sin consumidor es un huérfano.
CUATRO LITERALES FIRMADOS (fuera del ratio)
Los tres 100% de identidad —el campo ocupa el ancho que le dan, la miniatura
llena la caja en la que se recorta, la barra ocupa la fila del grid— más el
block-size de la miniatura. Cada uno con su anotación en la declaración
(válvula de recipe-contract §3).
CUARENTA CLAVES DE TONO NO PINTAN
8 tonos x 5 ranuras (solid, solid-hover, track, border, text), 40 de sus 98.
Es la cascada de paleta: el forward emite AL FINAL un bloque genérico
[data-file-upload][data-color] que resuelve desde el --palette-* global, misma
especificidad (0,2,0), gana el último. Medido para los ocho tonos sobre las
dos superficies que las consumen —el trigger (solid) y el dropzone en hover
(track)—: ninguna se mueve, mientras --palette-solid sobre el nodo repinta.
Adjudicadas POR PATRÓN; incidencia de fondo en next-features §13.
FUERA POR DOCTRINA (los 9 privados)
Ocho son el puente de paleta THM-2 y el noveno es EL CANAL DE VALOR
(--_file-upload-progress-value, que el envoltorio escribe inline por fila).
Lo que el componente escribe inline no es tema, y un público sobre el puente
dejaría que un tema fijara el tono y matara el color= de cada instancia. Su
techo honesto NO es el 100 %.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (4.608 valores · 8 estados · 19 nodos: la
demo arranca con dos ficheros sembrados, así que monta la superficie entera) +
barrido de los estados que la ruta no arranca (3 variantes x 3 tallas, invalid,
disabled, dragging) 0 diffs sobre 6.840 valores · capturas 2x idénticas byte a
byte · R-5.4 54/98, exit 0, sin STALE · censo --only 83 % · component:audit
PASS · eidos-lint 29 morfo-backed / 0 invalid · vitest src/uix/eidos 35/36 (el
rojo conocido es skin-media-player) · rtl:check 0 · docs:check 0 · prettier
limpio · npm run check sin errores en ficheros del componente · pestaña Tokens
verificada en el navegador (98 filas, 0 errores de consola).
⚠ Truco de medición nuevo: el «antes» del barrido de estados se reconstruye
INYECTANDO el CSS de HEAD en la página (gana por orden a igual especificidad),
sin tocar un árbol que otras tres sesiones están usando. Con muta-prueba: 19
diffs al alterar el control, 1 si sólo se altera el borde inválido.
Índice construido a mano desde HEAD sobre los tres ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts). Fuera del
commit, como en el de `form`, docs/audit/theming/README.md — es un agregado
global que mezcla trabajo en vuelo de otras sesiones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'file-upload' : {
// Measured one by one 2026-08-23 (`__fu-adj`). The demo mounts the WHOLE
// surface (two seeded files, so list, preview, name, size, progress, remove
// and clear all render): what is left is state the route cannot reach and
// the freeze the guard does on purpose.
'transition-duration' :
'the guard freezes transitions and this token IS the transition; measured unfrozen on the dropzone -> reaches (0.12s -> 11.5s on its three properties)' ,
'transition-ease' :
'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))' ,
'disabled-opacity' :
'only under [data-disabled]; forced on the provider -> reaches (0.4 -> 0.123)' ,
'invalid-dropzone-border' :
'only under [data-invalid], and this component takes invalid from an enclosing Field, which the demo route does not mount; forced -> reaches (oklch(0.8059 0.1123 59.96) -> rgb(1,2,3))'
} ,
uix(switch): temable — 76 % → 81 %, y su tono NEUTRAL cae con los otros once
Una clave nueva (51 → 52) y veinticinco adjudicaciones medidas: trece propias y
doce por patrón. El default no se mueve: diff de computed 0 sobre 1.344 valores
en 36 estados y capturas 2x idénticas.
Censo: 76 % → 81 % (público 16 → 17, global 1 → 0; 22 knobs, 4 privados y 1
sistema fuera). Global 67 % (no baja).
QUÉ ENTRA (1 clave)
invalid-track-border (var(--color-threat-element)). Era el ÚNICO knob de
apariencia que iba a un primitivo global a pelo.
El nombre NO es el `invalid-border` que el catálogo repite seis veces
(checkbox, listbox, grid-list, radio-cards, date-range-field…): esta receta
tiene DOS partes con borde y las nombra — su hermana de estado es
`disabled-track-border` y el pulgar lleva `thumb-border`, así que un nombre
desnudo no diría cuál de las dos tiñe. La forma modificador + parte + ranura ya
está en el catálogo (`invalid-input-border`, `invalid-control-border`).
DOCE CLAVES DE TONO NO PINTAN — Y AQUÍ CAE TAMBIÉN «NEUTRAL»
Es la cascada de paleta ya medida en button / badge / callout: el forward emite
al FINAL un bloque genérico [data-switch][data-color] que resuelve desde el
--palette-* global, misma especificidad (0,2,0), gana el último.
Lo propio de este componente es que el fallback no salva ni al tono por
defecto: soma estampa data-color SIEMPRE (es el color RESUELTO, neutral por
defecto), así que el bloque genérico casa en toda instancia. Medido en checked:
--switch-neutral-solid no mueve nada, --palette-solid sobre el nodo repinta
(oklch(0.5556 …) -> rgb(4,5,6)). En button el neutral sobrevive; aquí no.
Adjudicadas POR PATRÓN, con esa diferencia escrita.
LAS OTRAS TRECE SON ESTADO O INSTRUMENTO
Cinco disabled-* y el invalid-track-border nuevo sólo existen bajo su atributo,
que ningún trigger alcanza (forzados: alcanzan, con su par medido).
Los cinco size-*-thumb-translate son DOS huecos a la vez: pintan sobre
`transform`, que el guard no fotografía, y valen 0px hasta que el interruptor
está encendido (forzado checked: 12/14/16/18/20px -> 1234px).
El par de transición ES la transición que el guard congela (sin congelar:
0,12s -> 11,5s en las cuatro propiedades).
FUERA POR DOCTRINA (los 4 privados que quedan)
--_switch-track-bg y --_switch-track-border-hover son CONMUTADORES: dos fuentes
cada uno (la clave pública del estado apagado, el forward de paleta del
encendido), y aplanarlos obligaría a duplicar cada regla por color.
Las dos lecturas directas de --_switch-palette-solid* (el fondo del track en
hover, la tinta del check dentro del pulgar) son el puente THM-2: un público
encima dejaría que un tema fijara el tono y matara el color= de cada instancia.
El techo honesto de este componente NO es el 100 %.
DEFECTO ANOTADO, NO ARREGLADO
El tinte inválido es --color-threat-element, el paso `element` de la escala:
oklch(0.9555 0.0207 13.86), casi blanco sobre superficie clara. Y en cuanto
entra el puntero, la regla de hover (0,4,0) lo tapa con el borde neutro fuerte.
Es el MISMO valor en checkbox, radio-group y toggle para el mismo estado, así
que es una decisión de escala del sistema (element contra border), no de esta
receta: cambiarlo mueve píxel en cuatro componentes y D-TH.5 lo deja fuera.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (416 valores · 7 estados) + sonda de
estados propia 0 diffs (928 valores · 29 estados: checked, invalid, readonly,
disabled, RTL y las cinco tallas, cada uno con hover y foco) · capturas 2x
antes/después idénticas · R-5.4 27/52, exit 0, sin STALE · censo --only 81 % ·
component:audit PASS · eidos-lint 13 morfo-backed / 0 invalid · vitest
src/uix/eidos 35/36 (el rojo conocido es skin-media-player) · rtl:check 0 ·
docs:check 0 · npm run check sin errores en ficheros del componente · pestaña
Tokens verificada en el navegador (52 filas, 0 errores de consola).
Índice construido a mano desde HEAD sobre los cuatro ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts): hay tres
sesiones más en vuelo sobre ellos. Fuera del commit, como en el de `form`,
docs/audit/theming/README.md — es un agregado global que mezcla ese trabajo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
switch : {
// Measured one by one 2026-08-23 (`__sw-adj`). The demo stage boots
// UNCHECKED, VALID and ENABLED and the switch has no trigger to open, so
// every state rule of the recipe is off-stage for the guard; the travel adds
// an instrument gap of its own.
'size-xs-thumb-translate' :
'the travel lands on `transform`, which the guard does not snapshot, and it is 0px until the thumb is checked; forced data-state=checked + data-size=xs -> reaches (translateX 12px -> 1234px)' ,
'size-sm-thumb-translate' : 'same pair of reasons; forced -> reaches (14px -> 1234px)' ,
'size-md-thumb-translate' : 'same pair of reasons; forced -> reaches (16px -> 1234px)' ,
'size-lg-thumb-translate' : 'same pair of reasons; forced -> reaches (18px -> 1234px)' ,
'size-xl-thumb-translate' : 'same pair of reasons; forced -> reaches (20px -> 1234px)' ,
'transition-duration' :
'the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s on all four properties)' ,
'transition-ease' :
'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))' ,
'disabled-track-bg' :
'only under [data-disabled]; forced -> reaches (color(srgb 0.9332 0.9334 0.9334) -> rgb(1,2,3))' ,
'disabled-track-border' : 'same state; forced -> reaches (color(srgb 0.8685 …) -> rgb(1,2,3))' ,
'disabled-thumb-bg' :
'same state, on the thumb; forced -> reaches (color(srgb 0.9867 …) -> rgb(1,2,3))' ,
'disabled-thumb-border' :
'same state, on the thumb; forced -> reaches (color(srgb 0.9097 …) -> rgb(1,2,3))' ,
'disabled-opacity' : 'same state; forced -> reaches (0.4 -> 0.123)' ,
'invalid-track-border' :
'only under [data-invalid], which no trigger reaches and the guard does not force; forced -> reaches (oklch(0.9555 0.0207 13.86) -> rgb(1,2,3))'
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía 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 (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
} ;
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
uix(callout): temable — 27 % → 79 %, y la base óptica deja de copiar el sistema
7 claves nuevas (28 → 35) y un literal anotado. El 21 % que falta es el PUENTE
DE PALETA (THM-2), que no es deuda: es la vía por la que `data-color` resuelve
los 8 roles y las 33 escalas donantes sin una línea de CSS extra. Global 64 %.
Cosido lo que la receta consumía a pelo: el hueco de fila, la tipografía del
cuerpo (`font-size` + `line-height` — así se lee una admonición), la tinta, el
tamaño del icono y el peso del título. Los tres pasos leen el BUNDLE
(`--size-md-font-size`, `--size-md-font-line-height`, `--size-md-icon-size`).
LA LÍNEA BASE ÓPTICA DEL ICONO DEJA DE REPETIR LAS MÉTRICAS DEL SISTEMA: era
`calc(var(--size-md-font-size) * var(--font-line-height-md, 1.5))` y ahora es
`calc(var(--callout-font-size) * var(--callout-line-height))` — la misma
derivación, sobre los tokens del propio callout, así que un tema que agrande la
copia mueve la caja del glifo con ella.
Cinco respaldos muertos retirados: `var(--callout-gap, var(--space-3))`,
`padding`, `accent-width`, `radius` —las cuatro claves están en el contrato— y
el `, 1.5` de `--font-line-height-md`, que además MENTÍA: el token existe y vale
1.45.
Los 24 rojos del guard son los TONOS, y no son de este componente: es la
supersesión de la cascada de paleta que `button` destapó hoy (el bloque genérico
va el último y gana por orden). Adjudicados con el patrón compartido, que ahora
nombra su razón una sola vez para los dos componentes.
Gates: sonda 0 diffs en 896 valores × 7 estados · centinela 10/34 con los tonos
adjudicados · censo 79 % · `component:audit` PASS · suite eidos con el rojo
conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios ·
captura de cuatro tonos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* The one architectural fact behind every tone key that reads dead . Measured
* 2026 - 08 - 23 on button , badge and callout : the palette forward emits a block per
* tone AND , LAST , a generic ` [data-{c}][data-color] ` that resolves the palette
* slots from the GLOBAL ` --palette-* ` ( which ` [data-color={tone}] ` fills from
* ` --color-{tone}-* ` ) . Equal specificity ( 0 , 2 , 0 ) , so the last one wins : with any
* tone attribute present the per - tone keys are bypassed . On data - color = risk ,
* ` --{c}-risk-solid ` moves nothing at :root NOR on the node , while
* ` --palette-solid ` and ` --color-risk-solid ` on the node repaint . The primary - *
* keys are that rule ' s FALLBACK and do act on an instance with no data - color .
* 49 recipes emit the generic block and the contract holds 419 tone keys —
* registered in next - features § 13 , where the fix ( emission order ) is proposed .
* /
const PALETTE_SUPERSEDED =
'SUPERSEDED, not missing: the generic [data-{c}][data-color] block of the palette forward is emitted LAST and wins by source order at equal specificity, so the tone keys are bypassed and the paint comes from the global --palette-* (see next-features 13, measured on button, badge and callout 2026-08-23)' ;
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/ * *
* The same adjudication , written ONCE for a whole family of keys . Born with
* button : 56 of its 106 keys are the eight tones x seven slots , and the reason
* they move nothing is ONE architectural fact , not 56 accidents . Copying the
* sentence 56 times would bury it .
*
* Checked only AFTER the exact lookup above fails , so a single key can always
* carry its own , more precise reason .
* /
export const SENTINEL_PATTERN_EXCEPTIONS : Array < {
component : string ;
pattern : RegExp ;
reason : string ;
} > = [
{
component : 'button' ,
pattern :
/^(secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|element|border|solid|solid-hover|text|contrast)$/ ,
uix(callout): temable — 27 % → 79 %, y la base óptica deja de copiar el sistema
7 claves nuevas (28 → 35) y un literal anotado. El 21 % que falta es el PUENTE
DE PALETA (THM-2), que no es deuda: es la vía por la que `data-color` resuelve
los 8 roles y las 33 escalas donantes sin una línea de CSS extra. Global 64 %.
Cosido lo que la receta consumía a pelo: el hueco de fila, la tipografía del
cuerpo (`font-size` + `line-height` — así se lee una admonición), la tinta, el
tamaño del icono y el peso del título. Los tres pasos leen el BUNDLE
(`--size-md-font-size`, `--size-md-font-line-height`, `--size-md-icon-size`).
LA LÍNEA BASE ÓPTICA DEL ICONO DEJA DE REPETIR LAS MÉTRICAS DEL SISTEMA: era
`calc(var(--size-md-font-size) * var(--font-line-height-md, 1.5))` y ahora es
`calc(var(--callout-font-size) * var(--callout-line-height))` — la misma
derivación, sobre los tokens del propio callout, así que un tema que agrande la
copia mueve la caja del glifo con ella.
Cinco respaldos muertos retirados: `var(--callout-gap, var(--space-3))`,
`padding`, `accent-width`, `radius` —las cuatro claves están en el contrato— y
el `, 1.5` de `--font-line-height-md`, que además MENTÍA: el token existe y vale
1.45.
Los 24 rojos del guard son los TONOS, y no son de este componente: es la
supersesión de la cascada de paleta que `button` destapó hoy (el bloque genérico
va el último y gana por orden). Adjudicados con el patrón compartido, que ahora
nombra su razón una sola vez para los dos componentes.
Gates: sonda 0 diffs en 896 valores × 7 estados · centinela 10/34 con los tonos
adjudicados · censo 79 % · `component:audit` PASS · suite eidos con el rojo
conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios ·
captura de cuatro tonos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
reason : PALETTE_SUPERSEDED + ' — the seven primary-* keys carry their own entry above'
} ,
{
component : 'callout' ,
pattern : /^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|text|solid)$/ ,
reason : PALETTE_SUPERSEDED
uix(checkbox): temable — 59 % → 72 %, y un HUÉRFANO que el test no puede ver
3 claves nuevas, 3 literales anotados y un token del contrato CABLEADO. El 28 %
que queda es el puente de paleta (`--_checkbox-palette-*`, THM-2): ocho filas
que no son deuda.
EL HALLAZGO: `indicator-fg` ERA UN HUÉRFANO QUE EL TEST DE HUÉRFANOS NO PUEDE
VER. La clave llevaba en el contrato desde siempre, pero la receta pintaba la
tinta de la casilla marcada leyendo `--color-content-on-solid` DIRECTAMENTE, sin
nombrarla nunca — y el test busca el NOMBRE en los ficheros del componente, así
que un token que nadie menciona pasa por vivo. Cableado
(`--_checkbox-palette-text: var(--checkbox-indicator-fg)`), computado idéntico, y
el propio guard lo confirmó marcando su adjudicación como STALE: ahora mueve. Es
la clase inversa del `--popover-bg` que vivía de un COMENTARIO.
Acuñado el estado INVALID, que gana a todas las variantes y leía el primitivo:
el borde y el DOBLE ANILLO DE FOCO (hueco + anillo), que era una sombra con dos
colores cableados. La geometría (2px/4px) se queda dentro de la declaración: es
la forma del anillo, no una perilla.
Tres literales pasan a excepción por identidad: el glifo llena su caja (`100 %`
× 2) y la etiqueta de grupo es su texto (`fit-content`).
Los 26 rojos del guard, medidos — y dos son trampas de ALCANCE del instrumento:
- El glifo SÓLO existe dentro de una casilla MARCADA, y la primera de la demo
está vacía: medir el trazo por talla sobre «el primer indicador del documento»
mide otro nodo. Con el path dentro de la misma casilla, los cinco pasos
alcanzan.
- La etiqueta de grupo lleva su PROPIO `[data-disabled]`: no lo hereda del
grupo, así que forzar el grupo no pinta nada.
El resto es lo esperable (invalid, disabled, los cuatro de transición que el
guard congela) y los ocho de TONO, que son la supersesión de la cascada de
paleta medida hoy en `button`: adjudicados con el patrón compartido.
Gates: sonda 0 diffs en 2.688 valores × 7 estados (13 nodos) · centinela 22/47
con todo lo demás adjudicado · censo 72 % · `component:audit` PASS · suite eidos
con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · captura de marcada,
invalid, disabled, xl y tono affirm.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
} ,
{
component : 'checkbox' ,
pattern : /^(primary|secondary|neutral|affirm)-(solid|solid-hover)$/ ,
reason : PALETTE_SUPERSEDED
} ,
uix(badge): temable — 59 % → 75 %, y subir un privado al contrato lo somete a reglas nuevas
Seis claves nuevas (65 → 71), dos literales firmados, cinco privados
adjudicados. Diff de computed 0 sobre 992 valores en 7 estados.
LA ALTURA DEL CHIP ERA EL HUECO
Badge monta su altura en la escala de CONTROL a propósito —para que `md`
signifique lo mismo lleve o no un control dentro (el ✕ removible es un
IconButton: medido, el mismo badge md daba 30px sin él y 46px con él)— pero lo
hacía desde un privado con cuatro fuentes por talla que leía --control-height-*
A PELO. Eso es una ESCALA, no un conmutador: sube al TSC como
min-block-size-{xs,sm,md,lg} + el resuelto, el molde que sus vecinas
padding-inline / gap / font-size ya tenían.
Y AL ENTRAR EN EL CONTRATO, LA SUITE LO CORRIGIÓ
El valor verbatim del CSS era el primitivo CRUDO (--control-height-{k}) y
recipe-css-contract exige la coordenada del BUNDLE (--size-{k}-control-height,
theming §5). Es la trampa que el handoff nombra —«la propuesta hereda el
incumplimiento del CSS de partida»— y que ya mordió a code-block: SUBIR UN
PRIVADO AL CONTRATO SOMETE SU VALOR A REGLAS QUE EN EL CSS NO SE LE APLICABAN.
Corregido; el diff sigue en 0.
dot-radius ES EL PAR QUE FALTABA
Redondo es el DEFECTO, no la definición: knob.indicator-radius vale
--radius-sm, así que el catálogo ya trata el radio de un marcador como knob.
Los dos 100% del glifo son identidad (el icono ES su ranura), firmados.
LOS CINCO PRIVADOS SON LAS TRES CLASES DE F2-B EN UN SOLO COMPONENTE
--_badge-bg / --_badge-fg son un CONMUTADOR (cambian de fuente con la variante:
track · solid · transparent, y aplanarlos obligaría a duplicar cada regla);
--_badge-fill-finish es el canal que el generador de degradados deriva de la
paleta de ESTA instancia; y --_badge-palette-* son forwards THM-2 que la capa de
color alimenta por instancia — un público encima dejaría que un tema los fijara
y matara el color= de cada chip.
LAS 40 CLAVES DE TONO NO MUEVEN NADA, Y YA SABÍAMOS POR QUÉ
El bloque genérico [data-badge][data-color] del forward de paleta se emite EL
ÚLTIMO y gana por orden a igual especificidad: la pintura sale del --palette-*
global. Es el hallazgo de cascada de paleta medido en button, badge y callout el
mismo día (§13, pendiente de firma) y entra como EXCEPCIÓN POR PATRÓN — una
razón arquitectónica escrita UNA vez, no cuarenta accidentes. Con ella y los dos
pasos de radius adjudicados (forzando data-rounded: 4px → 37px y 10px → 37px),
el guard queda en 29/71 y limpio.
EL INSTRUMENTO NO VEÍA TRES DE SUS CINCO PARTES
Dot, icon y remove son interruptores OPT-IN apagados por defecto: la sonda medía
2 nodos —raíz y etiqueta— y ninguna de las partes que la receta pinta.
Encendidos, 5. Misma clase que las capas opt-in de background.
⚠ component:audit da NEEDS-WORK y es PREEXISTENTE: R-1.5, sin tratamiento de
foco en la receta (grep focus-visible = 0 tanto en HEAD como después). El ✕ trae
el suyo del IconButton que compone; el chip no es focalizable. No se toca aquí:
darle foco mueve píxel y es decisión de diseño.
VERIFICACIÓN
Sonda antes/después 0 diffs (992 valores · 7 estados · 5 nodos con las tres
partes encendidas) · R-5.4 29/71 con el resto adjudicado (dos entradas propias +
una excepción por patrón), cero STALE · censo 75 % · eidos-lint 5 morfo-backed /
14 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite
eidos 439/440 (el rojo es skin-media-player, el conocido).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{
component : 'badge' ,
pattern :
/^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|border|solid|text|contrast)$/ ,
reason : PALETTE_SUPERSEDED
} ,
uix(form): temable — 71 % → 86 %, y 24 claves suyas NO PINTAN por una cascada
Diez claves nuevas (54 → 64), un literal firmado, veinte adjudicaciones medidas
y un hallazgo que espera firma. Diff de computed 0 sobre 832 valores en 7
estados; capturas 2× antes/después.
LAS DIEZ IBAN A LOS PRIMITIVOS A PELO
El resumen de errores (error-summary-{bg,fg,border}) —es el señalizador de nivel
de página y su superficie, su tinta y su borde SON el tono risk—, el acento de
formulario inválido (invalid-accent, un privado con UNA sola fuente, no un
conmutador), las tres superficies del árbol de AutoFields, las dos del widget y
el subrayado a media tinta de los enlaces del resumen (link-underline-fg, el par
del -offset que ya existía).
⚠ R-5.3 CAZÓ MI NOMBRE EN EL ACTO: lo acuñé como link-underline-color y la tinta
es `fg` (theming §6.7 r7, firmado el 20). Renombrado antes de commitear. El
guard de nombres funciona — es el vocabulario DIMENSIONAL el que no cubre (ver
badge, hoy mismo).
⚠⚠ VEINTICUATRO CLAVES DEL CONTRATO NO PINTAN, Y ES UNA CASCADA
[data-form-submit], [data-form-reset] y los dos botones del array llevan también
data-button porque COMPONEN el Button canónico, y [data-button] casa con la
MISMA especificidad (0,1,0) pero carga DESPUÉS: gana button.css. Medido
propiedad a propiedad sobre el nodo real —altura, padding-inline, tamaño y peso
de letra, interlineado, hueco, radio, grosor de borde, el par de transición y la
opacidad deshabilitada—: NINGUNA se mueve.
Es el defecto que gradient-picker documentó (36 de sus 45 claves) y que
emoji-picker encontró hoy en su trigger. Allí la respuesta fue RETIRAR las
declaraciones muertas; aquí son 24 claves públicas, y eso es una decisión del
autor, no del que ejecuta. Quedan adjudicadas POR PATRÓN (una razón
arquitectónica escrita una vez) y anotadas en §13 con la pregunta de fondo, que
es la misma de §12.9 con el plano overlay: qué manda, el componente compuesto o
la receta que lo compone. Si mandan las recetas, se sube la especificidad y no
se retira nada; si mandan los compuestos, hay ~63 claves públicas que retirar en
tres componentes.
LAS DEMÁS ADJUDICACIONES SON RAMAS QUE LA DEMO NO MONTA
El layout grid (forzado alcanza — y ojo: su grid-template-columns sólo CAMBIA si
el valor altera el número de columnas; con 90px pasa de una columna de 502px a
cuatro de 110,5), el resumen de errores VACÍO (sin <ul> que indentar ni <a> que
subrayar) y toda la rama de ARRAY de AutoFields: el modo `auto` de la demo monta
group y field, nunca array, item ni widget.
Fuera por doctrina: los tres privados del panel son un CONMUTADOR (variante
plain contra panel) y los cuatro --_form-action-* leen el forward de paleta
THM-2.
VERIFICACIÓN
Sonda antes/después 0 diffs (832 valores · 7 estados) · R-5.4 22/64 con las 20 +
el patrón adjudicados, exit 0 · component:audit PASS (tras el renombrado) ·
--names 0 desviadas · censo --only 86 % · eidos-lint 36 morfo-backed / 0 invalid
/ 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo
conocido) · el ledger sigue en sus 63 hunks de prettier previos.
Rama con cuatro sesiones en paralelo: índice construido desde HEAD + sólo mis
hunks (base.ts por bloque), verificado con `git diff --cached`. No entra
docs/audit/theming/README.md — es un agregado global que ahora mismo mezcla
trabajo en vuelo de otras sesiones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{
component : 'form' ,
pattern : /^(action-|transition-|disabled-opacity$)/ ,
reason :
'SUPERSEDED by the composed Button: [data-form-submit] & co. also carry `data-button`, and `[data-button]` matches at the SAME specificity (0,1,0) but loads LATER, so button.css paints the whole action chrome. Measured 2026-08-23, eight properties one by one — height, padding-inline, font-size, font-weight, line-height, gap, radius, border-width — plus the transition pair and the disabled opacity: NOT ONE of them moves. Same defect gradient-picker documented (36 of its 45 keys) and emoji-picker found in its trigger; the answer there was to retire the dead declarations, and here that is 24 public keys — a decision for the author, so they are adjudicated meanwhile (next-features 13)'
} ,
uix(editable): temable — 75 % → 82 %, y el guard CERRABA la superficie que medía
Protocolo PLAN-theming §7 sobre `editable`. Censo 75 % → **82 %**, contrato 56
claves, centinela **42/56** con 14 adjudicadas. El default NO se mueve.
## Lo que costó medirlo: dos superficies y una apertura frágil al revés
- **Preview e input comparten celda de rejilla y se excluyen** (`display: none`),
igual que los triggers (Edit en reposo, Save/Cancel editando): una pasada deja
fuera la mitad de la receta. Todo se midió DOS veces.
- **Salir del modo edición es lo que hace un `blur`** —da igual lo que diga
`submitMode`, esa opción sólo decide si el valor se compromete— y el guard
blurea justo después de abrir. Abriendo con clic en el trigger, la superficie
ya estaba cerrada cuando se medía: `submit-fg` leía muerto y mueve
perfectamente abierta (`rgb(255,255,255)` → `rgb(1,2,3)`). Se abre por FOCO,
con la demo en `activationMode=focus`, que es la apertura que el guard no
deshace. Misma ley que `navigation-menu`, con el gesto invertido.
## Lo que entra al contrato (1 clave)
`disabled-fg` — la tinta apagada de TODO el componente: preview, input y los
tres triggers comparten UNA regla, así que el nombre desnudo del catálogo es el
que toca (`label` y `select` ya lo llevan con este mismo valor). Era el único
knob que iba a un primitivo a pelo, y encima con un respaldo escrito a mano
—`var(--color-content-disabled, var(--color-content-muted))`— que estaba MUERTO:
el token sí está declarado, así que el respaldo no podía pintar nunca. Retirado
con él.
## Lo que sale (1 clave huérfana)
`control-border`: ninguna regla lo consumía. El borde en reposo del control es
`transparent` por diseño y el del input es el acento
(`--_editable-palette-border`), así que esa clave pública prometía un knob que no
existe — la deuda INVERSA del eje. Diff 0, y el centinela lo confirmaba muerto.
## Dos literales de identidad firmados
El `fit-content` del preview (es tan ancho como el texto que enseña; la ficha
proponía acuñarlo como `preview-width`, que sería acuñar la definición de la
parte) y el `100 %` del input (llena la celda que comparte con el preview).
## Lo que se queda privado (el techo honesto es 82 %)
Los SIETE knobs por privado son el puente de paleta THM-2
(`--_editable-palette-{solid,border,solid-hover}`), que la capa compartida
alimenta por instancia desde `[data-color]`: un público encima dejaría que un
tema lo fijara y matara el `color=` de cada instancia. Otros cuatro son sistema
transversal (anillo de foco y velo de hover), ya fuera del ratio.
## Las catorce adjudicaciones, en dos razones — y una tercera que casi se cuela
- El par `transition-*` **ES** la transición que el guard congela; medido sin
congelar: `0.12s → 4.321s` y `cubic-bezier(0.4, 0, 0.2, 1) → steps(3)`.
- Las doce claves de tono son la cascada de paleta, por PATRÓN y con el mecanismo
a la vista: el nodo lleva `--palette-solid` de la capa por instancia, el bloque
genérico `[data-editable][data-color]` —emitido EL ÚLTIMO, misma
especificidad— lo lee, y el respaldo que nombra la clave del componente no
llega nunca. Quítale `data-color` a la misma instancia y
`--editable-primary-solid` vuelve a pintar (`rgb(1, 2, 3)`).
- ⚠ **`risk-border` NO cae en ese patrón**: las tres reglas de inválido la leen
DIRECTAMENTE, así que su silencio tiene otra causa —la demo es válida— y
entrada propia: forzando `data-invalid` alcanza en el input Y en el control.
Una excepción por patrón que engulle una clave con vida propia es una
adjudicación falsa.
## Artefactos
- Sonda: **0 diffs** en las dos caras — modo preview (1.696 valores, 7 estados) y
modo edición (2.528 valores, 8 estados).
- Captura 2× del reposo idéntica al byte. La de edición NO es comparable por
bytes: la traza de la demo imprime un TIMESTAMP, así que dos corridas del mismo
código difieren (comprobado antes de creerme el diff). Revisada a ojo.
- `component-audit --only editable`: **PASS** · `eidos-lint` 0 invalid ·
`vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED, 0 errores en
`editable`.
- README «Talla y tema» + pestaña `Tokens` (56 filas, verificada en el navegador
con `networkidle` y clic real).
## Defecto anotado, NO arreglado
El tinte inválido ES la clave de tono (`--editable-risk-border`): dos knobs con
una sola llave — un tema que quiera otro color de error mueve también las
instancias `color="risk"`. El arreglo es una clave de estado por parte
(`invalid-input-border` / `invalid-control-border`, la forma que `switch` acuñó
el mismo día) y es contrato: se lista.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{
component : 'editable' ,
pattern : /^(primary|secondary|neutral|affirm|risk|threat)-(solid|border)$/ ,
reason :
PALETTE_SUPERSEDED +
' — measured here 2026-08-23 with the mechanism in the open: the node carries --palette-solid (oklch(0.5556 …)) from the shared per-instance layer, the generic block reads it, and the fallback that names the component key never applies; strip data-color from the same instance and --editable-primary-solid paints again (rgb(1, 2, 3)). soma always stamps data-color, so no instance escapes'
} ,
uix(surface): ADJUDICADO — el 25 % es su techo, y su ruta de demo es un 404
Cero claves nuevas: sus cuatro knobs son, uno a uno, doctrina bien aplicada.
Surface es la cara de PINTURA de Box, y lo que pinta no es suyo: `bg` y `fg`
salen del PUENTE DE PALETA (THM-2) —que es exactamente cómo un tema llega a
ellos, por el color— y `--_surface-fill-finish` es el CANAL DE VALOR del acabado
de degradado, que el generador deriva por instancia (§39). La rama `inherit` del
`fg` suave es intencionada: la tinta del contenido se queda en la global porque
el tinte del track es discreto, y está escrito en la receta.
PERO NO TIENE RUTA PROPIA: `/uix/components/surface` es un 404, como
`picker-shell` y `mockup`. El guard medía una página VACÍA y reportaba 0/25 —
incluido `radius`, que está vivísimo—. Apuntado a `/temas/gradientes` (cuatro
surfaces, dos variantes, tres colores) y a `/blocks/cta`: 1/25, y el que mueve es
el radio (10px → 1234px). Ya son TRES los componentes del canon sin demo → §13.
Sus 24 claves de tono son la SUPERSESIÓN de la cascada de paleta medida hoy en
`button`: el bloque genérico va el último y gana por orden. Adjudicadas con el
patrón compartido.
Gates: centinela 1/25 con las 24 adjudicadas · censo 25 %, que es el techo ·
`docs:check` 0 · sin cambios de código.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{
component : 'surface' ,
pattern : /^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|solid|contrast)$/ ,
reason : PALETTE_SUPERSEDED
} ,
uix(file-upload): temable — 76 % → 83 %, y una clave suya NO PINTABA en ninguno de sus tres botones
Una clave acuñada, UNA RETIRADA con su declaración muerta, cuatro literales
firmados y cuarenta y cuatro adjudicaciones medidas. El default no se mueve:
diff de computed 0 sobre 11.448 valores en 17 estados y capturas 2x idénticas
byte a byte.
Censo: 76 % → 83 % (público 45, global 1 → 0, literal 4 → 0, excepción 0 → 4;
knobs 61 → 56). Contrato 98 claves (una entra, otra sale). Global no baja.
QUÉ ENTRA (1 clave)
invalid-dropzone-border (var(--color-risk-border)). Era el único knob de
apariencia que iba a un primitivo global a pelo.
Lleva la PARTE en el nombre y no el `invalid-border` desnudo que el catálogo
repite seis veces (checkbox, listbox, grid-list, radio-cards…): aquí hay TRES
partes con borde —dropzone, botón e ítem— y el nombre desnudo no diría cuál
tiñe. La forma modificador + parte + ranura ya está en el catálogo
(invalid-input-border, invalid-control-border).
Su estado no lo alcanza esta ruta: isInvalid sale de un Field envolvente y la
demo no monta ninguno. Forzado: oklch(0.8059 0.1123 59.96) -> rgb(1,2,3).
QUÉ SALE (1 clave, y su declaración)
button-bg NO PINTABA. La regla compartida de los tres botones declaraba
`background: var(--file-upload-button-bg)` y los tres la pisan después: el
trigger con el sólido de la paleta, ItemRemove y ClearTrigger con transparent.
Medido sobre los tres nodos, uno a uno: el centinela no mueve NADA en ninguno.
Con UNA clave la respuesta de F2-A es retirar, no adjudicar — y retirarla da
diff 0, que es la prueba de que estaba muerta. La clave sale con ella: un
público sin consumidor es un huérfano.
CUATRO LITERALES FIRMADOS (fuera del ratio)
Los tres 100% de identidad —el campo ocupa el ancho que le dan, la miniatura
llena la caja en la que se recorta, la barra ocupa la fila del grid— más el
block-size de la miniatura. Cada uno con su anotación en la declaración
(válvula de recipe-contract §3).
CUARENTA CLAVES DE TONO NO PINTAN
8 tonos x 5 ranuras (solid, solid-hover, track, border, text), 40 de sus 98.
Es la cascada de paleta: el forward emite AL FINAL un bloque genérico
[data-file-upload][data-color] que resuelve desde el --palette-* global, misma
especificidad (0,2,0), gana el último. Medido para los ocho tonos sobre las
dos superficies que las consumen —el trigger (solid) y el dropzone en hover
(track)—: ninguna se mueve, mientras --palette-solid sobre el nodo repinta.
Adjudicadas POR PATRÓN; incidencia de fondo en next-features §13.
FUERA POR DOCTRINA (los 9 privados)
Ocho son el puente de paleta THM-2 y el noveno es EL CANAL DE VALOR
(--_file-upload-progress-value, que el envoltorio escribe inline por fila).
Lo que el componente escribe inline no es tema, y un público sobre el puente
dejaría que un tema fijara el tono y matara el color= de cada instancia. Su
techo honesto NO es el 100 %.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (4.608 valores · 8 estados · 19 nodos: la
demo arranca con dos ficheros sembrados, así que monta la superficie entera) +
barrido de los estados que la ruta no arranca (3 variantes x 3 tallas, invalid,
disabled, dragging) 0 diffs sobre 6.840 valores · capturas 2x idénticas byte a
byte · R-5.4 54/98, exit 0, sin STALE · censo --only 83 % · component:audit
PASS · eidos-lint 29 morfo-backed / 0 invalid · vitest src/uix/eidos 35/36 (el
rojo conocido es skin-media-player) · rtl:check 0 · docs:check 0 · prettier
limpio · npm run check sin errores en ficheros del componente · pestaña Tokens
verificada en el navegador (98 filas, 0 errores de consola).
⚠ Truco de medición nuevo: el «antes» del barrido de estados se reconstruye
INYECTANDO el CSS de HEAD en la página (gana por orden a igual especificidad),
sin tocar un árbol que otras tres sesiones están usando. Con muta-prueba: 19
diffs al alterar el control, 1 si sólo se altera el borde inválido.
Índice construido a mano desde HEAD sobre los tres ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts). Fuera del
commit, como en el de `form`, docs/audit/theming/README.md — es un agregado
global que mezcla trabajo en vuelo de otras sesiones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{
component : 'file-upload' ,
pattern :
/^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(solid|solid-hover|track|border|text)$/ ,
reason :
PALETTE_SUPERSEDED +
' — the eight tones x five slots, 40 of its 97 keys. Measured 2026-08-23 on the trigger (solid) and on the hovered dropzone (track) for the eight tones: not one moves, while --palette-solid on the node repaints (oklch(0.6908 …) -> rgb(4,5,6)). Its host default is `primary`, and the demo passes color explicitly, so the fallback never applies either'
} ,
uix(switch): temable — 76 % → 81 %, y su tono NEUTRAL cae con los otros once
Una clave nueva (51 → 52) y veinticinco adjudicaciones medidas: trece propias y
doce por patrón. El default no se mueve: diff de computed 0 sobre 1.344 valores
en 36 estados y capturas 2x idénticas.
Censo: 76 % → 81 % (público 16 → 17, global 1 → 0; 22 knobs, 4 privados y 1
sistema fuera). Global 67 % (no baja).
QUÉ ENTRA (1 clave)
invalid-track-border (var(--color-threat-element)). Era el ÚNICO knob de
apariencia que iba a un primitivo global a pelo.
El nombre NO es el `invalid-border` que el catálogo repite seis veces
(checkbox, listbox, grid-list, radio-cards, date-range-field…): esta receta
tiene DOS partes con borde y las nombra — su hermana de estado es
`disabled-track-border` y el pulgar lleva `thumb-border`, así que un nombre
desnudo no diría cuál de las dos tiñe. La forma modificador + parte + ranura ya
está en el catálogo (`invalid-input-border`, `invalid-control-border`).
DOCE CLAVES DE TONO NO PINTAN — Y AQUÍ CAE TAMBIÉN «NEUTRAL»
Es la cascada de paleta ya medida en button / badge / callout: el forward emite
al FINAL un bloque genérico [data-switch][data-color] que resuelve desde el
--palette-* global, misma especificidad (0,2,0), gana el último.
Lo propio de este componente es que el fallback no salva ni al tono por
defecto: soma estampa data-color SIEMPRE (es el color RESUELTO, neutral por
defecto), así que el bloque genérico casa en toda instancia. Medido en checked:
--switch-neutral-solid no mueve nada, --palette-solid sobre el nodo repinta
(oklch(0.5556 …) -> rgb(4,5,6)). En button el neutral sobrevive; aquí no.
Adjudicadas POR PATRÓN, con esa diferencia escrita.
LAS OTRAS TRECE SON ESTADO O INSTRUMENTO
Cinco disabled-* y el invalid-track-border nuevo sólo existen bajo su atributo,
que ningún trigger alcanza (forzados: alcanzan, con su par medido).
Los cinco size-*-thumb-translate son DOS huecos a la vez: pintan sobre
`transform`, que el guard no fotografía, y valen 0px hasta que el interruptor
está encendido (forzado checked: 12/14/16/18/20px -> 1234px).
El par de transición ES la transición que el guard congela (sin congelar:
0,12s -> 11,5s en las cuatro propiedades).
FUERA POR DOCTRINA (los 4 privados que quedan)
--_switch-track-bg y --_switch-track-border-hover son CONMUTADORES: dos fuentes
cada uno (la clave pública del estado apagado, el forward de paleta del
encendido), y aplanarlos obligaría a duplicar cada regla por color.
Las dos lecturas directas de --_switch-palette-solid* (el fondo del track en
hover, la tinta del check dentro del pulgar) son el puente THM-2: un público
encima dejaría que un tema fijara el tono y matara el color= de cada instancia.
El techo honesto de este componente NO es el 100 %.
DEFECTO ANOTADO, NO ARREGLADO
El tinte inválido es --color-threat-element, el paso `element` de la escala:
oklch(0.9555 0.0207 13.86), casi blanco sobre superficie clara. Y en cuanto
entra el puntero, la regla de hover (0,4,0) lo tapa con el borde neutro fuerte.
Es el MISMO valor en checkbox, radio-group y toggle para el mismo estado, así
que es una decisión de escala del sistema (element contra border), no de esta
receta: cambiarlo mueve píxel en cuatro componentes y D-TH.5 lo deja fuera.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (416 valores · 7 estados) + sonda de
estados propia 0 diffs (928 valores · 29 estados: checked, invalid, readonly,
disabled, RTL y las cinco tallas, cada uno con hover y foco) · capturas 2x
antes/después idénticas · R-5.4 27/52, exit 0, sin STALE · censo --only 81 % ·
component:audit PASS · eidos-lint 13 morfo-backed / 0 invalid · vitest
src/uix/eidos 35/36 (el rojo conocido es skin-media-player) · rtl:check 0 ·
docs:check 0 · npm run check sin errores en ficheros del componente · pestaña
Tokens verificada en el navegador (52 filas, 0 errores de consola).
Índice construido a mano desde HEAD sobre los cuatro ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts): hay tres
sesiones más en vuelo sobre ellos. Fuera del commit, como en el de `form`,
docs/audit/theming/README.md — es un agregado global que mezcla ese trabajo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{
component : 'switch' ,
pattern : /^(primary|secondary|neutral|affirm|risk|threat)-(solid|solid-hover)$/ ,
reason :
PALETTE_SUPERSEDED +
' — and here NEUTRAL falls with the rest: soma always stamps data-color (it is the resolved colour, neutral by default), so the generic block matches on every instance and the fallback the other recipes keep alive never applies. Measured checked 2026-08-23: --switch-neutral-solid moves nothing, --palette-solid on the node repaints (oklch(0.5556 …) -> rgb(4,5,6))'
}
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
] ;