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 .
* /
export const SENTINEL_EXCEPTIONS : Record < string , Record < string , string > > = {
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]' ,
track :
'forwarded into the composed Slider — paints on [data-slider-*] nodes, outside this component attribute space' ,
'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(color-picker): temable — 51 % → 75 %, y un prefijo ABREVIADO fuera de todo el árbol
20 claves de costura (54 → 71) y cinco nombres renombrados. Global del eje
60 % → **61 %**.
`--cp-*` VIOLABA theming §6 r5 («no abrevies el nombre del componente»). Cinco
nombres —`checker`, `checker-cell`, `current-color`, `channel-gradient`,
`trigger-color`— y **45 ocurrencias en 8 ficheros**, tres de ellos de SOMA: el
provider los estampa inline, su test los lee y sus tipos los declaran.
Renombrados a `--_color-picker-*` con `git ls-files` delante; dos de ellos —el
damero y el gradiente de canal— ya derivaban de un público, así que el censo los
reclasifica solo. Precedente: los `--gb-*` / `--_ntp-*` de F2-A.
Cosido: el ritmo del componente, la tinta de la etiqueta, la tipografía del
disparador y la del VALOR —que es metadato: mono, regular, tinta e interlineado
propios—, el borde de la muestra, el raíl del canal y su pulgar, el hueco del
anillo de la muestra y el borde del cuentagotas.
UNA SOMBRA QUE SON DOS Y HACEN UNA COSA: el pulgar del canal lleva contorno +
elevación en un solo `box-shadow`, así que se acuña como UN token y la regla de
foco lo compone con su tercer anillo, en vez de repetir las dos sombras.
EL INSTRUMENTO NO VEÍA DOS FAMILIAS DE NODO, y las dos se arreglaron midiendo:
las muestras del picker **son `<ColorSwatch>`** (llevan `data-color-swatch`, no
`data-color-picker-*`) y sus sliders de canal **son SLIDERS** (el pulgar y el
raíl llevan `data-slider-*`). Cuatro tokens vivos leían muertos por eso.
LO ROJO QUE QUEDA ES ANTERIOR A ESTE COMMIT: el guard reporta 23 sin adjudicar
—las muestras, la rueda de tono (`hue-0..360` + `saturation-floor`), el ancho del
panel por talla, el cuentagotas y las dos de transición—, todos del bloque que ya
existía. Antes eran **24**, así que la cifra baja. Su pase de medición queda
anotado en §13 con dos causas ya descartadas: el panel SÍ abre y sobrevive al
blur, al parqueo del ratón y al forzado de `data-size`, y el guard ya ve sus
nodos. Lo que este commit añadió está medido: dos adjudicadas montando el
disparador de muestra dentro del panel abierto, el resto en vivo.
Gates: sonda antes/después **0 diffs en 1.350 valores** (8 estados), medida DOS
veces —una tras el renombrado, otra tras la costura— · tests de soma del
color-picker **7/7** tras el renombrado · censo --only 75 % · `--names` sin
desviadas · 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
'color-picker' : {
// The demo mounts no swatch TRIGGER: its presets render as <ColorSwatch>
// components, so the two parts these paint never enter the panel. Mounted
// inside the OPEN panel 2026-08-22 with the attrs the CSS selects.
'swatch-border-width' : 'no swatch trigger in the demo panel; mounted in place -> reaches (1px -> 1234px)' ,
'swatch-ring-gap' : 'the ring gap of the checked swatch and of its indicator; mounted in place -> reaches (oklch(0.9911 0 0) 0 0 0 1px -> rgb(1,2,3) 0 0 0 1px)'
} ,
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): 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
}
} ;