You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/scripts/theming-sentinel-exceptions.ts

592 lines
46 KiB

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]',
'buffered-bg':
'forwarded into the composed Slider (SecondaryRange) — outside this component attribute space',
'captions-bg': 'captions are conditional and the demo video carries no track',
'captions-fg': 'captions are conditional and the demo video carries no track',
'captions-radius': 'captions are conditional and the demo video carries no track',
'captions-padding-block': 'captions are conditional and the demo video carries no track',
'captions-padding-inline': 'captions are conditional and the demo video carries no track',
'captions-line-height': 'captions are conditional and the demo video carries no track',
'volume-vertical-length': 'the volume float is interaction-mounted; not open at measurement',
'volume-float-padding-block':
'the volume float is interaction-mounted; not open at measurement',
'volume-float-padding-inline':
'the volume float is interaction-mounted; not open at measurement',
'rate-button-min-width': 'the rate control is conditional and the demo does not mount it',
'audio-title-padding-block': 'audio variant — the video demo does not mount the audio title',
'audio-title-padding-inline': 'audio variant — the video demo does not mount the audio title'
},
'tree-grid': {
'header-z':
'consumed only under [data-sticky-header], which the demo does not set; forced → reaches (2 → 4321)',
'disabled-row-fg': 'no disabled row in the demo; forced data-disabled → reaches (rgb(1,2,3))'
},
'gradient-picker': {
'chip-radius':
'the swatch is a CLASS hook (.gradient-picker-trigger-swatch) outside the attribute-filtered node set; direct read → reaches (§13 class-hook debt)',
'chip-border':
'the swatch is a CLASS hook outside the attribute-filtered node set; direct read → reaches (§13 class-hook debt)',
'content-width-sm':
"sized by data-picker-size (picker-shell's axis, not data-size); forced → reaches (288 → 512)",
'content-width-lg':
"sized by data-picker-size (picker-shell's axis, not data-size); forced → reaches (352 → 512)",
'stoplist-max-block':
"consumer node lives in the composed ScrollArea's attribute space (--scroll-area-auto-max-block on the viewport); direct read → reaches (132 → 1234)",
'stoplist-scrollbar-size':
'the design scrollbar only mounts on overflow; the demo list is short',
'stoplist-scrollbar-inset':
"consumer node lives in the composed ScrollArea's attribute space; direct read → reaches (12 → 1234)",
'presets-panel-width':
"the presets popover needs a second click the guard's open step does not perform; forced → reaches (240 → 1234)",
'presets-gap': 'presets popover not mounted by the open step; forced → reaches (8 → 1234)',
'presets-min-column':
'presets popover not mounted by the open step; forced → reaches (66px columns → 1234)',
'preset-height': 'presets popover not mounted by the open step; forced → reaches (32 → 1234)',
'preset-radius': 'presets popover not mounted by the open step; forced → reaches (6 → 1234)',
'preset-border': 'presets popover not mounted by the open step; forced → reaches (rgb(1,2,3))',
'hover-preset-border':
'presets popover not mounted by the open step; forced + real hover → reaches (rgb(1,2,3))',
'selected-preset-border':
'presets popover not mounted by the open step; forced data-selected → reaches (rgb(1,2,3))',
'selected-preset-outline-offset':
'presets popover not mounted by the open step; forced data-selected → reaches (2 → 1234)'
},
listbox: {
'invalid-border': 'no invalid listbox in the demo; forced data-invalid → reaches (rgb(1,2,3))'
},
carousel: {
'vertical-block-size':
'vertical orientation not mounted in the demo; forced → reaches (256 → 1234)'
},
feed: {
'sentinel-spinner-size':
'the spinner only exists while [data-busy], and the guard WAITS for the busy window to close (the settle fix); measured live during the busy window (::before, 12 → 1234)',
'sentinel-spinner-width':
'the spinner only exists while [data-busy]; measured live during the busy window (::before border)',
'sentinel-spinner-gap':
'the spinner only exists while [data-busy]; measured live during the busy window (::before margin)',
'sentinel-spinner-duration':
'the spinner only exists while [data-busy]; measured live during the busy window (animation-duration 0.7s → 11.5s)',
'article-title-font-size-5':
'the demo mounts heading levels 3–4 only; forced aria-level=5 → reaches (14 → 1234)',
'article-title-font-size-6':
'the demo mounts heading levels 3–4 only; forced aria-level=6 → reaches (12 → 1234)',
'sentinel-spinner-reduced-duration':
'behind prefers-reduced-motion; emulated reduce context → reaches (4s → 11.5s)'
uix(grid-list): temable — 0 % → 85 %, y una selección que nunca pintó 38 claves nuevas en el bloque `grid-list` de `recipes/base.ts` (que ya existía con los dos forwards de paleta: se FUSIONA, no se añade un segundo). Alcance del censo 0 % → 85 % (22 de 26 puntuables); global 45 % → 46 %, sin contrato 49 → 48. El eje `size` se estampa en el PROVIDER —el wrapper de eidos no tiene parte `root`—, así que los tres nombres resueltos van SIN `parts`: molde `command`, no `table`/`tree-grid`. Fuera los cuatro bloques `[data-size]` del CSS; los emite el TSC. Altura de fila 1:1 con el bundle, `row-padding-inline` con ritmo propio, y la tipografía un paso por debajo de md en adelante — verbatim, como table y tree-grid. LO QUE LA MEDICIÓN CAMBIÓ (la §4 generada tenía un error de doctrina y dos defectos que sólo aparecieron midiendo; el §5 Veredicto se escribió con la sonda delante, porque estaba vacío): 1. La selección de fila NUNCA HA PINTADO. La receta seleccionaba `[data-grid-list-row][data-selected]`, un atributo que ni el morfo declara ni soma estampa (estampa `data-state='selected'` + `aria-selected`): la regla no casaba jamás y su acento `--_grid-list-palette-element` no llegó nunca al píxel — una fila seleccionada muestra el velo NEUTRO del arquetipo, indistinguible del hover (capturado). Retirada la declaración muerta y su forward huérfano: 0 diffs, que es la prueba. Repararlo mueve píxel y choca con §12.5 (el arquetipo fija también `color`) → §13, espera firma. El acento del checkbox (`_palette-solid`) sí vive y se conserva. 2. NO se acuña `hover-row-bg` aunque la propuesta lo pedía. La fila es `archetype: 'item'`: el velo del sistema la pinta a especificidad plena y el plano de la receta pierde — medido, el hover pinta DOS VECES sobre el MISMO nodo (a diferencia de table/tree-grid, donde velo y plano caen en nodos distintos). Sería un token que no puede ganar; mismo criterio que listbox.highlighted → §12.7. Y fila Y celda llevan ambas `item`, así que bajo el puntero se apilan tres capas. 3. «Consume la capa compartida `menu-indicator`» es FALSO. Sale de `SHARED_LAYERS` (`theming-census.ts:432`), pero esa capa no tiene un solo selector de grid-list, la receta no la importa y soma no renderiza indicator: el checkbox ESPEJA el visual pintándolo por su cuenta. Entrada desviada del censo → §13 (listbox / menubar / navigation-menu están en la misma entrada y tampoco aparecen en el fichero). Nombres, sobre la propuesta: `selection-checkbox-{width,height}` son UN knob → `selection-checkbox-size`; `on-selection-checkbox-bg` → `checked-selection-checkbox-fg` (es la tinta del glifo, y `on-` es prefijo reservado); `border` partido en `border-width` + color (molde `command.input-border*`), que el checkbox comparte; `padding` uniforme se queda `padding` (precedente `command.viewport-padding`); `max-block` entra al contrato (molde `command.viewport-max-block`). Artefactos y gates: - Sonda antes/después = 0 diffs en 6.090 valores × 7 estados. Determinismo verificado: dos corridas del mismo código, 0 diffs. - R-5.4: 37/38 tokens mueven un computed. `invalid-border` adjudicado EN EL LEDGER con su razón medida (la demo arranca con su control en off; activado a mano alcanza: oklch(0.9555 0.0207 13.86) → rgb(1,2,3)). - censo --only 85 % · component:audit PASS (R-5.3 incluida) · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el único es el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio (72 globales, ninguno mío). - README con su «Talla y tema» y tab `Tokens` en la demo (38 claves, leídas en vivo). El tab `Recipe` documentaba la regla retirada: corregido. Las fichas de calendar / carousel / command / month-grid / range-calendar / table / year-grid entran porque `--report` regenera el informe entero: sólo actualizan NÚMEROS DE LÍNEA de commits ya hechos que no lo regeneraron. Cero cambios de alcance. Las otras 161, que sólo cambiaban la fecha, se revirtieron. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
},
uix(drag-drop): temable — 0 % → 89 %, casi todo en el fantasma del arrastre 10 claves en el bloque `drag-drop` de `recipes/base.ts` (fusionadas con `preview-z`, que ya estaba, y los dos forwards de paleta). Censo 0 % → 89 %, cero globales. **Siete de sus dieciséis knobs YA eran sistema** y no entran al ratio (las opacidades de fantasma y deshabilitado, los dos anillos de foco y el anillo `inset` de los droppables), así que los puntuables eran nueve — seis de ellos en el PREVIEW, el fantasma que sigue al puntero. 1. Los dos forwards de paleta siguen privados (THM-2): el fondo del `[data-dragover]` los lee a pelo y no admite un público sin duplicar la regla — es el 11 % que falta. En cambio el `border` y el `box-shadow` del preview los COMBINAN con una anchura y una sombra que sí son knobs propios, así que esas dos declaraciones entran al alcance. 2. El `--ring-inset-*` de los droppables no se toca: es vocabulario del anillo del SISTEMA y la receta se limita a alimentarlo con su acento. Acuñar un `droppable-ring-*` sería vocabulario paralelo sobre un transversal. 3. El `padding` del preview es un shorthand de dos valores → dos knobs lógicos. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.827 valores × 7 estados. Determinismo verificado. - R-5.4: 0/10 en vivo, y los DIEZ adjudicados con su medición. No es un instrumento roto ni tokens muertos: el preview se crea sólo mientras hay un arrastre en curso (0 nodos en reposo, contados), y forzando el nodo los diez mueven su propiedad — padding 8→1234px, radio 6→1236px, borde 1→9px, fondo, tinta, familia, tamaño 14→99px, sombra y z 100→4321. - censo --only 89 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (10 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'drag-drop': {
'preview-padding-block':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (8 → 1234px)',
'preview-padding-inline':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (12 → 1235px)',
'preview-radius':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (6 → 1236px)',
'preview-border-width':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (1 → 9px)',
'preview-bg':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (rgb(1,2,3))',
'preview-fg':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (rgb(4,5,6))',
'preview-font-family':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (Zapfino)',
'preview-font-size':
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (14 → 99px)',
'preview-shadow':
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA 10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese techo bajo es lo correcto: **es un componente de TONO FIJO**. Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga», dice su cabecera. La pintura vive en el SVG bajo la excepción que `recipe-css-contract.test.ts` ya reconoce para natural-time-picker, color-picker y proof-of-human. Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values: retune here, in one place»: - **El ancho base es POR SKIN**, no un número: un casete no mide lo que un tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y escribió un solo `base-width`; el assert del script lo cazó al encontrar dos declaraciones, y la corrección salió de volver a leer el fichero entero. - La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo vocabulario. - Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue enseñando su vidrio») y el radio del área de pulsación de las teclas. Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es madera) y los cuatro `100%` de layout. EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE no prueba nada contra una propiedad capada**. El ancho es `min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE (133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela igual al real lee como no efecto»: el centinela tiene que ser imposible **en la dirección en que la propiedad puede moverse**, y un guard que probara los dos extremos cazaría ambas familias. Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió a romperse por comillas simples dentro de cadenas de comillas simples (`data-skin='record'`). Reescrito con comillas dobles, verificado con un import real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código de salida REAL**: cero fallos. Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente, que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'the drag PREVIEW is created only while a drag is in flight (0 nodes at rest, counted); forced → reaches (0 0 0 7px rgb(7,8,9))'
uix(drag-drop): temable — 0 % → 89 %, casi todo en el fantasma del arrastre 10 claves en el bloque `drag-drop` de `recipes/base.ts` (fusionadas con `preview-z`, que ya estaba, y los dos forwards de paleta). Censo 0 % → 89 %, cero globales. **Siete de sus dieciséis knobs YA eran sistema** y no entran al ratio (las opacidades de fantasma y deshabilitado, los dos anillos de foco y el anillo `inset` de los droppables), así que los puntuables eran nueve — seis de ellos en el PREVIEW, el fantasma que sigue al puntero. 1. Los dos forwards de paleta siguen privados (THM-2): el fondo del `[data-dragover]` los lee a pelo y no admite un público sin duplicar la regla — es el 11 % que falta. En cambio el `border` y el `box-shadow` del preview los COMBINAN con una anchura y una sombra que sí son knobs propios, así que esas dos declaraciones entran al alcance. 2. El `--ring-inset-*` de los droppables no se toca: es vocabulario del anillo del SISTEMA y la receta se limita a alimentarlo con su acento. Acuñar un `droppable-ring-*` sería vocabulario paralelo sobre un transversal. 3. El `padding` del preview es un shorthand de dos valores → dos knobs lógicos. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.827 valores × 7 estados. Determinismo verificado. - R-5.4: 0/10 en vivo, y los DIEZ adjudicados con su medición. No es un instrumento roto ni tokens muertos: el preview se crea sólo mientras hay un arrastre en curso (0 nodos en reposo, contados), y forzando el nodo los diez mueven su propiedad — padding 8→1234px, radio 6→1236px, borde 1→9px, fondo, tinta, familia, tamaño 14→99px, sombra y z 100→4321. - censo --only 89 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (10 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
},
uix(link): temable — 0 % → 36 %, y ese techo bajo es la respuesta correcta 5 claves fusionadas en el bloque `link` de `recipes/base.ts` (donde ya vivían los dos forwards de paleta). Censo 0 % → 36 %. **Un enlace en línea hereda casi todo del texto que lo rodea**, así que su superficie de tema es pequeña por diseño. Sus dos escotillas por instancia (`--_link-font-size`, `--_link-line-height`) tienen como fallback `inherit`, y ese `inherit` es SEMÁNTICO: el enlace toma la tipografía del párrafo en el que vive. Convertirlo en token exigiría un público con valor `inherit`, que en `:root` no significa lo mismo — se quedan privadas. Con los dos forwards de paleta, ésos son los cuatro privados que forman el grueso del 64 % restante. Lo tokenizable es la GEOMETRÍA: radio del anillo, hueco del glifo externo, tamaño del glifo (`0.85em` en las dos dimensiones → UN knob) y el grosor y desplazamiento del subrayado, cada uno declarado en las DOS reglas que lo dibujan (`always` y `hover`) con el mismo valor. Las tres opacidades son sistema; el `fill: currentColor` del glifo es identidad, sigue a la tinta del enlace a propósito. **La cascada no se toca.** El CSS lleva dos correcciones de especificidad ya medidas y explicadas en su sitio (el `inherit` condicional, para que un `color` pedido por el consumidor no se descarte en silencio; y el `:active` al final, porque una pulsación es siempre también un hover). El diff de computed confirma que tokenizar no las altera. Artefactos y gates: - Sonda antes/después = 0 diffs en 203 valores × 7 estados. Determinismo verificado. - R-5.4: 2/5, los tres mudos adjudicados con su medición. Ninguno es deuda: el glifo externo no se monta en la demo (0 nodos, contados) y el enlace arranca en `data-underline='hover'` sin puntero encima, así que NO HAY subrayado que mover (`text-decoration-line: none`, `thickness: auto`). Forzando el estado, los tres alcanzan. - En el mismo pase, `text-decoration-thickness` y `text-underline-offset` entran en la lista de propiedades del guard, que no las miraba — verificado que los otros DIECISÉIS componentes con ledger no cambian. - censo --only 36 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y tema» y tab `Tokens` (5 claves). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
link: {
'external-icon-size':
'the demo mounts no EXTERNAL link, so the glyph never renders (0 nodes, counted); forced → reaches (11.89 → 77px on both axes)',
'underline-width':
"the demo link sits at data-underline='hover' with no pointer on it, so there IS no underline to move (text-decoration-line: none, thickness: auto). Forced 'always' → reaches (1 → 9px). textDecorationThickness was missing from the guard prop list and was added in the same pass",
'underline-offset':
"same: no underline at rest under data-underline='hover'; forced → reaches (2 → 11px). textUnderlineOffset added to the guard prop list too"
},
uix(knob): temable — 29 % → 75 %, y un nombre que significaba dos cosas 33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 29 % → 75 %. El knob ya tenía siete públicos «de facto» —consumidos como `var(--knob-x, <default>)`— pero **ninguno declarado**, así que un tema no los veía en `getRecipeTokens()`. Tokenizar ha sido sobre todo DECLARAR lo que ya se consumía, y separar dos cosas que compartían nombre. 1. **Cuatro `--knob-*` quedan FUERA a propósito**: `progress`, `angle`, `start-angle` y `sweep` los escribe el provider INLINE desde el estado del control (`knob-provider.svelte.ts:358`). Canal de valor, no superficie de tema \u2014 ningún contrato gana a un estilo inline. Misma clase que `carousel.item-gap` y que la `z` del preview de drag-drop que retiró la revisión adversarial. 2. **⚠ `--knob-arc-width` significaba DOS cosas con DOS defaults distintos**: el inset de la cara del dial (`--space-3`, líneas 80 y 113) y la separación superior del puntero (`--space-2`, línea 165). Un tema que lo escribiera movía las tres declaraciones a la vez, pero **sin tema cada una valía algo distinto** \u2014 «un nombre que no distingue lo que debería», que es la definición de colisión que usa el propio generador. Separado en `arc-width` (la cara) y `pointer-inset` (el puntero), cada uno con su valor verbatim: cambia la superficie de tema, **no el píxel**. 3. **Las coordenadas por talla SUBEN al TSC, y no era opcional.** El default por talla vivía en un privado y el público era un override que iba DELANTE (`var(--knob-gap, var(--_knob-gap))`): declarar `--knob-gap` en el contrato **habría matado la escala**, porque ganaría siempre. Con el patrón del resto del eje (coordenadas + resuelto por `data-size`) el default sigue vivo y el tema alcanza. Eje sm|md|lg|xl, sin xs; el diámetro es `control-height × 2` en cada talla, proporción del bundle conservada verbatim. `--_knob-value-digits` sigue privado: es el número de dígitos del valor máximo, dato del consumidor. Artefactos y gates: - Sonda antes/después = 0 diffs en 754 valores × 7 estados. Determinismo verificado. - R-5.4: 25/33; los ocho mudos adjudicados con su medición (la demo no monta etiqueta \u2014 0 nodos contados \u2014 ni estado de arrastre; forzados, los ocho mueven). - censo --only 75 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos 439/440 (el rojo conocido) · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo (33 claves). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
knob: {
'label-font-size-sm':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (per-size coordinate; the resolved name moves with it)',
'label-font-size-md':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (16 → 99px)',
'label-font-size-lg':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (per-size coordinate)',
'label-font-size-xl':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (per-size coordinate)',
'label-font-size':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (16 → 99px)',
'label-fg':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (rgb(1,2,3))',
'label-line-height':
'the demo mounts no [data-knob-label] (0 nodes, counted); forced → reaches (20px → 3.77em)',
'dragging-shadow':
'consumed only under [data-dragging], and the demo knob is at rest (0 nodes, counted); forced → reaches (0 0 0 7px rgb(4,5,6))'
},
uix(label): temable — 0 % → 100 % con dos claves 2 claves fusionadas en el bloque `label` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → **100 %**. **Seis de sus ocho knobs son SISTEMA y no se acuñan.** `label` es uno de los seis primitivos tipográficos: sus seis ejes consumen `--style-label-*` a través de la escotilla por instancia (`var(--_label-{eje}, var(--style-label-{eje}))`), la forma que D-TH.2-b fijó y la que hace que un `<Label>` y un `<Text style="label">` se lean idénticos. La capa los posee; re-declararlos sería el vocabulario paralelo que la regla 2 prohíbe. Quedan DOS knobs propios, los que el primitivo añade por su cuenta: el `gap` al icono o indicador que puede acompañar al texto, y `disabled-fg`, la tinta cuando el consumidor marca la etiqueta como deshabilitada junto a su control. `--_label-color` sigue privado: conmutador de dos fuentes (eje de tinta en línea / forward de paleta cuando llega `data-color`), el mismo patrón ya resuelto igual en `code`, `textarea`, `spinner` y `skeleton`. Artefactos y gates: - Sonda antes/después = 0 diffs en 174 valores × 7 estados. - R-5.4: 1/2. `disabled-fg` adjudicado: la demo no monta ninguna etiqueta deshabilitada (0 nodos, contados); forzada, alcanza (rgb(1,2,3)). - censo --only 100 % · component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
label: {
'disabled-fg':
'the demo mounts no disabled label (0 [data-disabled] nodes, counted); forced → reaches (rgb(1,2,3))'
},
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía 33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato (los tres restantes son sistema — anillo de foco y los dos del plano de profundidad que pintan la flecha). Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8. 1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está PORTALADO y su `data-size` vive en el content, así que una cascada colgada del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas (padding, min-width, tipo); familia, peso e interlineado son constantes a propósito, como ya decía la receta. 2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano `overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo esa garantía. El subrayado del trigger es un `color-mix` sobre `currentColor` — la mezcla ES el aspecto, así que el knob es la declaración entera, no el 35 %. Y el fallback encadenado del color del trigger viaja verbatim: la coma es parte del valor, no un segundo knob. EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9): `[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo, borde y sombra: también `font-family` y `line-height`. El panel estampa ese plano para su elevación, así que la declaración del plano empata a (0,1,0) con la de la receta y **gana por orden de carga** (la hoja generada va después del chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par `font-family` / `line-height` de esta receta **no había pintado nunca**. Retirado con **0 diffs**, que es la prueba. Afecta a los **veinte** componentes que estampan el plano. Lo que hay que decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase «empate resuelto por orden de carga». CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí: El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1 nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por hover, captura los cinco nodos reales en las cinco tallas): determinismo verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras la retirada. El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'` para este componente (un hover card no se abre con clic — clicar su `<a>` navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes con ledger no se mueven ni una cifra. Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de hover lee vivo en la misma corrida. Adjudicados con esa razón. Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y tema» y tab `Tokens` en la demo (33 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'link-preview': {
uix(theming): correcciones de la revisión de F2-B — el empate no se resuelve de oficio Auditoría de la sesión con medición. Cinco correcciones; la primera toca código y es la que importa. 1. **`link-preview`: RESTAURADAS `font-family` y `line-height`.** Las retiré como «declaraciones muertas» apoyándome en los precedentes de `table.selected-row-fg` y del trigger de `gradient-picker`, pero **no son el mismo caso**: allí perdían por ESPECIFICIDAD, aquí empatan a (0,1,0) con el plano `[data-depth='overlay']` y decide el ORDEN DE CARGA. Retirarlas no es neutral — **fija el empate a favor del plano** y con él su 1.25 donde la receta pide `--leading-normal` (1.5), que es justo la decisión que §12.9 tiene pendiente para VEINTE componentes. Un empate por orden de carga se registra, no se resuelve dentro de un commit de tokenización. Y la medición era más débil de lo que el commit daba a entender: estable en cinco recargas **en DEV**, sin verificar en un bundle de producción. Restauradas → **0 diffs contra el estado PRE-SESIÓN** (550 valores × 5 tallas), censo de vuelta a 100 %, y las dos adjudicadas en el ledger con esa razón. §12.9 y el README dicen ahora que el par se deja declarado A PROPÓSITO. 2. **Marcas `R-5.x exception:` en 16 READMEs.** El gate del plan es «censo al 100 % o excepción escrita», y la válvula que `component-audit` lee es esa marca (`readmeException`), no la prosa. Tenía la razón documentada en veredicto, README y commit, pero sin la marca: cuando F3 active R-5.1/5.2, los 16 habrían dado error. Una marca por clase medida — forward de paleta THM-2 · conmutador de dos fuentes · eje propiedad de una capa · identidad de layout. Los tres al 100 % (`code`, `label`, `link-preview`) no la necesitan. 3. **Continuidad restaurada.** El handoff seguía diciendo «45 %» y «empieza por grid-list», y `PLAN §8` no tenía una sola línea de la sesión pese a que el protocolo lo pide por componente: una sesión nueva habría arrancado con el mapa viejo. Reescrita la agenda de `CONTINUE-theming.md` (cifras, la revisión adversarial pendiente COMO PRIMERA TAREA, la cola restante, las 8 firmas), registrado el bloque en `PLAN §8` con su tabla de 22 commits y sus seis lecciones, y actualizada la memoria del eje. 4. **`recipe-contract §4`** documenta las SEIS capacidades que R-5.4 ganó en el bloque, cada una nombrando la forma en que el instrumento mentía, más la lección que generaliza: una sonda que inspecciona pocos nodos pasa en falso — `picker-shell` medía CERO y reportaba éxito. 5. **«un paso por debajo desde lg» → «desde md»** en `tree-view`, `virtual-list` y `virtual-grid` (README + ficha). El escalón empieza en md (md→sm); los valores eran correctos (0 diffs), el texto no. Lo que esta revisión NO arregla, y queda como primera tarea del eje: **F2-B sigue sin su revisión adversarial §7.7**. Sus 21 componentes llevan sólo la medición de quien los escribió, y el plan la exige antes de cerrar un bloque. El handoff nombra las dos hipótesis concretas que hay que atacar. Gates: censo global 50 % · `--names` DESVIADAS 0 sobre 3.993 claves · `component:audit` 162 PASS / 4 NEEDS-WORK (los cuatro preexistentes, ninguno con un commit de esta sesión en su código) · R-5.4 verde en los 22 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'font-family':
"declared but out-ranked: the panel stamps `data-depth='overlay'`, whose plane declares font-family too at an EQUAL (0,1,0) — the generated sheet loads later, so it wins. Kept on purpose: retiring it would fix that tie in the plane's favour, a pixel decision pending signature (next-features §12.9). Measured with CDP matched-rules 2026-08-21, stable across 5 dev reloads",
'line-height':
'same load-order tie with the `overlay` plane, which imposes 1.25 where this recipe asks for --leading-normal (1.5). Declared and kept until the signature resolves who owns overlay typography',
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía 33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato (los tres restantes son sistema — anillo de foco y los dos del plano de profundidad que pintan la flecha). Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8. 1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está PORTALADO y su `data-size` vive en el content, así que una cascada colgada del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas (padding, min-width, tipo); familia, peso e interlineado son constantes a propósito, como ya decía la receta. 2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano `overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo esa garantía. El subrayado del trigger es un `color-mix` sobre `currentColor` — la mezcla ES el aspecto, así que el knob es la declaración entera, no el 35 %. Y el fallback encadenado del color del trigger viaja verbatim: la coma es parte del valor, no un segundo knob. EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9): `[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo, borde y sombra: también `font-family` y `line-height`. El panel estampa ese plano para su elevación, así que la declaración del plano empata a (0,1,0) con la de la receta y **gana por orden de carga** (la hoja generada va después del chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par `font-family` / `line-height` de esta receta **no había pintado nunca**. Retirado con **0 diffs**, que es la prueba. Afecta a los **veinte** componentes que estampan el plano. Lo que hay que decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase «empate resuelto por orden de carga». CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí: El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1 nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por hover, captura los cinco nodos reales en las cinco tallas): determinismo verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras la retirada. El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'` para este componente (un hover card no se abre con clic — clicar su `<a>` navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes con ledger no se mueven ni una cifra. Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de hover lee vivo en la misma corrida. Adjudicados con esa razón. Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y tema» y tab `Tokens` en la demo (33 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'trigger-fg':
'the panel opens on HOVER, so measuring it REQUIRES the pointer on the trigger — and hover is exactly the state whose rule overrides the resting ink. Structural, not a demo gap: the hover token (`hover-trigger-fg`) reads live in the same run',
'trigger-underline':
'same structural conflict: the hover rule sets `text-decoration-color: currentColor` over the resting mix, and the panel cannot be opened without hovering'
},
uix(menu-dial): temable — 0 % → 25 %, y las ranuras de la capa que NO se acuñan 3 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 25 %. **La receta posee sólo el cromo del dial**, como dice su propia cabecera: las acciones son `Button` compuestos y la colocación flotante es de la capa `viewport-placement`. Lo suyo son tres cosas: el hueco del abanico, el paso del escalonado de entrada y el tinte del velo. **`--_viewport-placement-offset` y `-z` NO se acuñan, y ésa es la decisión del commit.** Son las RANURAS DE OVERRIDE que la capa compartida publica, y esta receta escribe en ellas su decisión — 20px en vez de los 16px por defecto de la capa, y el comentario del CSS deja claro que es decisión y no deriva. Acuñar `menu-dial-placement-offset` sería el vocabulario paralelo que la regla 2 de capas compartidas prohíbe: el eje es de la capa, y un tema lo mueve ahí. Es el mismo techo que `listbox` paga por consumir bien `list-surface` — el grueso del 75 % que no alcanza, y es correcto que no lo haga (§13). **El escalonado es tema, no motion**: `stagger-step` no es una duración de animación sino el INTERVALO entre hermanos (multiplica el índice de cada acción), y es lo que da carácter al abanico. Por eso entra al contrato. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.856 valores × 8 estados. - R-5.4: 1/3, los dos mudos adjudicados con su medición — el velo sólo existe con el dial abierto (0 nodos en reposo, contados) y el escalonado sólo se aplica bajo `[data-stagger]` con la lista abierta; forzando ambos atributos con índice 3, alcanza (0.084s → 2.331s = 3 × 777ms). - censo --only 25 % · component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo (3 claves). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'menu-dial': {
'backdrop-bg':
'the backdrop only exists while the dial is open (0 nodes at rest, counted); forced → reaches (rgb(1,2,3))',
'stagger-step':
"consumed only by `[data-stagger] [data-state='open'] .menu-dial-action`, and the demo dial is closed and un-staggered; forced both attrs with index 3 → reaches (0.084s → 2.331s = 3 × 777ms)"
},
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía 31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global 48 %, sin contrato 43 → 42. La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta». Mirarlo cambió tres cosas del encargo: 1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de list-surface o calendar-surface no hay hook de capa ni vocabulario prestado — color-picker, date-picker y chronos importan sus `.svelte` directamente. No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien. 2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre `[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`, que no casa nunca), así que las coordenadas por talla quedan como públicos PLANOS y la receta las consume desde sus propios selectores. Es el único componente del eje donde el patrón de nombres resueltos no aplica; la regla base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de tiempo lleva un TERCER eje, `data-size` sobre su propia clase. 3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0 nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker anfitrión y detrás de su popover), y sus partes se llaman `data-picker-header` / `-body` / `-footer` en vez de `data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so every picker gets the same visual contract for free» —, así que todo filtro por prefijo del componente ve sólo la raíz. CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía: - Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0 diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**. - Los knobs que la demo no monta —el header entero y la fila de tiempo— se midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su picker-size). - El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES` (prefijo de atributo + selector de apertura) acotado a este componente. Verificado que los otros CATORCE con ledger no se mueven ni una cifra. Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en el ledger; los 25 que siguen mudos llevan cada uno su razón medida. Header y footer comparten la regla que los separa del cuerpo → UN par de knobs (`border-width` + `border`), no dos. Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en next-features §13 junto con la pregunta de fondo — si el precio de los nombres genéricos (un contrato visual común para todos los pickers) compensa la ceguera del instrumental — y con el eje portalado como caso de prueba si el TSC llega a ganar un scope configurable. Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio · `component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'picker-shell': {
'section-gap-xs':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'section-gap-sm':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'section-gap-lg':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'row-spacing-xs':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'row-spacing-lg':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'footer-gap-xs':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'footer-gap-lg':
'its size axis is `data-picker-size` on the PORTALED [data-popover-content], not `data-size` on itself, and the guard only cycles `data-size`; the demo popover sits at md. Cycled by hand across the four picker-sizes 2026-08-21 → reaches (section/row/footer spacing follows the picker size)',
'header-font-size-xs':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)',
'header-font-size-sm':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)',
'header-font-size-md':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)',
'header-font-size-lg':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (12/14/16/16px → 102px, each in its own picker-size)',
'header-gap':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)',
'header-fg':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)',
'header-font-weight':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)',
'header-line-height':
'the date-picker demo renders no [data-picker-header] (0 nodes, counted) — picker-shell has no demo route of its own (404) and is measured inside a host picker; forced → reaches (gap 8→1234px, fg → rgb(4,5,6), weight 500→123, line-height 20→60.32px)',
'time-row-gap':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-row-padding-block-start':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-row-padding-inline':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-row-border-width':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-row-border':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-label-fg':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-label-font-size-xs':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-label-font-size-sm':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-label-font-size-md':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)',
'time-label-font-size-lg':
'the time row is CLASS-hooked (.picker-time-row) and the date-picker demo does not render it; the guard filters nodes by attribute, so it cannot see it either way (§13 class-hook debt). Forced → reaches (gap/padding → 1234px, border 1→9px + rgb(7,8,9), label → rgb(10,11,12) / 99px)'
},
uix(scroll-frames): temable — 0 % → 33 %, porque es LAYOUT y no cromo 4 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 33 %. **Es layout, no cromo** — la receta lo dice de entrada y la medición lo confirma: una pista alta cuyo viewport pegajoso fija el medio a sangre mientras la pista rueda. Lo único que pinta es el rótulo de carga, y ése es el contrato entero: su separación del borde inferior, el tipo, el tracking y la tinta. **`--_scroll-frames-viewport-block-size` se queda privado**: es la escotilla que el consumidor sobrescribe para encajar el viewport en un contenedor (`100%`) en vez de en la ventana (`100vh`), y su fallback es geometría de LAYOUT, no un valor de tema — quien lo mueve es el sitio de uso, no el tema. Con los `100%` del medio y de la pista (identidad, D-TH.2), el `line-height: 1` del rótulo y el `opacity` del sistema, forman el 67 % que no alcanza, y es correcto que no lo haga. Artefactos y gates: - Sonda antes/después = 0 diffs en 580 valores × 7 estados. - R-5.4: 0/4, los cuatro adjudicados con su medición — el rótulo sólo existe mientras se decodifican los fotogramas (0 nodos una vez cargado, contados) y forzándolo los cuatro mueven (24→1234px, 12→99px, 1.2→9px, rgb(1,2,3)). - censo --only 33 % · component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo (4 claves). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'scroll-frames': {
'loader-inset-block-end':
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (24 → 1234px)',
'loader-font-size':
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (12 → 99px)',
'loader-letter-spacing':
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (1.2 → 9px)',
'loader-fg':
'the loading caption only exists while frames are decoding (0 nodes once loaded, counted); forced → reaches (rgb(1,2,3))'
},
uix(skeleton): temable — 0 % → 45 %, y dos privados que son conmutadores 9 claves fusionadas en el bloque `skeleton` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → 45 %, cero globales. Nueve de sus once knobs pasaban por privado, y la lectura correcta es que **dos de esos privados NO son deuda: son conmutadores**. `--_skeleton-bg` y `--_skeleton-highlight` tienen DOS fuentes cada uno según haya `data-color` — la mezcla neutra cross-mode (un `color-mix` hacia `neutral-solid`, para que el placeholder siga siendo un gris visible en los dos modos: el `neutral-track` desnudo desaparece sobre una superficie clara) o el forward de paleta. Aplanarlos exigiría duplicar cada regla por color, así que se quedan — mismo criterio que el borde de foco de textarea y la tinta de spinner. Son el grueso del 55 % que no alcanza, y es honesto. - **Una coordenada por talla** (`size`) gobierna el alto del bloque, el diámetro del círculo (vía `aspect-ratio`) y el hueco entre líneas de la forma `text`. Ese hueco es `calc(size * 0.5)`: una PROPORCIÓN, construcción, no un knob. - **El radio es por FORMA, no por talla**: `radius-md` en `rect` y `50%` en `circle`, donde el `50%` es identidad (un círculo es redondo) — el knob es sólo el del rectángulo. La línea de la forma `text` lleva el suyo. - `duration` entra al contrato en vez de seguir en `1.4s` a pelo, igual que se hizo con el spinner (§13). Artefactos y gates: - Sonda antes/después = 0 diffs en 203 valores × 7 estados. - R-5.4: 8/9. `line-radius` adjudicado: sólo la forma `text` renderiza `[data-skeleton-line]` y la demo monta `rect` (0 líneas, contadas); forzada la forma, alcanza (4 → 1234px). - censo --only 45 % · component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo (9 claves). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
skeleton: {
'line-radius':
"only the `text` shape renders [data-skeleton-line]; the demo mounts `rect` (0 lines, counted). Forced shape='text' → reaches (4 → 1234px)"
},
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA 10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese techo bajo es lo correcto: **es un componente de TONO FIJO**. Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga», dice su cabecera. La pintura vive en el SVG bajo la excepción que `recipe-css-contract.test.ts` ya reconoce para natural-time-picker, color-picker y proof-of-human. Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values: retune here, in one place»: - **El ancho base es POR SKIN**, no un número: un casete no mide lo que un tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y escribió un solo `base-width`; el assert del script lo cazó al encontrar dos declaraciones, y la corrección salió de volver a leer el fichero entero. - La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo vocabulario. - Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue enseñando su vidrio») y el radio del área de pulsación de las teclas. Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es madera) y los cuatro `100%` de layout. EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE no prueba nada contra una propiedad capada**. El ancho es `min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE (133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela igual al real lee como no efecto»: el centinela tiene que ser imposible **en la dirección en que la propiedad puede moverse**, y un guard que probara los dos extremos cazaría ambas familias. Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió a romperse por comillas simples dentro de cadenas de comillas simples (`data-skin='record'`). Reescrito con comillas dobles, verificado con un import real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código de salida REAL**: cero fallos. Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente, que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'skin-media-player': {
'record-base-width':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, with data-skin=record forced (the demo mounts turntable) -> reaches (111px exactly)',
'cassette-base-width':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-skin=cassette forced -> reaches (122px exactly)',
'deck-base-width':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-skin=deck forced -> reaches (133px exactly)',
'scale-sm':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, with the data-size=sm of the PLAYER forced -> reaches (133 x 0.11 = 14.625px)',
'scale-lg':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-size=lg forced -> reaches (133 x 0.22 = 29.25px)',
'scale-xl':
'the width is `min(100%, base * scale)`, so the LARGE sentinel of the guard is capped by the container and proves nothing. Re-measured with SMALL values 2026-08-22, data-size=xl forced -> reaches (133 x 0.33 = 43.875px)',
'lamp-opacity':
'the lamp only dims while the player is paused and the deck still; forced -> reaches (0.18 -> 0.123)'
},
uix(spinner): temable — 0 % → 70 %, y 71 tokens propuestos que eran 18 18 claves en el bloque `spinner` de `recipes/base.ts` (fusionadas con los dos forwards de paleta). Censo 0 % → 70 %, cero globales. Global 48 %, sin contrato 44 → 43. **La §4 proponía 71 tokens para 23 knobs — la peor inflación que ha dado el generador en todo el eje.** El spinner tiene UNA coordenada de talla (`--spinner-size`) que gobierna el anillo, la pista de puntos y las barras, más el trazo (`--spinner-thickness`). La propuesta multiplicaba esa única coordenada por variante × parte × dimensión × talla y producía `ring-track-width-xs`, `dots-track-gap-xs`, `dots-dot-width-xs`, `bars-track-gap-xs`… todos con el mismo `0.75rem`, porque todos SON el tamaño en xs. Un tema habría necesitado veinte claves para cambiar un número. 1. Las proporciones son CONSTRUCCIÓN, no knobs: `calc(size * 0.3)` es el punto, `* 0.18` la barra, `* 0.2` y `* 0.12` los huecos. Definen la FORMA de cada variante y se quedan en la receta — mismo reparto que los gradientes de las guías de tree-grid o el damero de gradient-builder. 2. `fg` / `dots-dot-bg` / `bars-bar-bg` ⚠ no eran tres knobs con dos valores en pugna: son UNA variable (`--_spinner-color`) leída en tres sitios, con el conmutador `data-color='inherit'` que la cambia de forward de paleta a `currentColor`. Se queda privada, igual que `--_spinner-track`, que además envuelve su fuente en un color-mix distinto por rama (60 % / 25 %). Aplanarlas exigiría duplicar cada regla por color. 3. La escala es PROPIA, no la del bundle de iconos: medido, 12/16/20/28/40 px contra los 14/16/18/20/32 de `--icon-size-*`, y sólo coinciden en sm. Verbatim con la desviación anotada — forzar el bundle habría movido el default en cuatro tallas de cinco. 4. `duration` entra al contrato en vez de seguir en `0.9s` a pelo: el registro ya tenía abierta la incoherencia de que el spinner de feed ganó tokens de duración y el de media-player no (§13). Aquí se hace bien de entrada. El 30 % que falta son los dos privados conmutadores, los dos `border-radius: 50%` (identidad de forma) y los fotogramas de los @keyframes. Artefactos y gates: - Sonda antes/después = 0 diffs en 377 valores × 7 estados. Determinismo verificado. - R-5.4: 14/18. Los cuatro mudos, medidos uno a uno y adjudicados: la demo monta SÓLO la variante ring y sin etiqueta (0 barras, 0 puntos, 0 labels, contados), y `track-opacity` sólo se declara dentro de `prefers-reduced-motion` — forzando variante, etiqueta y contexto reduce, los cuatro alcanzan. - censo --only 70 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (18 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
spinner: {
'bar-radius':
"the demo mounts the RING variant only (0 bars, 0 dots, counted); forced variant='bars' → reaches (1234px)",
'label-font-size':
'the label is optional and the demo mounts none (0 nodes, counted); forced → reaches (1234px)',
'label-fg': 'the label is optional and the demo mounts none; forced → reaches (rgb(1,2,3))',
'track-opacity':
'its only declaration lives inside `prefers-reduced-motion`; emulated reduce context → reaches (0.6 → 0.123)'
},
uix(card-group): temable — 0 % → 90 %, y dos privados abreviados que mueren 20 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 90 %, cero globales y cero privados. Global 47 %, sin contrato 45 → 44. Es un CASCO de composición y su propia cabecera ya lo decía: compone ToggleGroup, Collapsible, Card y Button, y la receta posee «ONLY the genuinely CardGroup-level visual». Eso acota el contrato antes de nombrar nada — el cromo de las tarjetas es de Card, el del título de disclosure es de Button. 1. **`--_cg-pad` y `--_cg-card-radius` eran privados ABREVIADOS** (§6 r5 los prohíbe: `cg` por `card-group`), el mismo defecto que los `--_mp-*` de media-player. Pasar a públicos con el nombre completo es a la vez el renombrado que la regla pedía y la tokenización. La §3 de la ficha decía «la receta no declara privados propios»: los declaraba en su bloque raíz (`card-group.css:15-16`), y el escáner no los veía por buscarlos con el prefijo completo del componente. 2. **`--shape-outer-radius` NO se acuña: se CALCULA.** La receta lo compone como `card-radius + padding` para que las tarjetas aniden concéntricamente (§30). Un tema mueve las dos coordenadas y el radio exterior se recompone solo; el censo ya lo clasifica como sistema. 3. **El eje `size` es sm|md|lg, no las cinco tallas** (`Extract<Size, 'sm' | 'md' | 'lg'>`): seis coordenadas por talla, no diez. Escribir xs/xl habría sido vocabulario muerto. 4. Los tres `padding` son shorthand con una parte fija y otra variable; lo que varía y no es el inset del grupo es el hueco inferior → `title-padding-block-end` y `description-padding-block-end`. 5. El chevron es geometría propia (dos bordes girados 45°): `chevron-size` cubre las dos dimensiones y `chevron-width` el trazo. Su `currentColor` hereda del título a propósito y no es knob. 6. Los `font-size` van al BUNDLE (`--size-{k}-font-size`, alias 1:1) y no al primitivo crudo — misma trampa que cayó en code-block: la propuesta copia el CSS verbatim y hereda el incumplimiento cuando el valor de partida ya violaba `recipe-css-contract`. Aquí se aplicó de entrada. Artefactos y gates: - Sonda antes/después = 0 diffs en 2.581 valores × 7 estados. Determinismo verificado: dos corridas, 0 diffs. - R-5.4: 15/20. Los cinco mudos son los del encabezado ESTÁTICO, que la demo no monta (0 nodos `[data-static]` contra 1 `[data-button]`, CONTADOS); forzado `data-static` alcanzan los cinco, y cada uno lleva su medición en el ledger. El hueco de demo queda registrado en next-features §13, misma clase que el `kind='month'|'year'` de date-range-picker. - censo --only 90 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'card-group': {
'title-fg':
'the STATIC heading form is not mounted in the demo (0 [data-static] nodes vs 1 [data-button], counted); forced → reaches (rgb(1,2,3))',
'title-font-family':
'static heading not mounted in the demo; forced → reaches (Instrument Sans → Zapfino)',
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA 10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese techo bajo es lo correcto: **es un componente de TONO FIJO**. Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga», dice su cabecera. La pintura vive en el SVG bajo la excepción que `recipe-css-contract.test.ts` ya reconoce para natural-time-picker, color-picker y proof-of-human. Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values: retune here, in one place»: - **El ancho base es POR SKIN**, no un número: un casete no mide lo que un tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y escribió un solo `base-width`; el assert del script lo cazó al encontrar dos declaraciones, y la corrección salió de volver a leer el fichero entero. - La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo vocabulario. - Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue enseñando su vidrio») y el radio del área de pulsación de las teclas. Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es madera) y los cuatro `100%` de layout. EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE no prueba nada contra una propiedad capada**. El ancho es `min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE (133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela igual al real lee como no efecto»: el centinela tiene que ser imposible **en la dirección en que la propiedad puede moverse**, y un guard que probara los dos extremos cazaría ambas familias. Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió a romperse por comillas simples dentro de cadenas de comillas simples (`data-skin='record'`). Reescrito con comillas dobles, verificado con un import real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código de salida REAL**: cero fallos. Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente, que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'title-font-size': 'static heading not mounted in the demo; forced → reaches (16 → 1234px)',
'title-line-height': 'static heading not mounted in the demo; forced → reaches (20px → 3.77em)',
uix(card-group): temable — 0 % → 90 %, y dos privados abreviados que mueren 20 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 90 %, cero globales y cero privados. Global 47 %, sin contrato 45 → 44. Es un CASCO de composición y su propia cabecera ya lo decía: compone ToggleGroup, Collapsible, Card y Button, y la receta posee «ONLY the genuinely CardGroup-level visual». Eso acota el contrato antes de nombrar nada — el cromo de las tarjetas es de Card, el del título de disclosure es de Button. 1. **`--_cg-pad` y `--_cg-card-radius` eran privados ABREVIADOS** (§6 r5 los prohíbe: `cg` por `card-group`), el mismo defecto que los `--_mp-*` de media-player. Pasar a públicos con el nombre completo es a la vez el renombrado que la regla pedía y la tokenización. La §3 de la ficha decía «la receta no declara privados propios»: los declaraba en su bloque raíz (`card-group.css:15-16`), y el escáner no los veía por buscarlos con el prefijo completo del componente. 2. **`--shape-outer-radius` NO se acuña: se CALCULA.** La receta lo compone como `card-radius + padding` para que las tarjetas aniden concéntricamente (§30). Un tema mueve las dos coordenadas y el radio exterior se recompone solo; el censo ya lo clasifica como sistema. 3. **El eje `size` es sm|md|lg, no las cinco tallas** (`Extract<Size, 'sm' | 'md' | 'lg'>`): seis coordenadas por talla, no diez. Escribir xs/xl habría sido vocabulario muerto. 4. Los tres `padding` son shorthand con una parte fija y otra variable; lo que varía y no es el inset del grupo es el hueco inferior → `title-padding-block-end` y `description-padding-block-end`. 5. El chevron es geometría propia (dos bordes girados 45°): `chevron-size` cubre las dos dimensiones y `chevron-width` el trazo. Su `currentColor` hereda del título a propósito y no es knob. 6. Los `font-size` van al BUNDLE (`--size-{k}-font-size`, alias 1:1) y no al primitivo crudo — misma trampa que cayó en code-block: la propuesta copia el CSS verbatim y hereda el incumplimiento cuando el valor de partida ya violaba `recipe-css-contract`. Aquí se aplicó de entrada. Artefactos y gates: - Sonda antes/después = 0 diffs en 2.581 valores × 7 estados. Determinismo verificado: dos corridas, 0 diffs. - R-5.4: 15/20. Los cinco mudos son los del encabezado ESTÁTICO, que la demo no monta (0 nodos `[data-static]` contra 1 `[data-button]`, CONTADOS); forzado `data-static` alcanzan los cinco, y cada uno lleva su medición en el ledger. El hueco de demo queda registrado en next-features §13, misma clase que el `kind='month'|'year'` de date-range-picker. - censo --only 90 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'title-padding-block-end':
'static heading not mounted in the demo; forced → reaches (8 → 1234px)'
},
uix(clipboard): temable — 0 % → 63 %, todo en la píldora de «Copied!» 6 claves fusionadas en el bloque `clipboard` de `recipes/base.ts` (donde ya vivían los dos forwards de paleta). Censo 0 % → 63 %. **Todo lo tokenizable es el INDICADOR.** El host va `display: contents` y el trigger reusa la receta de `Button` por el patrón de consumidor, así que la receta sólo posee la píldora que aparece un instante tras copiar. - Los dos forwards de paleta siguen privados (THM-2): el fondo y la tinta del chip los leen a pelo, sin sitio donde meter un público sin duplicar la regla — son el 37 % que no alcanza. - `font-family` queda fuera por la misma razón que en code-block, kbd y announce: lo posee la capa tipográfica. - El padding es un shorthand de dos valores → dos knobs lógicos. - **Los `--motion-*` no son knobs de tema**: son los ganchos con que el indicador afina el preset `scale-fade` del canal de motion (escala 0.85, rápida). Pertenecen al canal, no al contrato, y la política de `prefers-reduced-motion` la emite el generador. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.392 valores × 8 estados. - R-5.4: 0/6, los seis adjudicados con su medición — el indicador sólo existe un instante tras copiar (0 nodos en reposo, contados) y forzándolo los seis mueven su propiedad (gap 4→1234px, padding 2/8→1235/1236px, radio 9999→1237px, tipo 12→99px, peso 500→123). - censo --only 63 % · component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con «Talla y tema» y tab `Tokens` en la demo (6 claves). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
clipboard: {
'indicator-gap':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (4 → 1234px)',
'indicator-padding-block':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (2 → 1235px)',
'indicator-padding-inline':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (8 → 1236px)',
'indicator-radius':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (9999 → 1237px)',
'indicator-font-size':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (12 → 99px)',
'indicator-font-weight':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (500 → 123)'
},
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio 6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato. **Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`. - El padding es em-relativo a propósito (la píldora crece con el código que envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje, no cuatro. - `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido en línea / forward de paleta cuando llega `data-color`), mismo patrón que el borde de foco de textarea. - El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la base es RESET: la variante `plain` es texto desnudo por identidad. EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado): Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`, `padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta **(0,1,0)**. En la página de documentación el radio del componente es el del sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los dos componentes. Artefactos y gates: - Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo verificado. - R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness (CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin cromo; forzando las variantes, los cinco alcanzan. - censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 · check por fichero limpio · suite eidos sin rojos nuevos. - README con «Talla y tema» y tab `Tokens` en la demo (6 claves). Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por bueno porque el `$?` capturado era el del `grep` de la tubería, no el del guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando el código de salida REAL: cero fallos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
code: {
radius:
'the DEMO HARNESS wins: `[data-uix-docs] code` (uix.css, 0-1-1) beats the recipe base rule `[data-code]` (0-1-0) and sets border-radius/padding/font on the inline code — confirmed with CDP matched-rules. The variant rules (0,2,0) DO win, which is why only this base-rule token reads dead. Same harness rule that hides the <pre> of code-block (next-features §13)',
'soft-bg':
"the demo mounts the `plain` variant only (no chrome); forced variant='soft' → reaches (rgb(1,2,3))",
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA 10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese techo bajo es lo correcto: **es un componente de TONO FIJO**. Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga», dice su cabecera. La pintura vive en el SVG bajo la excepción que `recipe-css-contract.test.ts` ya reconoce para natural-time-picker, color-picker y proof-of-human. Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values: retune here, in one place»: - **El ancho base es POR SKIN**, no un número: un casete no mide lo que un tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y escribió un solo `base-width`; el assert del script lo cazó al encontrar dos declaraciones, y la corrección salió de volver a leer el fichero entero. - La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo vocabulario. - Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue enseñando su vidrio») y el radio del área de pulsación de las teclas. Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es madera) y los cuatro `100%` de layout. EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE no prueba nada contra una propiedad capada**. El ancho es `min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE (133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela igual al real lee como no efecto»: el centinela tiene que ser imposible **en la dirección en que la propiedad puede moverse**, y un guard que probara los dos extremos cazaría ambas familias. Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió a romperse por comillas simples dentro de cadenas de comillas simples (`data-skin='record'`). Reescrito con comillas dobles, verificado con un import real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código de salida REAL**: cero fallos. Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente, que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'border-width': "the demo mounts `plain` only; forced variant='outline' → reaches (1 → 9px)",
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio 6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato. **Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`. - El padding es em-relativo a propósito (la píldora crece con el código que envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje, no cuatro. - `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido en línea / forward de paleta cuando llega `data-color`), mismo patrón que el borde de foco de textarea. - El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la base es RESET: la variante `plain` es texto desnudo por identidad. EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado): Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`, `padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta **(0,1,0)**. En la página de documentación el radio del componente es el del sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los dos componentes. Artefactos y gates: - Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo verificado. - R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness (CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin cromo; forzando las variantes, los cinco alcanzan. - censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 · check por fichero limpio · suite eidos sin rojos nuevos. - README con «Talla y tema» y tab `Tokens` en la demo (6 claves). Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por bueno porque el `$?` capturado era el del `grep` de la tubería, no el del guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando el código de salida REAL: cero fallos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
border: "the demo mounts `plain` only; forced variant='outline' → reaches (rgb(4,5,6))",
'padding-inline':
uix(skin-media-player): temable — 0 % → 22 %, y un techo que es NATURALEZA 10 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 22 %, y ese techo bajo es lo correcto: **es un componente de TONO FIJO**. Sus cuatro objetos son cosas físicas —caoba, aluminio cepillado, cromo, teclas de marfil, una etiqueta de vino, un eje dorado— y esos tonos son la DEPICCIÓN, no roles de tema: «un tocadiscos no se vuelve azul porque el tema lo haga», dice su cabecera. La pintura vive en el SVG bajo la excepción que `recipe-css-contract.test.ts` ya reconoce para natural-time-picker, color-picker y proof-of-human. Lo que SÍ entra es el TAMAÑO, que la propia receta llamaba «design values: retune here, in one place»: - **El ancho base es POR SKIN**, no un número: un casete no mide lo que un tocadiscos (300 / 340 / 480 px). Mi primera lectura del CSS se quedó corta y escribió un solo `base-width`; el assert del script lo cazó al encontrar dos declaraciones, y la corrección salió de volver a leer el fichero entero. - La escala sigue el eje `size` del REPRODUCTOR (0.7 / 1 / 1.35 / 1.7), que él estampa en su raíz y el skin lee desde ahí — sin prop propia ni segundo vocabulario. - Más la opacidad de la lámpara apagada (0.18: «una bombilla apagada sigue enseñando su vidrio») y el radio del área de pulsación de las teclas. Fuera a propósito: el `aspect-ratio` de cada skin (proporción del objeto dibujado — cambiarla lo deforma), el `drop-shadow` del casete (su marrón es madera) y los cuatro `100%` de layout. EL HALLAZGO DE INSTRUMENTO (→ next-features §13): **un valor centinela GRANDE no prueba nada contra una propiedad capada**. El ancho es `min(100%, base × scale)`, así que el `1234px` del guard se capa al contenedor y los siete tokens de tamaño leen «no effect» aunque estén vivos. Con valores PEQUEÑOS (111/122/133 px y escalas 0.11/0.22/0.33) alcanzan EXACTAMENTE (133 × 0.11 = 14.625px). Es primo del caso ya registrado «un valor centinela igual al real lee como no efecto»: el centinela tiene que ser imposible **en la dirección en que la propiedad puede moverse**, y un guard que probara los dos extremos cazaría ambas familias. Y un error de proceso repetido, ya corregido: el fichero de excepciones volvió a romperse por comillas simples dentro de cadenas de comillas simples (`data-skin='record'`). Reescrito con comillas dobles, verificado con un import real (26 componentes en el ledger) y **re-corridos los 26 comprobando el código de salida REAL**: cero fallos. Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo --only 22 % · suite eidos 439/440 (el rojo conocido de este mismo componente, que no tiene morfo — preexistente y ajeno a este commit) · rtl:check 0 · docs:check 0 · check por fichero limpio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'only the chromed variants pad; forced → reaches (4.12 → 77px), and the variant rule (0,2,0) beats the harness',
'padding-block': 'only the chromed variants pad; forced → reaches (1.29 → 66px)'
uix(code): temable — 0 % → 100 %, y el harness que también le pisa el radio 6 claves fusionadas en el bloque `code` de `recipes/base.ts` (donde ya vivía el forward de paleta). Censo 0 % → **100 %**: no queda un knob de apariencia fuera del contrato. **Seis de sus trece knobs son SISTEMA y no se acuñan.** `code` es el primitivo tipográfico canónico: sus seis ejes de tipo consumen `--style-code-*` con la escotilla por instancia (`var(--_code-{eje}, var(--style-code-{eje}))`), la forma que D-TH.2-b fijó, y un primitivo no re-declara el vocabulario de su capa. Lo que entra al contrato es el CROMO de las variantes `soft` y `outline`. - El padding es em-relativo a propósito (la píldora crece con el código que envuelve) y lo declaran IGUAL las dos variantes con cromo: un knob por eje, no cuatro. - `--_code-color` sigue privado: conmutador de dos fuentes (tinta de contenido en línea / forward de paleta cuando llega `data-color`), mismo patrón que el borde de foco de textarea. - El `margin: 0` / `padding: 0` / `background: transparent` / `border: 0` de la base es RESET: la variante `plain` es texto desnudo por identidad. EL HALLAZGO — el harness pisa también a `code` (→ next-features §13, ampliado): Medido con CDP: `[data-uix-docs] code` declara `font-family`, `font-size`, `padding` y `border-radius` y gana **(0,1,1)** contra la regla BASE de la receta **(0,1,0)**. En la página de documentación el radio del componente es el del sitio (3px), no el suyo (4px). Sus reglas de VARIANTE (0,2,0) sí ganan, así que el efecto es selectivo y por eso sólo `radius` lee muerto. **No es un caso aislado**: es la misma familia de reglas del harness que tapa el `<pre>` de code-block con siete propiedades. Cualquier decisión sobre esa regla cubre los dos componentes. Artefactos y gates: - Sonda antes/después = 0 diffs en 174 valores × 7 estados. Determinismo verificado. - R-5.4: 0/6, los seis adjudicados con su medición — `radius` por el harness (CDP), los otros cinco porque la demo monta sólo la variante `plain`, sin cromo; forzando las variantes, los cinco alcanzan. - censo --only 100 % · component:audit PASS · rtl:check 0 · docs:check 0 · check por fichero limpio · suite eidos sin rojos nuevos. - README con «Talla y tema» y tab `Tokens` en la demo (6 claves). Nota de proceso: el fichero de excepciones llegó a quedar con sintaxis TS inválida por un apóstrofe sin escapar, y la comprobación que hice lo dio por bueno porque el `$?` capturado era el del `grep` de la tubería, no el del guard. Corregido, y re-verificados los ONCE componentes con ledger comprobando el código de salida REAL: cero fallos. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
},
uix(code-block): temable — 0 % → 81 %, y la demo que no enseña el componente 20 claves en una entrada NUEVA de `recipes/base.ts` (no tenía). Censo 0 % → 81 %. Global 46 % → 47 %, sin contrato 46 → 45. El handoff lo llamaba «el más mecánico de los que quedan». No lo es: sus 26 knobs se parten en dos grupos con doctrinas distintas. 1. **El nodo del código NO acuña nada, y la §4 pedía que lo hiciera.** `[data-code-block-code]` consume la capa tipográfica `--style-code-*` en sus cinco declaraciones, y las dos que pasan por privado usan la forma canónica de la escotilla por instancia (`var(--_code-block-font-size, var(--style-code-font-size))`, escrita por el wrapper desde la prop `size` y sólo si llega). Es el patrón que D-TH.2-b fijó para heading/text/code. Acuñar `code-font-family` y compañía sería el vocabulario paralelo que la regla 2 de capas prohíbe. Todo lo que entra al contrato es el CASCO alrededor. 2. **Los tres `font-size` iban al primitivo crudo, y lo cazó un guard.** La propuesta copia el valor del CSS verbatim, y ese valor ya violaba `recipe-css-contract` («recipes consume the size bundle, not the raw size-coordinate primitives»): `title/lang/copy-font-size` corregidos a `var(--size-{k}-font-size)`. Los alias son 1:1 → 0 diffs tras aplicarlo. Aviso general: cuando el CSS de partida ya incumple el contrato, la propuesta generada hereda el incumplimiento. 3. Shorthand partidos: los dos `border` y el `border-bottom` de la cabecera comparten anchura y color → `border-width` + `border`; el `padding` del `<pre>` → `pre-padding`. Los `transparent` de outline/ghost y el `border: 0` de ghost son identidad de variante. El 19 % que falta son esas cinco de la capa, que el censo cuenta como `global` porque `code-block` no está en `TYPOGRAPHIC_PRIMITIVES` — es un compuesto que CONTIENE un primitivo tipográfico, no uno de ellos. Extender la clase `system` del censo a las capas sigue pendiente de tu firma; la métrica penaliza hacer lo correcto y no se fuerza de oficio. DOS HALLAZGOS QUE NO SE ARREGLAN AQUÍ (→ next-features §13): - **La demo de `code-block` no enseña el `code-block`.** `[data-uix-docs] pre` (`web/routes/uix/uix.css:801`) pesa (0,1,1) contra los (0,1,0) de `[data-code-block-pre]`, y `[data-uix-docs]` es el shell de TODAS las páginas. Siete propiedades del `<pre>` las pinta el harness y no la receta: padding (16px 20px contra 12px), border (1px contra ninguno), radius (8px contra 0), background (gris contra transparente), font-family, font-size (13 contra 14) y line-height (18.2 contra 20.3). El fondo, el borde y el radio que se ven ahí son del sitio. Por eso `pre-padding` lee muerto en el guard, adjudicado con esa medición. Acotar la regla toca el CSS del SITIO, que sirve a ~162 páginas: decisión aparte. - **El guard de claves duplicadas del handoff inspeccionaba el VACÍO.** El comando documentado usa `grep -oE "^\t…"`, y `grep -E` lee `\t` como una `t` literal: 0 coincidencias sobre 130 bloques reales, así que salía «vacío = OK» sin mirar nada — el antipatrón que el proyecto tiene registrado, dentro del propio handoff. Comprobado con un contador fiable: cero duplicados en el árbol, así que no hay daño en los commits que confiaron en él. CONTINUE queda corregido con una versión que CUENTA los bloques y avisa de que «0 bloques» significa guard roto, no fichero limpio. Artefactos y gates: - Sonda antes/después = 0 diffs en 1.305 valores × 7 estados (dos veces: tras la tokenización y tras la corrección del bundle). Determinismo verificado. - R-5.4: 19/20; `pre-padding` adjudicado con las siete propiedades medidas. - censo --only 81 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos de vuelta al único rojo conocido (skin-media-player) tras corregir el bundle · rtl:check 0 · docs:check 0 · check por fichero limpio. - README con su «Talla y tema» y tab `Tokens` en la demo (20 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'code-block': {
'pre-padding':
'the DEMO HARNESS outweighs the component: `[data-uix-docs] pre` (uix.css:801, 0-1-1) beats `[data-code-block-pre]` (0-1-0) and paints padding/border/radius/bg/font on the <pre>. Forced with !important → reaches (16px 20px → 12px). Seven properties measured, registered in next-features §13'
},
uix(tree-view): temable — 0 % → 92 %, y el velo que tiñe la carpeta entera 46 claves en el bloque `tree-view` de `recipes/base.ts` (fusionadas con el forward de paleta). Censo 0 % → 92 %, cero globales. Global 46 %, sin contrato 47 → 46. Molde del hermano `tree-grid` allí donde el problema es el mismo. LA PROPUESTA GENERADA FALLABA EN EL NOMBRE DE CASI TODO LO QUE IMPORTA: el clasificador toma el PRIMER selector de cada regla como si fuera la parte pintada, y aquí ese primero es el root o el branch-control cuando el nodo real es otro. 1. `root-width: 1px` / `root-bg` NO son del root: son la GUÍA de indentación (el `::before` del branch, una línea vertical). → `guide-width` + `guide-fg`, literalmente los nombres de tree-grid. Un `root-bg` que tiñe una línea es la clase de nombre que miente. 2. `branch-control-padding-inline-{k}` ⚠ fundía DOS ejes que se SUMAN en el mismo calc: el ritmo de la fila y el PASO DE PROFUNDIDAD (`--tree-depth × indent + padding`). Fundidos, tematizar la sangría habría movido el padding. → `row-padding-inline-{k}` + `indent-{k}`. 3. El prefijo `branch-control-` es erróneo: la regla reza `[data-tree-view-branch-control], [data-tree-view-item]` — las DOS filas. El CSS ya llamaba `row` a sus privados. → `row-*`, como tree-grid y como la lección de table. Y fuera el prefijo `root-` del chasis: el wrapper ES el componente. 4. `row-padding-block` ⚠ no eran dos valores en pugna sino cinco por talla: xs colapsa a `0`, las otras cuatro `--space-1`. 5. `branch-indicator-width` + `-height` son UN knob (`1em`) → `branch-indicator-size`. Bundle: la altura de fila casa 1:1; padding e indent llevan ritmo propio; la tipografía un paso por debajo desde lg — verbatim, como table y tree-grid. Los resueltos con `parts: ['root']` (el `data-size` va en `[data-tree-view-root]`, wrapper de eidos, no parte del morfo). Los `transparent` de outline/ghost y el `padding: 0` de ghost son identidad de variante y siguen literales. `hover-row-bg` SÍ se acuña, al revés que en grid-list, y por medición: la misma regla pinta dos nodos con arquetipo distinto, y `branch-control` NO lleva archetype — ahí el plano de la receta es la única pintura (velo medido ausente) y el token es toda la superficie de tema del hover. Tener un consumidor legítimo es lo que lo distingue del caso grid-list. EL DEFECTO QUE LA MEDICIÓN DESTAPÓ (preexistente, NO se toca aquí → next-features §12.8): al pasar el ratón por la fila de una carpeta abierta se tiñe LA CARPETA ENTERA, hijos incluidos. `archetype: 'item'` está declarado en el `<li>` branch (`morfo/components/tree-view.ts:103`), que contiene el control Y todo el `branch-content`, mientras la fila que el usuario señala es el `branch-control` de dentro: el nodo velado mide 336 px frente a los 36 px de la fila — medido y capturado. Un branch anidado acumula además el velo de sus ancestros, así que sus filas se ven más oscuras que las hermanas. Mover el arquetipo es morfo y mueve píxel en toda la demo. Es la TERCERA variante de la misma familia: table lo tiene en fila Y celda, grid-list en fila Y celda sobre el mismo nodo, y tree-view en un ANCESTRO del nodo señalado. Artefactos y gates: - Sonda antes/después = 0 diffs en 8.062 valores × 7 estados. - R-5.4: 45/46; `disabled-row-fg` adjudicado en el ledger (la demo monta CERO filas deshabilitadas, contadas; forzada alcanza oklch(0.61 0 0) → rgb(1,2,3)). - censo --only 92 % · component:audit PASS · eidos-lint invalid 0, class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio (72 globales, ninguno mío). - README con su «Talla y tema» y tab `Tokens` en la demo (46 claves, en vivo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
'tree-view': {
'disabled-row-fg':
'no disabled row in the demo (0 mounted, counted); forced data-disabled → reaches (oklch(0.61 0 0) → rgb(1,2,3))'
},
uix(password-field): temable — 41 % → 62 %, y el guard queda EN VERDE 7 claves (23 → 30). Censo password-field 41 % → **62 %**; el centinela pasa a verde con **las siete adjudicaciones medidas**, incluidas las tres que este componente arrastraba sin medir. Cosido: el tracking del input enmascarado —los puntos leen mejor algo sueltos—, el chip de bloq-mayús (radio y peso; su peso llevaba un `500` muerto detrás de un token que ya vale 500) y **los cuatro peldaños del medidor de fuerza**. LOS CUATRO PELDAÑOS SON UNA ESCALA DE INTENCIÓN, no cuatro colores sueltos: cada uno lee su rol —pérdida, riesgo, cumplimiento, afirmación— y ahora un tema puede recorrer la escala entera sin tocar los roles del sistema. De paso muere otra referencia con respaldo: el primer peldaño leía `var(--color-loss-solid, var(--color-threat-solid))` y el primitivo SÍ existe, así que el respaldo era ruido. LO QUE NO SE ACUÑA, Y ES COMPOSICIÓN: el `padding-inline` del medidor y el `font-size` de su rótulo leen privados de FIELD (`--_field-control-*`), porque este componente monta un Field. Se teman en la ficha del dueño — mandato de composición, `theming-audit` §5.3-3. Siete adjudicaciones, todas medidas: los cuatro peldaños sólo pintan bajo su `data-strength` y la demo arranca con el campo vacío (fuerza 0, ningún segmento activo); las dos de transición las congela el guard; y la opacidad deshabilitada vive en el disparador de visibilidad, no en la raíz — por eso mi primera medición dio «sin efecto» y hubo que buscar el nodo que la regla señala. Gates: sonda antes/después **0 diffs en 3.060 valores** (14 nodos × 8 estados) · centinela 23/30 con las siete adjudicadas, exit 0 · censo --only 62 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (campo, medidor de cuatro segmentos y rótulo en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'password-field': {
// The demo boots with an empty field: data-strength=0, so no segment is
// active and none of the four steps paints. Forced level by level
// 2026-08-22, transitions frozen.
'strength-1-bg': 'the meter sits at strength 0 (empty field); forced -> reaches (oklch(0.5787 0.1877 322.11) -> rgb(1,2,3))',
'strength-2-bg': 'same; forced -> reaches (oklch(0.6908 0.1909 45.02) -> rgb(1,2,3))',
'strength-3-bg': 'same; forced -> reaches (oklch(0.6406 0.1329 157.68) -> rgb(1,2,3))',
'strength-4-bg': 'same; forced -> reaches (oklch(0.6491 0.1136 181.96) -> rgb(1,2,3))',
// The visibility trigger owns both: the guard freezes transitions, and the
// demo boots enabled. Measured on it 2026-08-22.
'transition-duration': 'the guard freezes transitions; measured without the freeze on the visibility trigger -> reaches (0.12s -> 11.5s)',
'transition-ease': 'same freeze, same node -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(4))',
'disabled-opacity': 'only under [data-disabled] on the visibility trigger; forced -> reaches (0.4 -> 0.123)'
},
uix(context-menu): temable — 53 % → 90 %, y el instrumento aprende a abrir un menú contextual 11 claves (18 → 29). Censo context-menu 53 % → **90 %** con `global` a CERO. EL INSTRUMENTO NO SABÍA ABRIRLO. Un menú contextual se abre con clic DERECHO y con nada más, así que sonda y guard medían UN nodo: el disparador. Los dos aprenden el modo (`openBy: 'contextmenu'` en `COMPONENT_OVERRIDES`, clic derecho en la sonda) y pasan a ver **14 nodos** en el estado abierto; el guard, de un baremo ciego a **27/29**. Cosido el cromo propio del panel y del disparador: tipografía y tinta del contenido, tinta e interlineado de la fila, el galón del submenú (dos bordes girados 45°, un solo trazo), el encabezado de grupo (tinta, peso y tracking de versalitas) y el contorno DISCONTINUO que marca el disparador abierto — el estilo `dashed` es identidad de la afordancia; el grosor y el color son knobs. Dos limpiezas de paso: el `font-family` leía `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo— y el peso del encabezado llevaba un `500` muerto detrás de un token que ya vale 500. DOS ADJUDICACIONES MEDIDAS, y una es la TERCERA ocurrencia del mismo hallazgo: `content-font-family` no puede ganar porque el plano `overlay` impone la tipografía del panel (§12.9, pendiente de tu firma — ya medido en `popover` y en `float-panel`). La otra es el ítem deshabilitado, que la demo no monta: forzado, 0.55 → 0.123. Gates: sonda antes/después **0 diffs en 600 valores** (8 estados, 14 nodos en el abierto) · centinela 27/29 · censo --only 90 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del panel mirada (encabezado en versalitas, filas, casillas, galón del submenú y separadores en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'context-menu': {
// Third measurement of the same finding: the overlay DEPTH PLANE imposes the
// panel font and beats the recipe (section 12.9, pending signature, 20
// components). Writing the token moves nothing while the plane does.
'content-font-family': 'the overlay depth plane wins over the panel font - section 12.9, measured again 2026-08-22',
'item-disabled-opacity': 'no disabled item in the demo; forced data-disabled -> reaches (0.55 -> 0.123)'
},
metrics: {
// The demo's icon runs the `rounded` shape; the other two only paint under
// their own data-shape value. Forced in place 2026-08-22.
'icon-circle-radius': 'the demo icon is data-shape=rounded; forced circle -> reaches (9999px -> 1234px)',
'icon-square-radius': 'same; forced square -> reaches (6px -> 1234px)'
},
uix(float-panel): temable — 59 % → 84 %, muere un fantasma y queda una anomalía registrada 12 claves (28 → 40). Censo float-panel 59 % → **84 %** con `global` a CERO. Cosido: las dos VARIANTES que ajustan desde el default elevado (`outline-*`, `ghost-*`), los botones de su barra (`action-*`, `hover-action-*`) y el asa de redimensionado. MUERE UNO DE LOS NUEVE FANTASMAS DEL BARRIDO de esta mañana: el asa leía `var(--color-content-tertiary, var(--color-content-secondary))` y ese primitivo NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad pintaba, con lo que la referencia muerta desaparece sin mover un píxel. ONCE ADJUDICACIONES MEDIDAS: la demo monta la variante por DEFECTO —ni outline ni ghost— y no tiene botón de acción genérico, sólo el control de cierre; se forzó el atributo de variante y se montó el botón donde el CSS lo selecciona, y el radio del asa sólo pinta bajo `:focus-visible`. Las once alcanzan. Y UNA ANOMALÍA REGISTRADA EN VEZ DE ESCONDIDA: `resize-grip-fg` **mueve** bajo una réplica a mano de la secuencia del propio guard —congelar transiciones, blur, aparcar el ratón, escribir en `:root` y en cada nodo con el prefijo, fotografiar los mismos props con `::before`/`::after`/`::placeholder`— (oklch(0.5032 0 0) → rgb(1,2,3)), y aun así el guard lo da por muerto. Descartado que el nodo no lleve el atributo (lo lleva), que la regla necesite un estado (es la regla base), que sea el valor centinela (es el mismo) y que sea HMR (dos corridas). Queda adjudicado CON la medición y marcado como anomalía en §13: el token está vivo, lo que está en duda es la lectura. Es por donde debe empezar la revisión adversarial de este bloque — el precedente de F2-A dice que estos casos sí tienen causa. Lo rojo que queda son 12 tokens anteriores, y seis de ellos —`bg`, `border`, `border-width`, `shadow`, `font-family`, `line-height`— son el mismo conjunto que en `popover`: el PLANO de profundidad gana sobre el panel (§12.9, pendiente de firma). Gates: sonda antes/después **0 diffs en 1.890 valores** (9 nodos × 7 estados) · censo --only 84 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (cabecera, botón de cierre y asa de la esquina en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'float-panel': {
// The demo mounts the DEFAULT variant and no generic action button (only the
// close control). Forced in place 2026-08-22, transitions frozen.
'outline-bg': 'the demo runs the default variant; set data-variant=outline -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))',
'outline-border': 'same variant; forced -> reaches (oklch(0.7921 0 0) -> rgb(1,2,3))',
'ghost-bg': 'same, variant ghost; forced -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))',
'ghost-border': 'same, variant ghost; forced -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))',
'ghost-shadow': 'same, variant ghost; forced -> reaches (rgba(15,23,42,0.1) 0 4px 12px -> rgb(1,2,3) 0 0 0 7px)',
'action-fg': 'no generic action button in the demo (only the close control); mounted in place -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))',
'action-radius': 'same button; mounted -> reaches (4px -> 1234px)',
'action-font-size': 'same button; mounted -> reaches (16px -> 1234px)',
'hover-action-bg': 'same button, and only under :hover; same mounting pass',
'hover-action-fg': 'same button, and only under :hover; same mounting pass',
uix(theming): revisión adversarial del bloque — la pasada de HOVER envenenaba a todos los tokens de reposo Cinco hipótesis, un hallazgo real, cuatro refutadas. Bloque de CATORCE componentes; global del eje 56 % → **62 %**. HALLAZGO REAL — la anomalía de `float-panel` tenía causa, y era del guard. `resize-grip-fg` movía aislado y leía muerto en una corrida completa. Instrumenté el propio guard para probar UNA clave y salió `moved=true`; luego, en la corrida entera, muerto. La causa: **la pasada de HOVER deja el puntero sobre el último nodo que tocó**, y una regla `:hover` gana a la de reposo con la que comparte nodo — el asa re-apunta su color al acento al pasar el ratón. Es decir: **cualquier token de reposo probado DESPUÉS de un token `hover-*` podía leer muerto**. Es exactamente la clase que F2-A arregló con el clic-foco, una pasada más tarde: allí se aparcó el puntero tras ABRIR, aquí faltaba aparcarlo tras HOVERAR. Arreglado. Y entonces el propio guard delató DOS excepciones STALE — `float-panel.resize-grip-fg` y `media-player.track` —, retiradas: dos tokens que el ledger daba por muertos están vivos. Se confirma la regla de F2-A: **un falso negativo siempre tiene causa**; registrarlo como «sin causa» era el paso previo, no el final. REFUTADA «hay tokens huérfanos»: comprobación ESTRICTA —comentarios despojados, que es el hueco por el que `popover.bg` sobrevivió, sobre todo el CSS del árbol— de las **553 claves** de los catorce bloques: ninguna declarada sin que una regla la lea. REFUTADA «el cambio de instrumento rompió algo»: los **27 componentes con ledger re-verificados** tras aparcar el puntero — cero regresiones. Los tres rojos que quedan (`drawer` 24, `metrics` 16, `color-picker` 23) son ANTERIORES al bloque y están anotados con lo que ya se descartó de cada uno. REFUTADA «quedan fantasmas del barrido en lo tocado»: pasa de 9 a **8** — el `--color-content-tertiary` de float-panel murió con su costura. Los ocho restantes son de componentes que este bloque no tocó. CONFIRMADO, y ya estaba escrito: hay gates FINOS. `popover` mide 1 nodo en reposo (7 en abierto), `drawer` 1, `context-menu` 1 + 14 en abierto, `color-picker` 5 + 12. Cada veredicto lo dice, y lo que sostiene esas cifras es el forzado a mano de sus estados, no la sonda. Guards: `--names` sin desviadas · docs:check 0 · censo 62 % (2.877 públicos de 5.137 knobs; `global` 1.145 → 912). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'resize-grip-radius': 'only under :focus-visible; focused the grip -> reaches (4px -> 1234px)'
uix(float-panel): temable — 59 % → 84 %, muere un fantasma y queda una anomalía registrada 12 claves (28 → 40). Censo float-panel 59 % → **84 %** con `global` a CERO. Cosido: las dos VARIANTES que ajustan desde el default elevado (`outline-*`, `ghost-*`), los botones de su barra (`action-*`, `hover-action-*`) y el asa de redimensionado. MUERE UNO DE LOS NUEVE FANTASMAS DEL BARRIDO de esta mañana: el asa leía `var(--color-content-tertiary, var(--color-content-secondary))` y ese primitivo NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad pintaba, con lo que la referencia muerta desaparece sin mover un píxel. ONCE ADJUDICACIONES MEDIDAS: la demo monta la variante por DEFECTO —ni outline ni ghost— y no tiene botón de acción genérico, sólo el control de cierre; se forzó el atributo de variante y se montó el botón donde el CSS lo selecciona, y el radio del asa sólo pinta bajo `:focus-visible`. Las once alcanzan. Y UNA ANOMALÍA REGISTRADA EN VEZ DE ESCONDIDA: `resize-grip-fg` **mueve** bajo una réplica a mano de la secuencia del propio guard —congelar transiciones, blur, aparcar el ratón, escribir en `:root` y en cada nodo con el prefijo, fotografiar los mismos props con `::before`/`::after`/`::placeholder`— (oklch(0.5032 0 0) → rgb(1,2,3)), y aun así el guard lo da por muerto. Descartado que el nodo no lleve el atributo (lo lleva), que la regla necesite un estado (es la regla base), que sea el valor centinela (es el mismo) y que sea HMR (dos corridas). Queda adjudicado CON la medición y marcado como anomalía en §13: el token está vivo, lo que está en duda es la lectura. Es por donde debe empezar la revisión adversarial de este bloque — el precedente de F2-A dice que estos casos sí tienen causa. Lo rojo que queda son 12 tokens anteriores, y seis de ellos —`bg`, `border`, `border-width`, `shadow`, `font-family`, `line-height`— son el mismo conjunto que en `popover`: el PLANO de profundidad gana sobre el panel (§12.9, pendiente de firma). Gates: sonda antes/después **0 diffs en 1.890 valores** (9 nodos × 7 estados) · censo --only 84 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura mirada (cabecera, botón de cierre y asa de la esquina en su sitio). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
},
uix(drawer): temable — 57 % → 72 %, el disparador gemelo del de popover 10 claves (44 → 54). Censo drawer 57 % → **72 %**; global del eje 61 %. Mismo cromo que el disparador de `popover` y con la misma corrección: `min-block-size` lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo crudo que el CSS traía. Aquí no hay estado abierto que teñir, así que el hover lleva una sola clave. LO QUE QUEDA FUERA ES LA FIRMA DE SALIDA, y no se cose aquí: los cuatro `border-{side}: var(--border-width-thick) solid transparent` son la base que las reglas de `data-last-action` tiñen al cerrar. La misma matriz vive en `popover` y `dialog`, ya ha derivado entre las tres, y es mapeo transversal según la tabla morfo→eidos → §13. Los ocho privados son canales de valor (los `*-override` que el wrapper escribe desde sus props, y el par `panel-width/height` de la variante persistente) y los cinco literales, identidad. UNA ADJUDICACIÓN, MEDIDA, Y DE UNA CLASE NUEVA DEL INSTRUMENTO: el guard ABRE el cajón antes de probar, y entonces el velo tapa el disparador — la pasada de hover no puede posarse en él (Playwright se niega a hacer hover sobre un nodo que no recibe puntero). Con el cajón cerrado y hover a mano, `hover-trigger-bg` alcanza (oklch(0.931 0 0) → rgb(1,2,3)). Lo rojo que queda son **24 tokens anteriores** a este commit —el asa de arrastre, el anillo de arrastre, los anchos y altos por talla, los gaps de cabecera y pie—, todos de partes o estados que la demo no monta. Este commit no los empeora. Gates: sonda antes/después **0 diffs en 390 valores** (8 estados) · censo --only 72 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del escenario mirada (disparador con su cromo intacto). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
drawer: {
// The guard OPENS the drawer before testing, and the overlay then covers the
// trigger: the hover pass cannot reach it (Playwright refuses to hover a
// node that does not receive the pointer). Hovered by hand with the drawer
// closed 2026-08-22 -> reaches.
uix(drawer): los 24 rojos medidos uno a uno — y uno era un DEFECTO REAL: el velo no pinta Sin cambios de código: 24 adjudicaciones medidas y dos hallazgos. El guard queda en VERDE. EL VELO DEL CAJÓN NO PINTA. Su regla es background: color-mix(in srgb, var(--drawer-overlay-bg) var(--drawer-overlay-opacity), transparent) y **soma escribe `--drawer-overlay-opacity: 1` INLINE** en el nodo del velo — es su progreso de arrastre, un número sin unidad. `color-mix` exige ahí un PORCENTAJE: con `1` la función entera es inválida y el fondo cae a `transparent`. Medido: el velo abierto computa `rgba(0, 0, 0, 0)`; escribiendo `80%` en el propio nodo, pinta. Y VISTO en captura: el fondo tras el cajón sale desenfocado pero **sin atenuar** — el `backdrop-filter` funciona y el tinte no existe. Un cajón modal sin velo. No se arregla aquí porque mueve píxel y toca soma (§7.8). Puede arreglarse de un lado o del otro —que soma escriba un porcentaje, o que la receta no use ese slot como porcentaje—, y la lección general queda escrita en §13: **un valor que soma escribe inline y una receta consume tiene que compartir UNIDAD**, o el fallo es silencioso. SEGUNDO HALLAZGO: las tallas son canal de valor en el eje que manda. Soma escribe `width` (o `height`, según el lado) inline en el contenido, así que `content-width-{sm,md,lg}` no puede ganar desde `:root` en un cajón izquierda/derecha; el eje cruzado sí alcanza (`height-sm` forzado: 260px → 976px). Misma clase que el `item-gap` de carousel y el `preview-z` de drag-drop, que se retiraron — pero aquí el token sigue siendo la fuente del valor que soma resuelve, así que retirarlo o no es firma tuya. Los otros 22 son lo esperable, y quedan medidos con su valor: el asa —que la demo no monta y cuyo tamaño INTERCAMBIA ejes según el lado (36px→1234px de largo, 4px→1234px de grosor)—, el anillo de arrastre, el padding `full`, la cabecera y el pie opcionales, y la z de la variante inline. Gates: centinela 29/54 con las 24 adjudicadas, exit 0 · docs:check 0 · captura de la página con el cajón abierto mirada. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'hover-trigger-bg': 'the open drawer overlay covers the trigger, so the hover pass cannot land on it; hovered by hand with the drawer closed -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))',
// ── The 24 the component carried from before the axis, measured one by one
// 2026-08-22 (transitions frozen, drawer open, each state forced).
// The GRABBER is not mounted in this demo and its size swaps with the side:
// length is the inline axis for top/bottom and the block axis for left/right.
'handle-bg': 'no grabber in this demo; mounted in place -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))',
'active-handle-bg': 'same grabber, and only while dragging; forced -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))',
'handle-radius': 'same grabber; mounted -> reaches (9999px -> 1234px)',
'handle-length': 'same grabber, and only under side=top/bottom; forced -> reaches (36px -> 1234px)',
'handle-thickness': 'same grabber, same side; forced -> reaches (4px -> 1234px)',
'handle-active-scale': 'same grabber, only while dragging (transform, which the guard does not read)',
'handle-transition-duration': 'the guard freezes transitions; the grabber transitions background and transform on it',
'handle-transition-ease': 'same freeze, same pair',
// The dragging ring only exists while a pointer drags the panel.
'content-ring-dragging-fg': 'only under [data-dragging]; forced -> reaches (srgb 0.392 / 0.32 -> rgb(1,2,3))',
'content-ring-dragging-width': 'same state; forced -> reaches (2px -> 1234px)',
'content-ring-dragging-offset': 'same state, outline-offset (read set)',
'content-shadow-dragging': 'same state; forced -> the box-shadow changes',
// Sizes: the demo runs md on the right side, and soma writes the resolved
// axis INLINE (width for left/right, height for top/bottom), so the token of
// the axis in play cannot win from :root — a value channel, like carousel
// item-gap. The cross axis does reach: height-sm forced 260px -> 976px.
'content-width-sm': 'soma writes width INLINE on the content for a left/right drawer, so no cascade can beat it - value channel, registered in next-features 13',
'content-width-md': 'same inline width channel',
'content-width-lg': 'same inline width channel',
'content-height-sm': 'the demo runs a right drawer, where the height is not the axis in play; forced side=bottom -> reaches (260px -> 976px)',
'content-height-md': 'same cross axis, same pass',
'content-height-lg': 'same cross axis, same pass',
'content-padding-full': 'only under [data-size=full]; forced -> reaches (24px -> 1234px)',
// Header and footer are optional parts the demo does not render.
'header-gap': 'the demo mounts no header; mounted in place -> reaches (12px -> 1234px)',
'footer-gap': 'the demo mounts no footer; mounted in place -> reaches (12px -> 1234px)',
'footer-margin-top': 'same footer; mounted -> reaches (8px -> 1234px)',
'inline-z': 'only under [data-variant=inline]; forced -> reaches (50 -> 4321)',
// AND A REAL DEFECT, not an instrument limit: the veil paints NOTHING.
'overlay-bg': 'the veil paints nothing today: soma writes --drawer-overlay-opacity INLINE as the unitless 1 (its drag progress) and the CSS consumes it as a color-mix PERCENTAGE, so the mix is invalid and the background falls to transparent. Writing 80% on the node paints it. Registered in next-features 13 - it moves pixel, so it is not fixed here'
uix(drawer): temable — 57 % → 72 %, el disparador gemelo del de popover 10 claves (44 → 54). Censo drawer 57 % → **72 %**; global del eje 61 %. Mismo cromo que el disparador de `popover` y con la misma corrección: `min-block-size` lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo crudo que el CSS traía. Aquí no hay estado abierto que teñir, así que el hover lleva una sola clave. LO QUE QUEDA FUERA ES LA FIRMA DE SALIDA, y no se cose aquí: los cuatro `border-{side}: var(--border-width-thick) solid transparent` son la base que las reglas de `data-last-action` tiñen al cerrar. La misma matriz vive en `popover` y `dialog`, ya ha derivado entre las tres, y es mapeo transversal según la tabla morfo→eidos → §13. Los ocho privados son canales de valor (los `*-override` que el wrapper escribe desde sus props, y el par `panel-width/height` de la variante persistente) y los cinco literales, identidad. UNA ADJUDICACIÓN, MEDIDA, Y DE UNA CLASE NUEVA DEL INSTRUMENTO: el guard ABRE el cajón antes de probar, y entonces el velo tapa el disparador — la pasada de hover no puede posarse en él (Playwright se niega a hacer hover sobre un nodo que no recibe puntero). Con el cajón cerrado y hover a mano, `hover-trigger-bg` alcanza (oklch(0.931 0 0) → rgb(1,2,3)). Lo rojo que queda son **24 tokens anteriores** a este commit —el asa de arrastre, el anillo de arrastre, los anchos y altos por talla, los gaps de cabecera y pie—, todos de partes o estados que la demo no monta. Este commit no los empeora. Gates: sonda antes/después **0 diffs en 390 valores** (8 estados) · censo --only 72 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0 · captura del escenario mirada (disparador con su cromo intacto). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
},
uix(aura): temable — 43 % → 80 %, y doce silencios que son diseño 13 claves (24 → 37). Censo aura 43 % → **80 %**; global del eje 61 %. Su texto de estado y su detalle bajan UN paso del bundle por talla, y las TRES coordenadas viajan juntas (tamaño, interlineado, tracking): doce declaraciones, doce valores distintos, doce claves — 1:1, sin inflación. El valor sigue siendo del bundle; el knob pasa a ser del componente. La decimotercera es `idle-opacity`: el reposo no desaparece, se retira a media opacidad. Fuera por identidad: el `--radius-full` del anillo (redondo por definición), los cuatro `100%` del canvas y del SVG, y el `1px` × 2 de la región viva — la técnica sr-only, la misma de `text-blur`. DOCE ADJUDICACIONES, TODAS MEDIDAS, Y NINGUNA ES DEUDA: - **Las seis tintas de estado y el halo pintan el FONDO DEL INDICADOR**, y la demo corre con el canvas WebGL montado: la regla `[data-aura-indicator]:has(canvas)` pone `background: none` A PROPÓSITO — el orbe ES la presencia, y un disco detrás enturbia su silueta. Quitando el canvas en vivo, el gradiente vuelve y las siete siguen al token (oklch(0.6434 0 0) → rgb(1,2,3) dentro del radial-gradient). - **El arco y el halo son SLOTS del anillo que sólo existen en los estados de trabajo**; la demo se queda en la pista. Montados como `<circle data-aura-ring-slot>` dentro del SVG real —que es exactamente lo que el CSS selecciona—: 2px → 1234px, 1px → 1234px, 0.5 → 0.123. - Las dos de transición, congeladas por el guard como en todo el catálogo. Y una ceguera más del instrumento, la tercera de la sesión: **el guard no veía el SVG del anillo** porque sus `<circle>` no llevan atributo `data-aura*`. `extraNodes` los mete en el conjunto medido. Gates: sonda antes/después **0 diffs en 1.710 valores** (9 nodos × 7 estados) · centinela 25/37 con las doce adjudicadas · censo --only 80 % · `--names` sin desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check 0 · docs:check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
aura: {
// The six state tints and the halo paint the INDICATOR background, and the
// demo runs with the WebGL canvas mounted — the rule
// [data-aura-indicator]:has(canvas) sets background:none ON PURPOSE (the orb
// IS the presence; a disc behind it muddies the silhouette). Removed the
// canvas in place 2026-08-22: the gradient returns and all six follow.
'orb-color-idle': 'the canvas covers the disc by design; removed in place -> reaches (oklch(0.6434 0 0) -> rgb(1,2,3) inside the radial-gradient)',
'orb-color-offered': 'same disc, same pass',
'orb-color-planned': 'same disc, same pass',
'orb-color-reviewing': 'same disc, same pass',
'orb-color-acting': 'same disc, same pass',
'orb-color-escalated': 'same disc, same pass',
'orb-halo-alpha': 'the outer stop of the same gradient; removed the canvas -> the gradient changes',
// The arc and the halo are ring SLOTS that only exist in the working
// states; the demo sits on the track. Mounted as <circle data-aura-ring-slot>
// inside the real SVG 2026-08-22 — which is exactly what the CSS selects.
'ring-arc-width': 'the arc slot is not mounted in the idle demo; mounted in place -> reaches (2px -> 1234px)',
'ring-halo-width': 'the halo slot is not mounted; mounted in place -> reaches (1px -> 1234px)',
'ring-halo-opacity': 'same slot; mounted in place -> reaches (0.5 -> 0.123)',
'transition-duration': 'the guard freezes transitions on purpose; the orb tint and the arc stroke transition on it',
'transition-ease': 'same freeze, same pair of properties'
},
uix(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): los dos rojos que quedaban — y el eje `size` del picker MUERE EN EL PORTAL Medidos uno a uno los 18 de `metrics` y los 25 de `color-picker`, los últimos del bloque viejo que el guard R-5.4 arrastraba sin adjudicar. Los dos quedan en verde. Sin cambios de código: esto es medición y registro. `metrics`: los 18 están VIVOS. Ninguno miente; lo que faltaba era el escenario. Seis piden el paso `xxl` (la demo llega a `xl`), dos el icono SIN `data-color` (su defecto sólo pinta cuando no hay color, y todos los de la demo llevan uno), dos las formas `circle`/`square`, cuatro la fila de acciones, uno el gráfico en `end`, uno el borde de la variante `outline`, y dos el sello de sema durante el hold (`data-event-family=signal` + `phase=active`): el destello del valor sólo se mide estampando el sello a mano, porque cuando el guard lee ya se ha ido. `color-picker`: 23 de 25 vivos, y los otros dos son un DEFECTO REAL. EL EJE `size` MUERE EN EL PORTAL, dos veces. `--_color-picker-content-width` se declara TRES veces y no lo consume NADIE: la anchura del panel la fija `--_popover-content-width-override`, cableada al paso `md`. Un picker `sm` y uno `lg` abren el MISMO panel de 344 px, y `content-width-sm`/`-lg` son dos públicos que no mueven nada. Lo mismo con las muestras dentro del panel (`--_color-picker-swatch-size` clavado a `md`): fuera del portal los tres pasos alcanzan (20/24/28 px → 1234 px), dentro sólo el `md`. La causa es una y vale como ley: el portal es una FRONTERA DE CASCADA — un privado declarado en la raíz del componente no la cruza. Misma familia que la capa `list-surface` inalcanzable desde `:root`. El arreglo honesto es una escala por talla en el ámbito del CONTENIDO (que sí lleva su `data-size`); mueve píxel en `sm` y `lg`, así que es firma → §13. Tres cegueras del instrumento, medidas: - El guard CONGELA las transiciones antes de medir —el arreglo que hizo medibles radio-cards y compañía— y `transition-duration`/`transition-ease` SON la transición: sin congelar alcanzan (0.12s → 11.5s). Es genérico: cualquier componente con esos dos públicos los reportará muertos. - Una lectura inmediata de una propiedad EN TRANSICIÓN devuelve el valor VIEJO. Es lo que hacía ilegible el fondo del cuentagotas: ni con `!important` en línea se movía, porque la transición aún no había corrido. - La DEMO pisa el cuentagotas con su regla de ámbito (`.cp-tools-row` le pone `inline-size:auto` y `background`), así que sus dos tokens sólo se miden devolviéndole el consumo del token. Gates: `theming:sentinel metrics` y `color-picker` en verde (54/72 y 46/71, el resto adjudicado por escrito) · `check` sin errores propios · `docs:check` 0/0 · formato sin regresión (los tres ficheros ya venían avisados en HEAD). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
},
metrics: {
// Measured one by one 2026-08-23 (transitions frozen, each condition forced).
// Every one of the 16 is ALIVE: the demo mounts neither the xxl step, nor the
// icon without a data-color, nor the actions row, nor an end-placed chart.
'gap-xxl': 'the size sweep of the guard does not reach the xxl step; forced data-size=xxl -> reaches (12px -> 1234px)',
'icon-gap-xxl': 'same xxl step, read on the padding-inline-start of an icon-start root; forced -> reaches (92px -> 1306px)',
'icon-box-xxl': 'same xxl step; forced -> reaches (72px -> 1234px)',
'label-font-size-xxl': 'same xxl step; forced -> reaches (28px -> 1234px)',
'value-font-size-xxl': 'same xxl step; forced -> reaches (80px -> 1234px)',
'description-font-size-xxl': 'same xxl step; forced -> reaches (28px -> 1234px)',
'icon-border-width': 'only the outline variant draws a stroke and the demo runs soft; forced data-variant=outline -> reaches (1px -> 1234px)',
'icon-bg': 'the no-data-color default, and every demo icon carries a colour that overrides the hook; removed data-color -> reaches (oklch(0.9551 0 0) -> rgb(1,2,3))',
'icon-fg': 'same no-data-color default; removed data-color -> reaches (oklch(0.5032 0 0) -> rgb(1,2,3))',
'actions-gap': 'the actions row is conditional and the demo mounts none; mounted in place -> reaches (12px -> 1234px)',
'actions-margin-block-start': 'same row; mounted -> reaches (12px -> 1234px)',
'actions-padding-block-start': 'same row; mounted -> reaches (12px -> 1234px)',
'actions-border-fg': 'same row; mounted -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))',
'chart-right-width': 'only an end-placed chart reserves the gutter and the demo places them full; forced data-placement=end -> reaches (112px -> 1234px)',
'flash-duration': 'the flash rule needs the sema stamp of the hold (data-event-family=signal + phase=active), which is gone by the time the guard reads; stamped by hand -> reaches (0.32s -> 11.5s)',
'flash-ease': 'same stamp; forced -> reaches (cubic-bezier(0, 0, 0.2, 1) -> steps(7))',
'icon-circle-radius': 'the demo mounts only the rounded shape; forced data-shape=circle -> reaches (9999px -> 1234px)',
'icon-square-radius': 'same, the demo mounts only rounded; forced data-shape=square -> reaches (6px -> 1234px)'
},
'color-picker': {
// Measured one by one 2026-08-23 (picker open, transitions frozen unless the
// token IS the transition). 21 of the 23 are alive; the two width steps are a
// real reach defect, registered in next-features 13.
'content-width-sm': 'the private --_color-picker-content-width is declared three times and consumed ZERO times: the portalled panel hardwires calc(content-width-md + 2rem), so no sm step can reach it. Registered in next-features 13 - wiring or retiring it moves pixel',
'content-width-lg': 'same orphan private; the panel width has no size axis today. Registered in next-features 13',
'swatch-size-sm': 'inside the portalled content the private is pinned to swatch-size-md with no size variants; on a swatch outside the portal the step reaches (20px -> 1234px). Same entry in next-features 13',
'swatch-size-md': 'the demo mounts no swatch of any kind; mounted in place -> reaches (24px -> 1234px)',
'swatch-size-lg': 'same pinning inside the portal; outside it reaches (28px -> 1234px)',
'swatch-radius': 'presets and saved swatches are conditional and the demo mounts none; mounted -> reaches (4px -> 1234px)',
'swatch-border': 'same swatches; mounted -> reaches (oklch(0.931 0 0) -> rgb(1,2,3))',
'swatch-checked-ring': 'same swatches, and only under [data-checked]; mounted and checked -> reaches (the 3px ring turns rgb(1,2,3))',
'swatch-gap': 'the swatch group is conditional and the demo mounts none; mounted -> reaches (6px -> 1234px)',
'swatch-border-width': 'same swatches; mounted -> reaches (1px -> 1234px)',
'swatch-ring-gap': 'same swatches, and only under [data-checked] or :focus-visible; mounted and checked -> reaches (the 1px inner ring turns rgb(1,2,3))',
'transparency-cell-alt': 'the standalone transparency grid utility is not mounted in the demo; mounted -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))',
'eye-dropper-size': 'the DEMO route overrides the eye dropper with its own scoped rule (.cp-tools-row sets inline-size:auto + aspect-ratio); with the token consumption restored -> reaches (30px -> 1234px)',
'eye-dropper-bg': 'same demo override (background:transparent); restored and frozen -> reaches (transparent -> rgb(1,2,3))',
'hue-0': 'the rainbow ramp needs data-x-channel=hue and the demo area runs saturation/brightness; forced -> reaches (rgb(255,0,0) -> rgb(1,2,3))',
'hue-60': 'same hue ramp; forced data-x-channel=hue -> reaches',
'hue-120': 'same hue ramp; forced data-x-channel=hue -> reaches',
'hue-180': 'same hue ramp; forced data-x-channel=hue -> reaches',
'hue-240': 'same hue ramp; forced data-x-channel=hue -> reaches',
'hue-300': 'same hue ramp; forced data-x-channel=hue -> reaches',
'hue-360': 'same hue ramp; forced data-x-channel=hue -> reaches',
'saturation-floor': 'the ::before ramp needs x-channel=hue AND y-channel=saturation; forced -> reaches (rgb(128,128,128) -> rgb(1,2,3))',
'transition-duration': 'the guard freezes transitions before measuring (the forced-measurement fix) and these two tokens ARE the transition; measured unfrozen on the eye dropper -> reaches (0.12s -> 11.5s)',
'transition-ease': 'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))',
'disabled-opacity': 'no disabled trigger or eye dropper in the demo; forced data-disabled -> reaches (0.4 -> 0.137)'
uix(menubar): temable — 15 % → 88 %, y el plano `overlay` GANA a la receta 30 claves nuevas (5 → 35): la barra (superficie, marco, radio —uno solo, la receta lo desacopla de la talla a propósito y el motor de forma deriva el del disparador de él— y las tres escalas `gap`/`padding`/`height`), el disparador (tinta, tipografía, escala de `font-size` y de `padding-inline`, el par de la entrada ABIERTA y la tinta deshabilitada) y el Panel `role=dialog` (tinta + escala). Global 62 % → 63 %. Los pasos leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`), no el primitivo crudo que el CSS venía consumiendo; y el `calc(var(--space-1) * 0.5)` del paso `sm` es `var(--space-0-5)` exacto. EL HUECO DEL PANEL DEJA DE REPETIR EL VALOR DEL PADDING. Las tres reglas de `--floating-gap` compensan el sangrado del disparador dentro de la barra con lo que era una COPIA del padding; ahora leen `--menubar-padding-{k}`, su fuente — si un tema ensancha el padding, el panel deja de quedar a ras de la barra. No pueden leer el privado `--_menubar-padding`: esas superficies viajan POR PORTAL y un privado declarado en la raíz no cruza esa frontera. ⚠ EL PLANO `overlay` GANA A LA RECETA — cuarta prueba de §12.9 y la primera con MECANISMO. Acuñé `panel-font-family` y `panel-line-height` sobre declaraciones que la receta YA tenía, y el centinela las dio muertas. Medido: `[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y más tarde en la cascada, así que gana; del mismo bloque, `color` y `font-size` —que el plano no declara— sí alcanzan. Retiradas del contrato y devueltas a su fuente literal, con la razón escrita en el CSS. Mientras §12.9 no se firme, un token de tipografía en CUALQUIER superficie `overlay` miente, y son veinte. EL HOVER NO SE ACUÑA: ES DEL EJE navigation-menu. `background: var(--color-surface-overlay)` sobre el disparador mata la capa del sistema —medido: `background-image` computa `none` en hover con `--state-hover` vivo—, por las dos razones que `navigation-menu.css` ya documenta al retirar esta MISMA regla: el shorthand resetea `background-image`, y la regla del arquetipo va en `:where()`, especificidad CERO. Acuñar un `hover-trigger-bg` habría fosilizado la invención. Instrumento: la barra suelta DOS superficies y el Panel —el que lleva sus knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`) y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó `openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`, que aquí no existe, así que volvía a pulsar el disparador antes de cada token y CERRABA el panel — media corrida contra una superficie cerrada. Gates: sonda 0 diffs en 1.470 valores × 8 estados · centinela 34/35 (la deshabilitada, adjudicada) · censo 88 % · `--names` 0 desviadas · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el rojo conocido ajeno (`skin-media-player`) · `check` sin errores propios · capturas de barra, tallas sm/lg, menú abierto y panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
},
menubar: {
'disabled-trigger-fg': 'no disabled entry in the demo bar; forced data-disabled 2026-08-23 -> reaches (oklch(0.61 0 0) -> rgb(1,2,3))'
uix(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
}
};

Powered by TurnKey Linux.