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-probe.ts

355 lines
14 KiB

docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* __theming-probe — the before/after computed-style probe of PLAN-theming §7.2/§7.4.
*
* node scripts/__theming-probe.ts <component> <out.json> [url]
*
* Temporary instrument (the `__` prefix keeps it out of the tracked surface).
* Walks the component demo, and for every node matching `[data-{c}…]` records
* the knob computed values at rest, per size, hovered and focused. The size is
* forced by WRITING `data-size` on the nodes that already carry it — the recipe
* selects on that attribute, so the computed result is the same one the real
* interaction produces, without depending on each demo's chips.
*
* Run it with plain `node`, NOT with tsx: the tsx loader injects helpers that
* are not defined inside `page.evaluate`, and every probe throws.
*
* Must run from the repo ROOT (playwright does not resolve from the scratchpad)
* and against a live dev server; a hidden screen freezes rAF, so headless is the
* reliable surface.
*/
import { writeFileSync } from 'node:fs';
import { chromium } from 'playwright';
const PROPS = [
'backgroundColor',
'backgroundImage',
'color',
'borderTopColor',
'borderTopWidth',
'borderTopLeftRadius',
'borderBottomRightRadius',
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft',
'rowGap',
'columnGap',
'fontSize',
'fontWeight',
'fontFamily',
'lineHeight',
'letterSpacing',
'blockSize',
'inlineSize',
'minBlockSize',
'minInlineSize',
'boxShadow',
'opacity',
'fill',
'stroke',
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'strokeWidth',
'strokeOpacity',
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'outlineColor',
'outlineWidth',
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'filter',
'backdropFilter'
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
];
const SIZES = ['xs', 'sm', 'md', 'lg', 'xl'];
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* Demo controls that must be ON before measuring: a part the demo does not
* mount is a part the diff never compares, and a probe over two nodes passes
* in false (the F2-B lesson — count the nodes before believing a green gate).
* text-gradient boots with `showBorder=false`, so its whole frame — three of
* its seven tokens — is absent from the default stage.
*/
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
/**
* Components whose PARTS are not tagged `data-{c}-*`: prose styles bare HTML
* (h1, code, table…) through `:where([data-prose] el)`, so the attribute filter
* below sees exactly ONE node and the gate proves nothing. The selector's
* matches join the measured set.
*/
const EXTRA_NODES: Record<string, string> = {
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
prose: '[data-prose] *',
uix(audio-player): temable — 37 % → 56 %, y el instrumento medía CERO nodos 3 claves nuevas (5 → 8) y un literal anotado. El 44 % restante es PRÉSTAMO CON DUEÑO, no deuda: ocho filas que leen `--media-player-*` porque este componente ES la piel de audio de ese chasis, y una ficha nunca propone duplicar un token prestado con prefijo propio (§5.3-3). Acuñado lo único que ESTE reproductor añade sobre el chasis: el peso del título y la insignia LIVE en versalitas espaciadas (peso + tracking). Los dos `600` pasan a `var(--font-weight-semibold)`: el mismo valor, con nombre. Y EL INSTRUMENTO ESTABA CIEGO DEL TODO: MEDÍA 0 NODOS. Tres razones encadenadas: 1. `/uix/components/audio-player` es un 404 — el CUARTO canónico sin ruta propia (picker-shell, mockup, surface y éste). Se mide en la demo del chasis. 2. Sus partes no llevan `data-audio-player-*`: son `data-media-player-*`. Con `extraNodes`, la sonda pasa de 0 a 48 NODOS. 3. Sólo existe tras el chip `media: audio` de esa demo: sonda (`DEMO_VARIANTS`) y guard (`openWith` + `openMarker`) lo pulsan ahora, y el guard barre además las cuatro variantes (card/row/bar/inline). Con eso, 0/8 → 6/8. Las dos que quedan son la insignia LIVE, que sólo existe mientras el proveedor refleja un directo: montada con `[data-live]`, alcanza (600 → 333 y 1,28px → 7px). Gates: sonda con 48 nodos (antes 0) · centinela 6/8 con las dos adjudicadas · censo 56 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · equivalencia comprobada en el computado (título 600, tracking 1,28px). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Its parts carry the MEDIA-PLAYER attrs (it is the audio skin of that
// chassis, not a component with its own part names): the `data-audio-player`
// filter matched ZERO nodes.
'audio-player': '[data-media-player][data-variant], [data-media-player][data-variant] *',
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA 7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en una regla inválida. Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el propio comentario del CSS dice que el hover usa «el mismo color que el anillo de selección, más claro» y lo que los separa es la opacidad, que ya tenía token—, la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos piezas del glifo `+`→`×`. Dos limpiezas de paso: un `var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)` con un respaldo que no puede ocurrir. Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN un atenuado (sin atenuar es 1 por definición) y la caja del icono. ⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el `70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL. Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa —la lección estaba escrita y se repitió—. → §13. Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el motor de color, como los rellenos de cada sector. No es superficie de tema. TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas: 1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir» lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya casa, no se pulsa nada (8 → 14 de golpe). 2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden con `extraNodes` — 37 nodos en reposo contra los 5 con atributo. 3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO: `rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la animación congelada computa 0,45 y sigue al token. → §13. Los cuatro tokens del rim son del look `luminous`, que la demo del componente no renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo que forzar): medidos en `/active/docs/agnt`, que sí lo usa. Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19 con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · capturas de reposo, sector en hover y disparador. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Its SVG innards hook on CLASSES, not on `data-onion-*`: without this the
// filter sees the five attribute nodes and misses every sector, label, icon
// and glyph — the whole surface the recipe paints.
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'onion-menu': '.onion-menu-sector, .onion-menu-label, .onion-menu-icon, .onion-menu-icon svg, .onion-menu-trigger, .onion-menu-trigger-glyph',
// Its mega-menu rows are the CONSUMER's bare `<a>` (the recipe styles them
// through `[data-navigation-menu-content] :is(a, …)`), so the attribute
// filter saw 10 nodes and NONE of them was the panel or a row — 9 of its 19
// knobs were outside the diff. They only exist while the panel is open, so
// the `open` pass below honours this selector too.
uix(waveform): temable — 55 % → 86 %, y el playhead era el ÚNICO primitivo crudo UNA clave nueva, cuatro literales firmados, un préstamo adjudicado. Diff de computed 0 sobre 1.600 valores en 7 estados; R-5.4 11/11, CERO adjudicaciones. LA CLAVE NUEVA NO LA PIDIÓ EL CENSO, LA PIDIÓ EL FICHERO `playhead-bg`. Todas las demás líneas del re-tinte del Slider embebido leen ya un token propio (--slider-secondary-bg: var(--waveform-buffered-fg)) o una identidad (transparent / none / 0px / 1); el thumb era la ÚNICA que pasaba un primitivo CRUDO (--color-content-primary) a un componente compuesto. Con eso, repintar el playhead exigía mover --slider-thumb-bg (todos los sliders de la app) o --color-content-primary (todo). Ahora es el par natural de `playhead-width`, que ya existía. El molde es el re-tinte de media-player, que reenvía --media-player-* en cada línea. El censo no lo veía: una custom property reenviada a un componente compuesto no entra en KNOB_PROPS (D-TH.2). La clase entera queda anotada en §13 — toda receta que re-tinta un embebido puede pasarle primitivos crudos sin que nada lo vea. EL 14 % QUE QUEDA ES UNA FILA, Y ES DOCTRINA La onda deshabilitada lee var(--slider-disabled-opacity). La prueba de si un token es tuyo no es qué valor tiene sino QUÉ DECIDE: aquí decide cuánto se apaga un control deshabilitado, y eso es del Slider — la onda vive fuera de su caja pero es la misma superficie a la vista. Un --waveform-disabled-opacity daría un segundo mando para UNA decisión y permitiría desincronizar cromo y pintura. Préstamo con dueño: se tema en la ficha de slider, como el 82 % de time-picker se tema en la de field. LOS CUATRO LITERALES SON IDENTIDAD, Y AHORA LO DICEN La pintura ES la caja del provider (100% sobre un position:absolute; inset:0), el área de arrastre ES la caja entera («scrubbing means grabbing anywhere on it»), y un playhead abarca todo el alto por definición. Anotación /* literal: */ en su declaración: desviación firmada, fuera del ratio. EL INSTRUMENTO, CIEGO POR UNA CAUSA NUEVA: EL COMPONENTE COMPUESTO La sonda medía 4 nodos —provider, svg y los dos paths— y NINGUNO era el playhead: el cromo entero es un Slider embebido cuyos nodos llevan data-slider*. Con extraNodes, 8. Es la TERCERA cara del mismo agujero: HTML crudo (prose), partes que compone el consumidor (navigation-menu) y partes de un componente compuesto (waveform). En §13, con la pregunta de fondo: hoy hay que sospecharlo y contar los nodos, el instrumento no lo detecta solo. VERIFICACIÓN Sonda antes/después 0 diffs (1.600 valores · 7 estados · 8 nodos) · R-5.4 11/11 sin adjudicaciones · component:audit PASS · censo 86 % con la única fila restante explicada · eidos-lint 4 morfo-backed / 16 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · Chrome: playhead 2px × 40px, onda 544 × 40, trazo 0.6 — idénticos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'navigation-menu': '[data-navigation-menu-content] a',
// Its interaction chrome IS an embedded canonical Slider that the recipe
// re-tints (the thumb becomes the playhead): those nodes carry
// `data-slider*`, so the attribute filter saw 4 nodes and none of them was
// the playhead the recipe paints.
waveform: '[data-waveform] [data-slider], [data-waveform] [data-slider] *'
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
};
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
/**
* The trigger the OPEN pass must click. The default is the first trigger in the
* document, which is wrong whenever the surface worth measuring hangs off
* ANOTHER one: menubar drops a role=menu Content from its first entry and its
* own role=dialog Panel — the part that carries its public knobs — only from
* the "Format" entry.
*/
const OPEN_TRIGGER: Record<string, string> = {
uix(color-swatch): temable — 70 % → 100 %, y el color del chip NO es un knob Cuatro claves nuevas por una retirada (12 → 14 en el contrato), dos adjudicaciones medidas. Diff de computed 0 sobre 832 valores en 15 estados y DOS rutas; capturas 2× byte a byte idénticas. LA §4 PEDÍA UN NOMBRE Y LO QUE FALTABA ERA UNA ESCALA La ficha marcaba `radius` con ⚠ y tres valores en colisión. No es una colisión: es el eje `rounded` con cuatro pasos, y la clave que ya estaba en el contrato ERA el paso `sm` bajo un nombre que no lo dice. Contado en base.ts: siete recetas escriben `radius-{k}`, y una es `badge` — el gemelo que lee ESTE MISMO `data-rounded` con el mismo conmutador privado. Así que entra la escala entera (radius-sm/md/lg/full) más `--_color-swatch-radius`, exactamente la forma que su propio eje `size` usaba dos líneas más arriba. Dejar `radius` al lado de `radius-md` habría dejado el paso `sm` llamándose `radius`: un nombre que miente. `fill` SALE DEL CONTRATO: ES UN CANAL DE VALOR, NO UN KNOB DE TEMA El wrapper escribe `--color-swatch-fill: {color}` INLINE en cada render desde una prop obligatoria. Medido sobre un chip real con transiciones congeladas: escribir el token en :root deja el ::before en rgb(124, 58, 237) — la declaración inline gana siempre. Un público encima sólo podía mentir. La forma correcta ya estaba escrita en el guard de la capa: recipe-css-contract sanciona `var(--x, default)` para las vars que el runtime escribe («el respaldo ES el default»), que es justo lo que la receta tenía. Por eso el respaldo NO se retira: al salir del contrato deja de ser código muerto y pasa a ser el default. ⚠ EL CENTINELA DABA `fill` POR VIVO — ES UN FALSO POSITIVO SUYO El guard escribe el token en :root Y en el `style` inline de cada nodo del componente, que es exactamente donde vive el canal de valor: lo pisa y lee «mueve». Es la cara opuesta de drag-drop.preview-z, donde la adjudicación se había medido sobre un nodo fabricado. Regla: un token que el componente escribe INLINE no se puede juzgar con el guard — se mide a mano escribiendo sólo en :root. EL INSTRUMENTO ESTABA CIEGO, DOS VECES 1. color-swatch NO TIENE RUTA DE DEMO (404): es el QUINTO canónico en ese estado, tras picker-shell, mockup, surface y audio-player. La sonda medía 1 nodo. Se mide donde de verdad se renderiza — gradient-builder (3 chips en el stage) y color-picker (el del trigger). 2. El eje `rounded` no lo monta NADIE: ningún consumidor del repo pasa la prop, así que los tres pasos que no son el default no tenían nodo que pintar. El guard los barre con sweepAttr: data-rounded, como meter hace con su shape. LAS DOS ADJUDICACIONES SON EL ANILLO OPT-IN border-width y border-fg: `bordered` nace apagado y ningún consumidor lo enciende, así que su ::after no existe en ninguna página. Forzado data-bordered sobre un chip real, los dos llegan (inset 0 0 0 1px → 1234px; oklch(0.931 0 0) → rgb(1, 2, 3)). ARTEFACTOS Sonda antes/después en dos rutas (256 valores × 8 estados en color-picker, 576 × 7 en gradient-builder) → 0 diffs, 0 nodos perdidos · el eje `rounded` medido a mano forzando el atributo: 4 / 4 / 6 / 10 / 9999 px antes y después · capturas 2× idénticas byte a byte · R-5.4 12/14 con las dos adjudicadas, exit 0 · censo --only 70 % → 100 % (knobs 10 → 7: los tres border-radius por variante pasan a declarar el privado) · component:audit PASS · eidos-lint 2 morfo-backed / 8 eidos-only / 0 invalid · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) · npm run check COMPLETED sin errores en ficheros tocados. LO QUE NO ENTRA Y POR QUÉ - Paso 9 del protocolo (pestaña Tokens en la demo): no hay demo que tocar. - docs/audit/theming/README.md: es un agregado global y otra sesión tiene form y progress en vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de base.ts, generated/base.css, theming-sentinel.ts y el ledger sólo entran mis hunks (verificado con git diff --cached). Defectos anotados, no arreglados (mueven documentación ajena a este cambio): la cabecera del CSS, el docblock del .svelte y types.ts siguen diciendo que la talla sale de `--icon-size-*`, cuando desde la extracción tiene escala propia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
menubar: "[data-menubar-trigger][data-menubar-value='format']",
// It has NO route of its own (`/uix/components/color-swatch` is a 404, the
// fifth canon component in that state): it is measured on the color-picker
// demo, whose stage mounts exactly ONE swatch (the trigger chip). Every other
// swatch — presets and saved swatches, a dozen of them — lives in the
// PORTALED panel, so the open pass is where the node count stops being 1.
'color-swatch': '[data-color-picker-trigger]'
uix(menubar): temable — 15 % → 88 %, y el plano `overlay` GANA a la receta 30 claves nuevas (5 → 35): la barra (superficie, marco, radio —uno solo, la receta lo desacopla de la talla a propósito y el motor de forma deriva el del disparador de él— y las tres escalas `gap`/`padding`/`height`), el disparador (tinta, tipografía, escala de `font-size` y de `padding-inline`, el par de la entrada ABIERTA y la tinta deshabilitada) y el Panel `role=dialog` (tinta + escala). Global 62 % → 63 %. Los pasos leen el BUNDLE (`--size-{k}-control-height` / `--size-{k}-font-size`), no el primitivo crudo que el CSS venía consumiendo; y el `calc(var(--space-1) * 0.5)` del paso `sm` es `var(--space-0-5)` exacto. EL HUECO DEL PANEL DEJA DE REPETIR EL VALOR DEL PADDING. Las tres reglas de `--floating-gap` compensan el sangrado del disparador dentro de la barra con lo que era una COPIA del padding; ahora leen `--menubar-padding-{k}`, su fuente — si un tema ensancha el padding, el panel deja de quedar a ras de la barra. No pueden leer el privado `--_menubar-padding`: esas superficies viajan POR PORTAL y un privado declarado en la raíz no cruza esa frontera. ⚠ EL PLANO `overlay` GANA A LA RECETA — cuarta prueba de §12.9 y la primera con MECANISMO. Acuñé `panel-font-family` y `panel-line-height` sobre declaraciones que la receta YA tenía, y el centinela las dio muertas. Medido: `[data-depth='overlay']` declara las dos con la MISMA especificidad (0,1,0) y más tarde en la cascada, así que gana; del mismo bloque, `color` y `font-size` —que el plano no declara— sí alcanzan. Retiradas del contrato y devueltas a su fuente literal, con la razón escrita en el CSS. Mientras §12.9 no se firme, un token de tipografía en CUALQUIER superficie `overlay` miente, y son veinte. EL HOVER NO SE ACUÑA: ES DEL EJE navigation-menu. `background: var(--color-surface-overlay)` sobre el disparador mata la capa del sistema —medido: `background-image` computa `none` en hover con `--state-hover` vivo—, por las dos razones que `navigation-menu.css` ya documenta al retirar esta MISMA regla: el shorthand resetea `background-image`, y la regla del arquetipo va en `:where()`, especificidad CERO. Acuñar un `hover-trigger-bg` habría fosilizado la invención. Instrumento: la barra suelta DOS superficies y el Panel —el que lleva sus knobs— cuelga de la entrada «Format», no de la primera; sonda (`OPEN_TRIGGER`) y guard (`openWith`) aprenden a abrir la que toca. Y el guard necesitó `openMarker`: su comprobación de «ya está abierto» busca `[data-{c}-content]`, que aquí no existe, así que volvía a pulsar el disparador antes de cada token y CERRABA el panel — media corrida contra una superficie cerrada. Gates: sonda 0 diffs en 1.470 valores × 8 estados · centinela 34/35 (la deshabilitada, adjudicada) · censo 88 % · `--names` 0 desviadas · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 · suite eidos con el rojo conocido ajeno (`skin-media-player`) · `check` sin errores propios · capturas de barra, tallas sm/lg, menú abierto y panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
};
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const DEMO_VARIANTS: Record<string, string[]> = {
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela **83/88** con 5 adjudicadas. El default NO se mueve. ## Lo primero: el instrumento no veía el contrato La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el `return {`, un tabulador más adentro. Consecuencia medida: - el censo la leía como «sin entrada en base.ts» — 84 claves invisibles; - el centinela **moría** con `no recipe block for avatar`: el componente no se podía medir, y su gate no significaba nada. Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo lo demás de este commit habría sido humo. ## Lo que entra al contrato (6 claves) `group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`. Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por `--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 · −16,8 · −22,4 · −33,6 px → 37 px, uno a uno). ## Lo que se retira (2 declaraciones muertas, diff 0 las dos) - `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta declaraba su default `99`. **No lo leía nadie** — el tope se aplica con `data-has-max` + `:nth-child(n + M)` porque una variable no entra en `:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos sitios; el `+3` del grupo sigue exactamente donde estaba. - el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara `--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue computando `rgb(255, 255, 255)`. ## Seis literales firmados (salen del ratio) Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo centrado por la caja flex. ## Lo que se queda privado (el techo real es 90 %) `--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian de fuente con la variante (solid · soft · outline) y su valor sale del forward de paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`. Un público encima dejaría que un tema los fijara y matara el `color=` de cada avatar. ## Artefactos - Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576 valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072). - Capturas 2× antes/después de las dos demos: **idénticas al byte**. - Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y `font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza — el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas). - El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de `data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en su propia ruta (21 muertos → 5). - `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED sin un solo error en `avatar`. - README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador con `networkidle` y clic real). ## Lo que NO se arregla aquí (anotado) - **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que renombrarlas cambia el contrato público de tres canales: se lista, el audit sigue en NEEDS-WORK por esto y sólo por esto. - **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token en `:root` y también sobre cada nodo, y para una propiedad personalizada que la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo. - **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE las seis excepciones de `metrics`, así que se hace con la re-verificación del ledger entero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Its badge is OPT-IN and boots in DOT mode (no text frame), and its ring is
// off: the probe saw 2 nodes — the portrait and its image — and neither the
// chip nor the halo the recipe paints.
avatar: [
'[data-uix-control]:has([data-uix-control-label]:text-is("show badge")) input[type=checkbox]',
'[data-uix-control]:has([data-uix-control-label]:text-is("dot")) input[type=checkbox]',
'[data-uix-control]:has([data-uix-control-label]:text-is("ring")) [data-uix-chip]:text-is("solid")'
],
uix(audio-player): temable — 37 % → 56 %, y el instrumento medía CERO nodos 3 claves nuevas (5 → 8) y un literal anotado. El 44 % restante es PRÉSTAMO CON DUEÑO, no deuda: ocho filas que leen `--media-player-*` porque este componente ES la piel de audio de ese chasis, y una ficha nunca propone duplicar un token prestado con prefijo propio (§5.3-3). Acuñado lo único que ESTE reproductor añade sobre el chasis: el peso del título y la insignia LIVE en versalitas espaciadas (peso + tracking). Los dos `600` pasan a `var(--font-weight-semibold)`: el mismo valor, con nombre. Y EL INSTRUMENTO ESTABA CIEGO DEL TODO: MEDÍA 0 NODOS. Tres razones encadenadas: 1. `/uix/components/audio-player` es un 404 — el CUARTO canónico sin ruta propia (picker-shell, mockup, surface y éste). Se mide en la demo del chasis. 2. Sus partes no llevan `data-audio-player-*`: son `data-media-player-*`. Con `extraNodes`, la sonda pasa de 0 a 48 NODOS. 3. Sólo existe tras el chip `media: audio` de esa demo: sonda (`DEMO_VARIANTS`) y guard (`openWith` + `openMarker`) lo pulsan ahora, y el guard barre además las cuatro variantes (card/row/bar/inline). Con eso, 0/8 → 6/8. Las dos que quedan son la insignia LIVE, que sólo existe mientras el proveedor refleja un directo: montada con `[data-live]`, alcanza (600 → 333 y 1,28px → 7px). Gates: sonda con 48 nodos (antes 0) · centinela 6/8 con las dos adjudicadas · censo 56 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · equivalencia comprobada en el computado (título 600, tracking 1,28px). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// It renders only in the media-player demo, after the `media: audio` chip.
'audio-player': [
'[data-uix-control]:has([data-uix-control-label]:text-is("media")) [data-uix-chip]:text-is("audio")'
],
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'text-gradient': [
'[data-uix-control]:has([data-uix-control-label]:text-is("showBorder")) [data-uix-chip]:text-is("true")'
uix(badge): temable — 59 % → 75 %, y subir un privado al contrato lo somete a reglas nuevas Seis claves nuevas (65 → 71), dos literales firmados, cinco privados adjudicados. Diff de computed 0 sobre 992 valores en 7 estados. LA ALTURA DEL CHIP ERA EL HUECO Badge monta su altura en la escala de CONTROL a propósito —para que `md` signifique lo mismo lleve o no un control dentro (el ✕ removible es un IconButton: medido, el mismo badge md daba 30px sin él y 46px con él)— pero lo hacía desde un privado con cuatro fuentes por talla que leía --control-height-* A PELO. Eso es una ESCALA, no un conmutador: sube al TSC como min-block-size-{xs,sm,md,lg} + el resuelto, el molde que sus vecinas padding-inline / gap / font-size ya tenían. Y AL ENTRAR EN EL CONTRATO, LA SUITE LO CORRIGIÓ El valor verbatim del CSS era el primitivo CRUDO (--control-height-{k}) y recipe-css-contract exige la coordenada del BUNDLE (--size-{k}-control-height, theming §5). Es la trampa que el handoff nombra —«la propuesta hereda el incumplimiento del CSS de partida»— y que ya mordió a code-block: SUBIR UN PRIVADO AL CONTRATO SOMETE SU VALOR A REGLAS QUE EN EL CSS NO SE LE APLICABAN. Corregido; el diff sigue en 0. dot-radius ES EL PAR QUE FALTABA Redondo es el DEFECTO, no la definición: knob.indicator-radius vale --radius-sm, así que el catálogo ya trata el radio de un marcador como knob. Los dos 100% del glifo son identidad (el icono ES su ranura), firmados. LOS CINCO PRIVADOS SON LAS TRES CLASES DE F2-B EN UN SOLO COMPONENTE --_badge-bg / --_badge-fg son un CONMUTADOR (cambian de fuente con la variante: track · solid · transparent, y aplanarlos obligaría a duplicar cada regla); --_badge-fill-finish es el canal que el generador de degradados deriva de la paleta de ESTA instancia; y --_badge-palette-* son forwards THM-2 que la capa de color alimenta por instancia — un público encima dejaría que un tema los fijara y matara el color= de cada chip. LAS 40 CLAVES DE TONO NO MUEVEN NADA, Y YA SABÍAMOS POR QUÉ El bloque genérico [data-badge][data-color] del forward de paleta se emite EL ÚLTIMO y gana por orden a igual especificidad: la pintura sale del --palette-* global. Es el hallazgo de cascada de paleta medido en button, badge y callout el mismo día (§13, pendiente de firma) y entra como EXCEPCIÓN POR PATRÓN — una razón arquitectónica escrita UNA vez, no cuarenta accidentes. Con ella y los dos pasos de radius adjudicados (forzando data-rounded: 4px → 37px y 10px → 37px), el guard queda en 29/71 y limpio. EL INSTRUMENTO NO VEÍA TRES DE SUS CINCO PARTES Dot, icon y remove son interruptores OPT-IN apagados por defecto: la sonda medía 2 nodos —raíz y etiqueta— y ninguna de las partes que la receta pinta. Encendidos, 5. Misma clase que las capas opt-in de background. ⚠ component:audit da NEEDS-WORK y es PREEXISTENTE: R-1.5, sin tratamiento de foco en la receta (grep focus-visible = 0 tanto en HEAD como después). El ✕ trae el suyo del IconButton que compone; el chip no es focalizable. No se toca aquí: darle foco mueve píxel y es decisión de diseño. VERIFICACIÓN Sonda antes/después 0 diffs (992 valores · 7 estados · 5 nodos con las tres partes encendidas) · R-5.4 29/71 con el resto adjudicado (dos entradas propias + una excepción por patrón), cero STALE · censo 75 % · eidos-lint 5 morfo-backed / 14 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
],
// Its dot, icon and remove parts are OPT-IN switches, all off by default: the
// probe saw 2 nodes (root + label) and none of the three parts the recipe
// paints. They are checkboxes, not chips.
badge: [
'[data-uix-control]:has([data-uix-control-label]:text-is("dot")) input[type=checkbox]',
'[data-uix-control]:has([data-uix-control-label]:text-is("icon")) input[type=checkbox]',
'[data-uix-control]:has([data-uix-control-label]:text-is("removable")) input[type=checkbox]'
uix(image-picker): temable — 71 % → 100 %, y el instrumento medía UN nodo Una clave nueva (13 → 14), cuatro literales firmados y el interruptor que hace medible el componente. Diff de computed 0 sobre 1.344 valores en 7 estados y 6 nodos; R-5.4 de 13/13 a 14/14 SIN una sola adjudicación; capturas 2× antes/después idénticas. EL PICKER ARRANCA VACÍO Y LA SONDA VEÍA UN NODO Todo lo que la receta pinta —preview, canvas, toolbar y los dos botones de icono— sólo existe en el estado ready, al que no llega ningún trigger: hay que cargar un fichero. Con el chip «Load sample image» de la demo la sonda pasa de 1 a 6 nodos. La sonda lo enciende por DEMO_VARIANTS y el centinela por prepareWith; éste necesita además openMarker, porque su re-apertura por token volvería a pulsar el chip y remontaría la vista previa a mitad de corrida. LA CLAVE ES hover-button-bg, Y EL NOMBRE LO DECIDIÓ EL CATÁLOGO Era el único knob que iba a un primitivo global a pelo. La §4 lo nombraba hover-rotate-bg por el PRIMER selector de la regla, y miente dos veces: pinta rotate Y remove, y el contrato de este componente ya llama button-* a ese par desde que nació. hover-{parte}-{slot} es la forma de las 60 claves hover-* de base.ts. NO ES EL VELO DEL SISTEMA — MEDIDO DESDE EL PÍXEL HACIA ARRIBA Los dos botones flotan SOBRE la foto: su relleno de reposo es translúcido (color-mix 88 %) y el hover lo vuelve OPACO. Es un cambio de RELLENO, no una capa encima. Con las transiciones congeladas, elementsFromPoint sobre el centro del botón: el cambio cae en el <button> —el nodo con forma, radio 6 px, de srgb .988/.88 a oklch(.9911 0 0)— y NINGÚN ancestro recibe background-image, porque el arquetipo action no trae velo (archetypes.css sólo vela trigger, item y option). No se bendice un duplicado, que es lo que paró el hover-* de listbox. LOS CUATRO LITERALES SE FIRMAN, NO SE ACUÑAN El 100 % doble del hijo del canvas es identidad (la <Image> ES la caja del canvas, ya con inset: 0), el line-height: 1 es un botón de un solo glifo, y el Highlight de forced-colors es la paleta del sistema operativo: un valor de tema ahí lo sustituiría el UA. Válvula de recipe-contract §3 — salen del ratio como ausencia ESCRITA. DEFECTO REAL, ANOTADO, NO ARREGLADO Ese hover neutro es una invención por componente y §38 / R-4.3 mandan que el hover neutro sea la capa --state-*. Migrarlo cambia el default (velo translúcido en vez de relleno opaco) y D-TH.5 lo prohíbe aquí. Y no basta con cambiar la receta: el arquetipo del par es action, que HOY no recibe velo — o action gana velo en archetypes.css (mueve píxel en todo el catálogo) o el par cambia de arquetipo, que es morfo. Escrito en el veredicto §5 y en el README. CIFRAS Y GUARDS Censo 71 % → 100 % (20 knobs → 16: público 12 → 13, global 1 → 0, literal 4 → 0, excepción firmada 0 → 4; sistema 3, fuera del ratio). Contrato 13 → 14 claves. component-audit PASS · eidos-lint invalid 0 (12 morfo-backed, 5 eidos-only) · vitest src/uix/eidos 35/36 (único rojo skin-media-player, preexistente) · rtl:check 0 · docs:check 0 · npm run check sin errores en ficheros de este componente. README con su sección «Talla y tema» y pestaña Tokens en la demo (14 claves, verificada con clic de Playwright tras networkidle). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
],
// It boots EMPTY (a FileUpload dropzone): the preview, the canvas, the
// toolbar and the two icon buttons — every part the recipe paints — only
// exist in the `ready` state. Without the sample chip the probe saw 1 node.
uix(search-field): temable — 71 % → 100 %, y sus 10 tokens sin medir NO mienten Cero claves nuevas. Su 29 % de deuda eran CUATRO literales que son identidad, y ahora van firmados. Diff de computed 0 sobre 1.216 valores en 8 estados y 5 nodos; R-5.4 de 7/17 a 17/17 SIN una sola adjudicación, dos corridas de acuerdo; capturas 2× antes/después idénticas byte a byte. LOS CUATRO LITERALES SON IDENTIDAD, NO KNOBS El line-height: 1 del icono —un glifo solo no tiene interlineado, la misma anotación que su hermana field.css—, el 100 % doble del ::before —el anillo ES la caja del indicador, que ya mide por --_search-field-loading-size— y el border-radius: 50 %. Ese último la §4 pedía acuñarlo como loading-indicator-radius: sería un knob que SÓLO puede romper el componente. El arco es un BORDE que gira; con cualquier otro valor la ruleta deja de leerse como una. Es la clase 4 de F2-B, «el 50 % de un círculo», al pie de la letra. DIEZ DE SUS DIECISIETE TOKENS NO SE HABÍAN MEDIDO NUNCA El centinela leía 7/17 porque la demo no monta dos estados: el indicador de carga sólo existe con loading (ocho tokens) y la X sólo se desvanece con el campo VACÍO, y el escenario arranca con valor (clear-hidden-opacity). Los dos interruptores entran como prepareWith, y EL ORDEN IMPORTA: el clic en la X tiene que ir PRIMERO, porque la propia regla que enciende le quita después el pointer-events al botón que la disparó. La sonda necesita el de loading por lo mismo: 4 → 5 nodos, sin perder ninguno. LOS DOS PRIVADOS NO SON DEUDA --_search-field-icon-size y --_search-field-loading-size son el conmutador por talla y sus cinco fuentes son, cada una, una clave pública. Se quedan: 58 de las 162 recetas resuelven así su eje size —incluidas las cerradas al 100 % este mes, empty-state y banner—, y convertirlas al ámbito size: del TSC cambiaría la especificidad de la emisión sin ganar un punto de alcance. ⚠ UN DEFAULT MEDIDO, ANOTADO, NO CAMBIADO Las dos escalas del mismo renglón divergen en los dos últimos peldaños. Medido en Chrome con transiciones y animación congeladas, forzando data-size sobre el nodo real, icono → indicador: xs 14→14, sm 16→16, md 18→18, lg 20→18, xl 32→20. El contrato lo declara de frente (loading-indicator-size-lg apunta a --size-md-icon-size y -xl a --size-lg-icon-size), así que en xl la ruleta es DOCE píxeles menor que la lupa que tiene al lado. No es deuda de alcance —las diez claves alcanzan—, es diseño que huele a copia a medias. Mueve píxel: D-TH.5 lo saca de este eje. Escrito en el veredicto §5 y en el README. CIFRAS Y GUARDS Censo 71 % → 100 % (15 knobs → 11: público 10, literal 4 → 0, excepción firmada 0 → 4; sistema 1, fuera del ratio). Contrato 17 claves, intacto — no hay cambio en base.ts ni en generated/. component-audit PASS · eidos-lint invalid 0 (9 morfo-backed, 4 eidos-only) · vitest src/uix/eidos 35/36 (único rojo skin-media-player, preexistente) · rtl:check 0 · docs:check 0 · npm run check sin errores en ficheros de este componente. README con su sección «Talla y tema» y pestaña Tokens en la demo (17 claves, verificada con clic de Playwright tras networkidle). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
'image-picker': ['[data-uix-chip]:text-is("Load sample image")'],
// Its loading indicator — the part that carries EIGHT of its seventeen
// tokens — renders only while `loading`, and the demo boots it off: 4 nodes.
'search-field': [
'[data-uix-control]:has([data-uix-control-label]:text-is("loading")) input[type=checkbox]'
]
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
};
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
async function main() {
const [component, out, urlArg] = process.argv.slice(2);
if (!component || !out) throw new Error('usage: <component> <out.json> [url]');
const url = urlArg ?? `http://localhost:5173/uix/components/${component}`;
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
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
// A demo that is still LOADING measures a different instant on every run:
// feed's auto load-more keeps [data-busy] for ~3s and its commit-settle
// firma animates box-shadow on the very node under measurement (5 phantom
// diffs between two runs of the same code, 2026-08-21). Wait for the busy
// window to close before every snapshot.
const settle = () =>
page
.waitForFunction(() => !document.querySelector('[data-busy]'), null, { timeout: 15000 })
.catch(() => {});
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Every load resets the demo's controls, so the variant chips are re-clicked
// after each one — the probe reloads once per size, plus open and hover.
const variants = async () => {
for (const sel of DEMO_VARIANTS[component] ?? []) {
const el = page.locator(sel).first();
if (!(await el.count())) throw new Error(`variant selector matches nothing: ${sel}`);
await el.click({ timeout: 2000 });
await page.waitForTimeout(200);
}
};
const prepare = async () => {
await settle();
await variants();
};
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de paleta del placeholder de color, que no es deuda. Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón. Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar todas las recetas que brillan. LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su estado de error y el navegador reintenta, así que sonda y guard morían con un `TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load` si la espera de red se agota — la ociosidad era una comodidad, no una garantía. UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO: `placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano. Emulando la preferencia alcanza, y vale para cualquier token detrás de una media query de preferencia. Y una lectura fantasma cazada por repetición: el primer diff acusó `opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la medida: 0 diffs. Un comentario CSS no puede mover un computado. Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// A demo whose network NEVER goes idle (image mounts a deliberately broken
// src for its error state, and the browser keeps retrying) killed the whole
// probe with a TimeoutError. The idle wait is a convenience, not a gate:
// fall back to the load event and carry on.
const open = async (u: string) => {
try {
await page.goto(u, { waitUntil: 'networkidle', timeout: 15000 });
} catch {
await page.goto(u, { waitUntil: 'load', timeout: 15000 });
await page.waitForTimeout(800);
}
};
await open(url);
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page.waitForTimeout(600);
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await prepare();
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const measure = (c: string, props: string[], size: string | null) =>
page.evaluate(
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
([kebab, keys, forced, extra]) => {
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const root = document.querySelector('[data-uix-stage]') ?? document.body;
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
const nodes = [...root.querySelectorAll<HTMLElement>('*')].filter(
(n) =>
[...n.attributes].some((a) => a.name.startsWith(`data-${kebab}`)) ||
(extra ? n.matches(extra as string) : false)
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
);
if (forced)
for (const n of nodes)
if (n.hasAttribute('data-size')) n.setAttribute('data-size', forced);
const key = (n: HTMLElement) => {
const attrs = [...n.attributes]
.filter((a) => a.name.startsWith('data-') && !a.name.startsWith('data-event'))
.map((a) => (a.value ? `${a.name}=${a.value}` : a.name))
.sort()
.join('|');
return `${n.tagName.toLowerCase()}[${attrs}]`;
};
const seen = new Map<string, number>();
const rows: Record<string, Record<string, string>> = {};
for (const n of nodes) {
const base = key(n);
const i = (seen.get(base) ?? 0) + 1;
seen.set(base, i);
const cs = getComputedStyle(n);
const row: Record<string, string> = {};
for (const k of keys as string[]) row[k] = cs[k as keyof CSSStyleDeclaration] as string;
rows[`${base}#${i}`] = row;
}
return rows;
},
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
[c, props, size, EXTRA_NODES[c] ?? null] as const
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
);
const result: Record<string, unknown> = {};
result.rest = await measure(component, PROPS, null);
for (const size of SIZES) {
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de paleta del placeholder de color, que no es deuda. Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón. Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar todas las recetas que brillan. LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su estado de error y el navegador reintenta, así que sonda y guard morían con un `TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load` si la espera de red se agota — la ociosidad era una comodidad, no una garantía. UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO: `placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano. Emulando la preferencia alcanza, y vale para cualquier token detrás de una media query de preferencia. Y una lectura fantasma cazada por repetición: el primer diff acusó `opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la medida: 0 diffs. Un comentario CSS no puede mover un computado. Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page
.reload({ waitUntil: 'networkidle', timeout: 15000 })
.catch(() => page.reload({ waitUntil: 'load', timeout: 15000 }));
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page.waitForTimeout(400);
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await prepare();
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
result[`size:${size}`] = await measure(component, PROPS, size);
}
// Open state: a portaled panel lives OUTSIDE the stage, so measure from the
// document once the trigger has opened it (§7.2 — probe the popup open).
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de paleta del placeholder de color, que no es deuda. Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón. Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar todas las recetas que brillan. LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su estado de error y el navegador reintenta, así que sonda y guard morían con un `TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load` si la espera de red se agota — la ociosidad era una comodidad, no una garantía. UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO: `placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano. Emulando la preferencia alcanza, y vale para cualquier token detrás de una media query de preferencia. Y una lectura fantasma cazada por repetición: el primer diff acusó `opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la medida: 0 diffs. Un comentario CSS no puede mover un computado. Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page
.reload({ waitUntil: 'networkidle', timeout: 15000 })
.catch(() => page.reload({ waitUntil: 'load', timeout: 15000 }));
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page.waitForTimeout(400);
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await prepare();
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
const trigger = page
.locator(OPEN_TRIGGER[component] ?? `[data-${component}-trigger], [data-${component}-input]`)
.first();
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
if (await trigger.count()) {
try {
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
// A context menu opens on RIGHT click and on nothing else.
await trigger.click({ button: component === 'context-menu' ? 'right' : 'left', timeout: 2000 });
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page.waitForTimeout(500);
result.open = await page.evaluate(
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
([kebab, keys, extra]) => {
const nodes = [...document.querySelectorAll<HTMLElement>('*')].filter(
(n) =>
[...n.attributes].some((a) => a.name.startsWith(`data-${kebab}`)) ||
(extra ? n.matches(extra as string) : false)
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
);
const seen = new Map<string, number>();
const rows: Record<string, Record<string, string>> = {};
for (const n of nodes) {
const attrs = [...n.attributes]
.filter((a) => a.name.startsWith('data-') && !a.name.startsWith('data-event'))
.map((a) => (a.value ? `${a.name}=${a.value}` : a.name))
.sort()
.join('|');
const base = `${n.tagName.toLowerCase()}[${attrs}]`;
const i = (seen.get(base) ?? 0) + 1;
seen.set(base, i);
const cs = getComputedStyle(n);
const row: Record<string, string> = {};
for (const k of keys as string[]) row[k] = cs[k as keyof CSSStyleDeclaration] as string;
rows[`${base}#${i}`] = row;
}
return rows;
},
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada. Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una adjudicación escrita. Global 65 % (al 100 % 25 → 26). LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %, context-menu 87 %) dan content-{min-width,padding,radius,fg} y chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg. Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL (0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio. Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por esa familia. LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer selector de la regla ([…-trigger]::after) y colisionando con trigger-height, que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la FILA, y el padding real del panel a «sin nombre mecánico». Llamaba content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra (LAYER_VOCABULARY sólo registra calendar-surface). LAS FILAS DEL PANEL NO SON item- NI link- En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o --…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo trigger-fg, como venía. EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR --_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja. indicator-x ERA DECLARACIÓN MUERTA Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador (-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el re-point de --command-radius, el trío item-gap* de carousel). -w se queda: vive, y es canal de valor, no superficie de tema. EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la BARRA (trigger-font-family), que no es overlay y alcanza. TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13) 1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia: el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9 de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES. 2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter y pointerleave programa el cierre; el guard abría con clic y luego aparcaba el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo código discrepaban. Y daba las DOS caras del error: falso negativo en los tokens de la fila, falso positivo en content-link-padding-inline, que leía «vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos corridas idénticas. 3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0). Y una de contabilidad, del instrumento y no del componente: el censo decide el eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le pasa a sidebar y nav-tree. En §13. VERIFICACIÓN Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única (disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30 morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido) · check sin error nuevo en los ficheros tocados · Chrome con el panel abierto: panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px. Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni STALE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
[component, PROPS, EXTRA_NODES[component] ?? null] as const
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
);
} catch {
/* no openable trigger — skip the state */
}
}
// Hover + focus on every interactive part the recipe could paint.
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de paleta del placeholder de color, que no es deuda. Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón. Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar todas las recetas que brillan. LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su estado de error y el navegador reintenta, así que sonda y guard morían con un `TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load` si la espera de red se agota — la ociosidad era una comodidad, no una garantía. UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO: `placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano. Emulando la preferencia alcanza, y vale para cualquier token detrás de una media query de preferencia. Y una lectura fantasma cazada por repetición: el primer diff acusó `opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la medida: 0 diffs. Un comentario CSS no puede mover un computado. Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page
.reload({ waitUntil: 'networkidle', timeout: 15000 })
.catch(() => page.reload({ waitUntil: 'load', timeout: 15000 }));
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await page.waitForTimeout(400);
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES 7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %** (10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos. Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres literales. Sin eje `size` ni privados propios, así que declarar no puede matar una escala. LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO, no contra el CSS de partida: - **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight` contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya pintaba: `500` ES `var(--font-weight-medium)`. - **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo del marco asomando a cada lado. El knob es `border-width` —con `var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de la escala— y el hueco DERIVA de él. - **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje `scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es `calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110. Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter. Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el 2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro. FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y `--_text-gradient-play-direction` los escribe el wrapper INLINE desde `animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`. INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B aplicada por adelantado, no después): - La demo arranca con `showBorder=false`, así que sonda y centinela veían **2 nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa `DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una vez por talla) y el centinela una entrada `openWith` que no abre un panel sino que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica. - La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el centinela ya leía: un token de fondo podía moverse entre las dos corridas sin que el diff lo viera. Con la propiedad y la variante: **4 nodos**. Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el único efecto de hover es `animation-play-state`, que no es knob. LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene `2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token `text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y `--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit. Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y este renombrado la dejaba obsoleta al instante. Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) · centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7 conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 · docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**. Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene 2.392), no se reformatean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
await prepare();
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
const targets = await page.evaluate(([kebab, extra]) => {
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const root = document.querySelector('[data-uix-stage]') ?? document.body;
return [...root.querySelectorAll<HTMLElement>('*')]
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
.filter(
(n) =>
[...n.attributes].some((a) => a.name.startsWith(`data-${kebab}`)) ||
(extra ? n.matches(extra) : false)
)
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
.map((n, i) => ({ i, tag: n.tagName.toLowerCase() }))
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
.filter((t) => ['button', 'a', 'input', 'li', 'div', 'tr'].includes(t.tag))
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
.slice(0, 24);
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
}, [component, EXTRA_NODES[component] ?? null] as const);
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const hovered: Record<string, Record<string, string>> = {};
for (const t of targets) {
const handle = await page.evaluateHandle(
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
([kebab, idx, extra]) => {
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const root = document.querySelector('[data-uix-stage]') ?? document.body;
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
const all = [...root.querySelectorAll<HTMLElement>('*')].filter(
(n) =>
[...n.attributes].some((a) => a.name.startsWith(`data-${kebab}`)) ||
(extra ? n.matches(extra as string) : false)
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
);
return all[idx as number] ?? null;
},
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
[component, t.i, EXTRA_NODES[component] ?? null] as const
docs(theming): handoff del eje al día — cola revisada, firmas pendientes y el instrumental que las verifica El eje deja de ser «plan entregado» y pasa a «en ejecución»: siete componentes corregidos, veinte fichas revisadas y el informe vivo. El handoff y el registro del plan lo dicen con cifras y commits, para que mañana se entre por la ficha y no por la memoria de nadie. `CONTINUE-theming.md` se reescribe con lo que hace falta para continuar: la cola inmediata de ocho componentes EJECUTABLES sin firma —cada uno con la trampa concreta que su veredicto ya destapó—, la lista de lo que ESPERA firma (familia calendar, mandato de Field, el perímetro del knob para la familia tipográfica, el hover a la capa de estado) y la tabla de lo ejecutado con su censo antes → después. El plan gana su §8 con el registro completo y corrige su cabecera, que seguía diciendo que no había nada construido. Y se commitea el instrumental, porque sin él el protocolo §7 es una intención: la sonda de computed antes/después, el diff que es el gate (vacío o se para), el centinela que pregunta si cada token alcanza de verdad, y la captura del stage. Se ejecutan con `node` a secas —el loader de tsx inyecta helpers que no existen dentro de `page.evaluate` y toda sonda revienta—, desde la raíz y contra el dev server. Lo que el instrumento aprendió a fuerza de mentir queda escrito donde se consulta: congelar `transition` pero NUNCA `animation` (Presence necesita el `animationend` para montar el panel, y si lo congelas mides un popup que no existe); un token resuelto no se mueve desde `:root` por diseño; un panel portalado no ve el ámbito del componente; la demo puede tapar el componente que mides; y un valor centinela igual al real se lee como «no efecto». Igual las cuatro trampas que costaron un commit cada una, con su guard al lado: la clave duplicada en `recipes/base.ts` que descarta el bloque en silencio, los bloques `[data-size]` del CSS que sobreviven a la cascada del TSC y hacen que el diff dé cero por la ruta vieja, los backticks en `node -e` desde bash, y el encabezado que un Edit se come al insertar una sección. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
);
const el = handle.asElement();
if (!el) continue;
try {
await el.hover({ timeout: 1500 });
await page.waitForTimeout(400);
const row = await el.evaluate((n, keys) => {
const cs = getComputedStyle(n as HTMLElement);
const attrs = [...(n as HTMLElement).attributes]
.filter((a) => a.name.startsWith('data-') && !a.name.startsWith('data-event'))
.map((a) => (a.value ? `${a.name}=${a.value}` : a.name))
.sort()
.join('|');
const out: Record<string, string> = { __key: attrs };
for (const k of keys as string[]) out[k] = cs[k as keyof CSSStyleDeclaration] as string;
return out;
}, PROPS);
hovered[`${t.i}:${row.__key}`] = row;
} catch {
/* not hoverable (offscreen / covered) — skip */
}
}
result.hover = hovered;
await browser.close();
writeFileSync(out, JSON.stringify(result, null, '\t'), 'utf8');
const count = Object.keys(result.rest as object).length;
console.log(`probe ${component}: ${count} nodes at rest, ${Object.keys(hovered).length} hovered`);
}
main();

Powered by TurnKey Linux.