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

279 lines
9.9 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',
'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> = {
prose: '[data-prose] *'
};
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> = {
menubar: "[data-menubar-trigger][data-menubar-value='format']"
};
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[]> = {
'text-gradient': [
'[data-uix-control]:has([data-uix-control-label]:text-is("showBorder")) [data-uix-chip]:text-is("true")'
]
};
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();
};
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.goto(url, { waitUntil: 'networkidle' });
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) {
await page.reload({ waitUntil: 'networkidle' });
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).
await page.reload({ waitUntil: 'networkidle' });
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(
([kebab, keys]) => {
const nodes = [...document.querySelectorAll<HTMLElement>('*')].filter((n) =>
[...n.attributes].some((a) => a.name.startsWith(`data-${kebab}`))
);
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;
},
[component, PROPS] as const
);
} catch {
/* no openable trigger — skip the state */
}
}
// Hover + focus on every interactive part the recipe could paint.
await page.reload({ waitUntil: 'networkidle' });
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.