uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
/**
|
|
|
|
|
* theming-sentinel — the sentinel GUARD of PLAN-theming §7.4 point 11 (R-5.4).
|
|
|
|
|
*
|
|
|
|
|
* node scripts/theming-sentinel.ts <component> [url]
|
|
|
|
|
* npm run theming:sentinel -- <component> [url]
|
|
|
|
|
*
|
|
|
|
|
* For every PUBLIC token the component declares in `recipes/base.ts`, sets an
|
|
|
|
|
* unmistakable value and checks that SOME node's computed style follows it. A
|
|
|
|
|
* token that moves nothing is a token that lies — unless its silence is
|
|
|
|
|
* ADJUDICATED in `theming-sentinel-exceptions.ts` with a written reason (a part
|
|
|
|
|
* the demo does not mount, a pseudo-element the instrument cannot read, a
|
|
|
|
|
* forward into a composed component). Dead + unadjudicated = exit 1.
|
|
|
|
|
*
|
|
|
|
|
* Born as `__theming-sentinel.ts` during F2-A; promoted to a guard after the
|
|
|
|
|
* adversarial review of 2026-08-21 measured 22 false negatives in 26 "no
|
|
|
|
|
* effect" verdicts and two genuinely dead declarations that only THIS
|
|
|
|
|
* instrument can see (gradient-picker's popover-owned chrome, carousel's
|
|
|
|
|
* inline-gap from soma — invisible to any static CSS analysis).
|
|
|
|
|
*
|
|
|
|
|
* The review fixed three false-negative causes, each measured:
|
|
|
|
|
* - the open step used to CLICK `[data-{c}-input]`, and a mouse click on a
|
|
|
|
|
* text input DOES match `:focus-visible`, so the focus rule repainted the
|
|
|
|
|
* rest-state chrome and rest tokens read dead (command.input-border, the
|
|
|
|
|
* "unexplained" §13 entry). The active element is now blurred after opening.
|
|
|
|
|
* - the override was written on `[data-{c}]` only, so components whose parts
|
|
|
|
|
* hang from `{c}-root` (and every node the TSC declares resolved names on)
|
|
|
|
|
* never received it. It is now written on :root AND every node carrying any
|
|
|
|
|
* `data-{c}…` attribute — property-level competition is untouched, so the
|
|
|
|
|
* gradient-picker / carousel class of genuine deaths still reads dead.
|
|
|
|
|
* - `::before` / `::after` were invisible (media-player's buffering ring,
|
|
|
|
|
* feed's spinner). Both pseudos are snapshotted now. `::placeholder` still
|
|
|
|
|
* is not — that limit stays adjudicated per token.
|
|
|
|
|
* Plus: a hover pass for hover-only tokens, a settle wait for [data-busy]
|
|
|
|
|
* demos, and inset/animation props in the read set.
|
|
|
|
|
*
|
|
|
|
|
* Same environment rules as the probe: plain `node`, repo root, live dev
|
|
|
|
|
* server, headless.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
|
import { resolve } from 'node:path';
|
|
|
|
|
import { chromium } from 'playwright';
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
import {
|
|
|
|
|
SENTINEL_EXCEPTIONS,
|
|
|
|
|
SENTINEL_PATTERN_EXCEPTIONS
|
|
|
|
|
} from './theming-sentinel-exceptions.ts';
|
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
|
|
|
|
|
|
|
|
const PROPS = [
|
|
|
|
|
'backgroundColor',
|
|
|
|
|
'backgroundImage',
|
|
|
|
|
'backgroundSize',
|
|
|
|
|
'backgroundPosition',
|
|
|
|
|
'color',
|
|
|
|
|
'borderTopColor',
|
|
|
|
|
'borderTopWidth',
|
|
|
|
|
'borderTopLeftRadius',
|
|
|
|
|
'borderInlineStartWidth',
|
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
|
|
|
// The colour of a border on any side but the TOP was invisible: prose tints
|
|
|
|
|
// its blockquote rule on the inline start and its table rules on the block
|
|
|
|
|
// edges, and all three read dead with a live token (measured 2026-08-22).
|
|
|
|
|
'borderInlineStartColor',
|
uix(nav-tree): temable — 4 % → 96 %, y 18/18 en el guard sin una sola excepción
17 claves nuevas (1 → 18): tenía UNA sola pública (`indent`) y todo lo demás iba
a primitivo crudo. Censo nav-tree 4 % → **96 %**; global del eje 61 %.
LA FILA ES LINK + TRIGGER A LA VEZ. Las reglas los tratan juntos —mismo padding,
misma tinta, mismo radio, mismo hover— así que el knob es de la FILA (`row-*`),
no de cada parte: es una pseudo-parte, el molde firmado de las regiones de
`waveform`. Sus estados llevan el modificador DELANTE, que es lo que la gramática
pide para lo interactivo (`hover-row-fg`, `active-row-fg`, `disabled-row-fg`), y
la estela —los ancestros de la página actual— es contextual y va DETRÁS
(`trail-row-fg`).
El disparador sí tiene lo suyo: el suelo de 24 px del blanco táctil (WCAG 2.5.8)
y su padding. Y el galón es una escuadra dibujada con dos bordes (`::after` con
`border-inline-end` + `border-block-end` girado 45°): su lado es un knob y el
grosor del trazo otro — mismo molde que la esquina de `text-focus`.
LA SUITE CAZÓ UN INCUMPLIMIENTO HEREDADO, tercera vez en el eje: el contrato
copió `var(--font-size-sm)` verbatim del CSS, y ésa es la coordenada CRUDA que
`recipe-css-contract` prohíbe en un token de receta. Corregido al bundle
(`var(--size-sm-font-size)`), que computa idéntico. El «valor verbatim» arrastra
el defecto del CSS de partida siempre que el CSS ya lo tuviera.
DOS CEGUERAS MÁS DEL GUARD, medidas y arregladas:
1. **La fila es un `<div>` pelado sin atributo propio**, así que el `gap` entre
link y trigger leía muerto moviendo 4px → 1234px.
2. **No leía el ANCHO de un borde que no fuera el de arriba**: el galón tiñe los
de inline-end y block-end. Añadidos los tres lados que faltaban (los COLORES
por lado ya se añadieron con `prose`, hoy mismo).
Con las dos: **18/18 en vivo, sin una sola excepción**. Los nueve componentes con
ledger re-verificados tras tocar las PROPS del guard: sin regresiones ni STALE.
Lo que queda fuera: el `100%` de la fila (identidad) y el anillo de foco
(sistema).
Gates: sonda antes/después **0 diffs en 22.140 valores** (102 nodos × 8 estados) ·
centinela 18/18 · censo --only 96 % · `--names` sin desviadas · component-audit
PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check
0 · docs:check 0 · captura del escenario mirada (raíl, galones, estela y fila
activa intactos).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// …and their WIDTHS, for the same reason: nav-tree draws its chevron with two
|
|
|
|
|
// borders on the inline-end and block-end edges (measured 2026-08-22).
|
|
|
|
|
'borderInlineEndWidth',
|
|
|
|
|
'borderBlockStartWidth',
|
|
|
|
|
'borderBlockEndWidth',
|
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
|
|
|
'borderInlineEndColor',
|
|
|
|
|
'borderBlockStartColor',
|
|
|
|
|
'borderBlockEndColor',
|
|
|
|
|
'borderBottomColor',
|
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
|
|
|
'paddingTop',
|
|
|
|
|
'paddingLeft',
|
|
|
|
|
'paddingRight',
|
|
|
|
|
'paddingBottom',
|
|
|
|
|
'marginBottom',
|
|
|
|
|
'marginLeft',
|
|
|
|
|
'rowGap',
|
|
|
|
|
'columnGap',
|
|
|
|
|
'fontSize',
|
|
|
|
|
'fontWeight',
|
|
|
|
|
'fontFamily',
|
|
|
|
|
'lineHeight',
|
|
|
|
|
'letterSpacing',
|
|
|
|
|
'blockSize',
|
|
|
|
|
'inlineSize',
|
|
|
|
|
'minBlockSize',
|
|
|
|
|
'minInlineSize',
|
|
|
|
|
'maxBlockSize',
|
|
|
|
|
'maxInlineSize',
|
|
|
|
|
'boxShadow',
|
|
|
|
|
'opacity',
|
uix(text-focus): temable — 63 % → 100 %, y un primitivo FANTASMA que pintaba por accidente
8 claves en una entrada NUEVA de `recipes/base.ts`. Censo 63 % → **100 %**.
Mismo patrón que text-circular: cinco públicos «de facto» consumidos con
fallback y ninguno declarado, más tres que faltaban — los tres en la ESQUINA
del marco de foco.
**La esquina es una escuadra dibujada con bordes**: 1rem × 1rem con dos de sus
cuatro lados a `0` según la posición. El tamaño es un knob, el grosor del trazo
otro, el radio el tercero. Los desplazamientos `-0.625rem` NO son knobs: colocan
la escuadra fuera del marco y se derivan del tamaño y el trazo. `border-color`
y `glow-color` son el mismo acento en dos capas y se declaran aparte a
propósito: un tema puede querer el halo de otro tono que el borde.
EL HALLAZGO — **`--radius-xs` NO EXISTE** (→ next-features §13):
La escala de radios del sistema es `sm | md | lg | xl | xxl` (+ none, full,
default), sin `xs`. Esta receta escribía `border-radius: var(--radius-xs, 3px)`
y **pintaba el fallback**, así que nadie lo notó nunca. Al tokenizarlo tal cual,
la variable quedó vacía, la declaración inválida y las cuatro esquinas
CUADRADAS: **48 diffs** — exactamente para lo que existe el gate. El contrato
guarda `3px` verbatim, el valor que de verdad pintaba, y el diff vuelve a 0.
Lo que abre: **un `var(--fantasma, fallback)` es una referencia muerta que
funciona por accidente**, y el guard G2 que el plan menciona
(«referencias-fantasma contra el contrato derivado») o no existe o no cubre los
fallbacks. Barrer el catálogo buscando `var(--…, …)` cuyo primer nombre no esté
en el contrato derivado es un pase mecánico que se paga solo.
Y un arreglo de instrumento: **`filter` y `backdrop-filter` faltaban en la lista
de propiedades del guard**. `glow-color` vive dentro de un `drop-shadow()` y
leía «no effect» estando vivo — `KNOB_PROPS` del censo sí las tenía, así que el
censo contaba el knob y el guard no podía verlo. Con ellas, text-focus pasa a
**8/8 sin una sola excepción**. Verificados los 30 componentes con ledger: cero
regresiones, cero STALE. (De paso se retiró una adjudicación que había escrito
con una razón equivocada antes de medir: el token sí movía el filter.)
Gates: sonda antes/después = 0 diffs en 1.769 valores × 7 estados · censo
--only 100 % · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 ·
check por fichero limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
'filter',
|
|
|
|
|
'backdropFilter',
|
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
'textDecorationColor',
|
uix(link): temable — 0 % → 36 %, y ese techo bajo es la respuesta correcta
5 claves fusionadas en el bloque `link` de `recipes/base.ts` (donde ya vivían
los dos forwards de paleta). Censo 0 % → 36 %.
**Un enlace en línea hereda casi todo del texto que lo rodea**, así que su
superficie de tema es pequeña por diseño. Sus dos escotillas por instancia
(`--_link-font-size`, `--_link-line-height`) tienen como fallback `inherit`, y
ese `inherit` es SEMÁNTICO: el enlace toma la tipografía del párrafo en el que
vive. Convertirlo en token exigiría un público con valor `inherit`, que en
`:root` no significa lo mismo — se quedan privadas. Con los dos forwards de
paleta, ésos son los cuatro privados que forman el grueso del 64 % restante.
Lo tokenizable es la GEOMETRÍA: radio del anillo, hueco del glifo externo,
tamaño del glifo (`0.85em` en las dos dimensiones → UN knob) y el grosor y
desplazamiento del subrayado, cada uno declarado en las DOS reglas que lo
dibujan (`always` y `hover`) con el mismo valor. Las tres opacidades son
sistema; el `fill: currentColor` del glifo es identidad, sigue a la tinta del
enlace a propósito.
**La cascada no se toca.** El CSS lleva dos correcciones de especificidad ya
medidas y explicadas en su sitio (el `inherit` condicional, para que un `color`
pedido por el consumidor no se descarte en silencio; y el `:active` al final,
porque una pulsación es siempre también un hover). El diff de computed confirma
que tokenizar no las altera.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 203 valores × 7 estados. Determinismo
verificado.
- R-5.4: 2/5, los tres mudos adjudicados con su medición. Ninguno es deuda: el
glifo externo no se monta en la demo (0 nodos, contados) y el enlace arranca
en `data-underline='hover'` sin puntero encima, así que NO HAY subrayado que
mover (`text-decoration-line: none`, `thickness: auto`). Forzando el estado,
los tres alcanzan.
- En el mismo pase, `text-decoration-thickness` y `text-underline-offset`
entran en la lista de propiedades del guard, que no las miraba —
verificado que los otros DIECISÉIS componentes con ledger no cambian.
- censo --only 36 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0 · check por
fichero limpio · README con «Talla y tema» y tab `Tokens` (5 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
'textDecorationThickness',
|
|
|
|
|
'textUnderlineOffset',
|
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
|
|
|
'outlineColor',
|
|
|
|
|
'outlineWidth',
|
|
|
|
|
'outlineOffset',
|
|
|
|
|
'zIndex',
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// SVG paint + geometry. Without these EVERY token of an SVG recipe reads
|
|
|
|
|
// dead: chart's axis / grid / separator ink, its stroke widths, the point
|
|
|
|
|
// radius and the area's fill-opacity are all painted through presentation
|
|
|
|
|
// attributes the box properties above never see (measured 2026-08-22 —
|
|
|
|
|
// 12 of chart's 39 tokens). Same class as the `filter` / `backdrop-filter`
|
|
|
|
|
// gap fixed the same day.
|
|
|
|
|
'fill',
|
|
|
|
|
'fillOpacity',
|
|
|
|
|
'stroke',
|
|
|
|
|
'strokeWidth',
|
|
|
|
|
'strokeOpacity',
|
|
|
|
|
'strokeDasharray',
|
|
|
|
|
'r',
|
|
|
|
|
'rx',
|
|
|
|
|
'ry',
|
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
|
|
|
'left',
|
|
|
|
|
'right',
|
|
|
|
|
'top',
|
|
|
|
|
'bottom',
|
|
|
|
|
'animationDuration'
|
|
|
|
|
];
|
|
|
|
|
|
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía
31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global
48 %, sin contrato 43 → 42.
La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta».
Mirarlo cambió tres cosas del encargo:
1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de
list-surface o calendar-surface no hay hook de capa ni vocabulario prestado
— color-picker, date-picker y chronos importan sus `.svelte` directamente.
No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien.
2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre
`[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa
cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`,
que no casa nunca), así que las coordenadas por talla quedan como públicos
PLANOS y la receta las consume desde sus propios selectores. Es el único
componente del eje donde el patrón de nombres resueltos no aplica; la regla
base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de
tiempo lleva un TERCER eje, `data-size` sobre su propia clase.
3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0
nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la
vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker
anfitrión y detrás de su popover), y sus partes se llaman
`data-picker-header` / `-body` / `-footer` en vez de
`data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so
every picker gets the same visual contract for free» —, así que todo filtro
por prefijo del componente ve sólo la raíz.
CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía:
- Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos
reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0
diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**.
- Los knobs que la demo no monta —el header entero y la fila de tiempo— se
midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del
header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su
picker-size).
- El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES`
(prefijo de atributo + selector de apertura) acotado a este componente.
Verificado que los otros CATORCE con ledger no se mueven ni una cifra.
Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en
el ledger; los 25 que siguen mudos llevan cada uno su razón medida.
Header y footer comparten la regla que los separa del cuerpo → UN par de knobs
(`border-width` + `border`), no dos.
Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en
next-features §13 junto con la pregunta de fondo — si el precio de los nombres
genéricos (un contrato visual común para todos los pickers) compensa la ceguera
del instrumental — y con el eje portalado como caso de prueba si el TSC llega a
ganar un scope configurable.
Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido
skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio ·
`component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
/**
|
|
|
|
|
* Components whose DOM does not follow `data-{component}-{part}`, so neither the
|
|
|
|
|
* node filter nor the open step can find them by convention.
|
|
|
|
|
*
|
|
|
|
|
* `picker-shell` names its parts GENERICALLY on purpose — `data-picker-header`
|
|
|
|
|
* / `-body` / `-footer` — "so every picker gets the same visual contract for
|
|
|
|
|
* free" (its recipe says so), and it has no demo route of its own: it is
|
|
|
|
|
* measured inside a host picker, behind that picker's popover. Without this the
|
|
|
|
|
* guard reported 0/31 and would have needed 31 false "exceptions".
|
|
|
|
|
*/
|
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
const COMPONENT_OVERRIDES: Record<
|
|
|
|
|
string,
|
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
|
|
|
{
|
|
|
|
|
attrPrefix?: string;
|
|
|
|
|
openWith?: string[];
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
/**
|
|
|
|
|
* `focus` is for a surface that exists ONLY while focused: skip-link is
|
|
|
|
|
* sr-only until Tab reaches it, so every one of its eight tokens read dead
|
|
|
|
|
* (0/8, measured 2026-08-23) — the guard never focused it, and the blur it
|
|
|
|
|
* does after opening would have undone it anyway.
|
|
|
|
|
*/
|
|
|
|
|
openBy?: 'click' | 'hover' | 'contextmenu' | 'focus';
|
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
|
|
|
urls?: string[];
|
|
|
|
|
/** Nodes to measure that carry NO `data-{c}-*` attr (prose styles bare HTML). */
|
|
|
|
|
extraNodes?: string;
|
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
/**
|
|
|
|
|
* Demo controls to switch ON before measuring — ALL of them, unlike
|
|
|
|
|
* `openWith`, which stops at the first that works because it opens ONE
|
|
|
|
|
* surface. A component whose parts are independent OPT-IN layers
|
|
|
|
|
* (background: scrim, spotlight, pause… each behind its own control)
|
|
|
|
|
* measured 3 of 37 tokens on the default stage: what the demo does not
|
|
|
|
|
* mount has no node to paint. Failures are ignored — a control that is
|
|
|
|
|
* not there is not an error, it is one layer this route cannot show.
|
|
|
|
|
*/
|
|
|
|
|
prepareWith?: string[];
|
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
|
|
|
/**
|
|
|
|
|
* How to tell the surface is ALREADY open, when it is not
|
|
|
|
|
* `[data-{c}-content]`. Without it the re-open guard that runs before every
|
|
|
|
|
* token sees "closed", clicks the trigger again and TOGGLES the surface
|
|
|
|
|
* shut — half the run measured against a closed panel.
|
|
|
|
|
*/
|
|
|
|
|
openMarker?: string;
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
/**
|
|
|
|
|
* An attribute to SWEEP while measuring, with the values to try. A demo that
|
|
|
|
|
* mounts ONE instance (button: a single solid/primary stage driven by chips)
|
|
|
|
|
* hides every token that only paints under another variant — 71 of buttons
|
|
|
|
|
* 105 read dead without this.
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
*
|
|
|
|
|
* SEVERAL axes may be given, and then the sweep is their CARTESIAN PRODUCT
|
|
|
|
|
* (toggle, 2026-08-23: its `{variant}-*` keys and its `{variant}-on-*` keys
|
|
|
|
|
* are the same 3 variants in two different STATES, and forcing the state
|
|
|
|
|
* with `prepareWith` traded one half of them for the other — the "mounting
|
|
|
|
|
* more can measure less" trap, this time inside one component). One axis
|
|
|
|
|
* yields the same sequence it always did, so nothing that had a single
|
|
|
|
|
* `sweepAttr` changes behaviour.
|
|
|
|
|
*
|
|
|
|
|
* A `null` value REMOVES the attribute, which is the only way to sweep an
|
|
|
|
|
* axis whose off position is the attribute's ABSENCE (`data-invalid`,
|
|
|
|
|
* `data-rounded`): stamping `data-invalid=""` for the whole run makes the
|
|
|
|
|
* invalid border out-rank every other border token on the node, and six
|
|
|
|
|
* of them read dead (measured on toggle, 2026-08-23).
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
*/
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
sweepAttr?:
|
|
|
|
|
| { attr: string; values: (string | null)[] }
|
|
|
|
|
| { attr: string; values: (string | 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
|
|
|
}
|
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
> = {
|
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos
Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte
receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela
**83/88** con 5 adjudicadas. El default NO se mueve.
## Lo primero: el instrumento no veía el contrato
La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un
helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el
`return {`, un tabulador más adentro. Consecuencia medida:
- el censo la leía como «sin entrada en base.ts» — 84 claves invisibles;
- el centinela **moría** con `no recipe block for avatar`: el componente no se
podía medir, y su gate no significaba nada.
Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo
lo demás de este commit habría sido humo.
## Lo que entra al contrato (6 claves)
`group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`.
Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis
bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde
escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el
mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por
`--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 ·
−16,8 · −22,4 · −33,6 px → 37 px, uno a uno).
## Lo que se retira (2 declaraciones muertas, diff 0 las dos)
- `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta
declaraba su default `99`. **No lo leía nadie** — el tope se aplica con
`data-has-max` + `:nth-child(n + M)` porque una variable no entra en
`:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos
sitios; el `+3` del grupo sigue exactamente donde estaba.
- el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara
`--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo
podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue
computando `rgb(255, 255, 255)`.
## Seis literales firmados (salen del ratio)
Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie
del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo
centrado por la caja flex.
## Lo que se queda privado (el techo real es 90 %)
`--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian
de fuente con la variante (solid · soft · outline) y su valor sale del forward de
paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`.
Un público encima dejaría que un tema los fijara y matara el `color=` de cada
avatar.
## Artefactos
- Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576
valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072).
- Capturas 2× antes/después de las dos demos: **idénticas al byte**.
- Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y
`font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza —
el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo
el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas).
- El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de
`data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en
su propia ruta (21 muertos → 5).
- `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido
`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check`
COMPLETED sin un solo error en `avatar`.
- README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador
con `networkidle` y clic real).
## Lo que NO se arregla aquí (anotado)
- **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática
exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que
renombrarlas cambia el contrato público de tres canales: se lista, el audit
sigue en NEEDS-WORK por esto y sólo por esto.
- **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token
en `:root` y también sobre cada nodo, y para una propiedad personalizada que
la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema
pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo.
- **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE
las seis excepciones de `metrics`, así que se hace con la re-verificación del
ledger entero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Two thirds of its surface is OPT-IN or lives on another route: the badge is
|
|
|
|
|
// off by default (and boots in DOT mode, where the chip has no text frame),
|
|
|
|
|
// the ring is off, and AvatarGroup — same recipe, same `data-avatar-group`
|
|
|
|
|
// prefix — is a component with its own demo. Without this the guard saw the
|
|
|
|
|
// bare portrait: no badge, no halo, no stack.
|
|
|
|
|
avatar: {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("show badge")) input[type=checkbox]',
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("dot")) input[type=checkbox]',
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("ring")) [data-uix-chip]:text-is("solid")'
|
|
|
|
|
],
|
|
|
|
|
// The 16 `*-outline-*` keys only paint under the outline variant, on the
|
|
|
|
|
// badge (the root reads its border through the shared palette forward).
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['solid', 'soft', 'outline'] },
|
|
|
|
|
urls: ['/uix/components/avatar', '/uix/components/avatar-group']
|
|
|
|
|
},
|
uix(editable): temable — 75 % → 82 %, y el guard CERRABA la superficie que medía
Protocolo PLAN-theming §7 sobre `editable`. Censo 75 % → **82 %**, contrato 56
claves, centinela **42/56** con 14 adjudicadas. El default NO se mueve.
## Lo que costó medirlo: dos superficies y una apertura frágil al revés
- **Preview e input comparten celda de rejilla y se excluyen** (`display: none`),
igual que los triggers (Edit en reposo, Save/Cancel editando): una pasada deja
fuera la mitad de la receta. Todo se midió DOS veces.
- **Salir del modo edición es lo que hace un `blur`** —da igual lo que diga
`submitMode`, esa opción sólo decide si el valor se compromete— y el guard
blurea justo después de abrir. Abriendo con clic en el trigger, la superficie
ya estaba cerrada cuando se medía: `submit-fg` leía muerto y mueve
perfectamente abierta (`rgb(255,255,255)` → `rgb(1,2,3)`). Se abre por FOCO,
con la demo en `activationMode=focus`, que es la apertura que el guard no
deshace. Misma ley que `navigation-menu`, con el gesto invertido.
## Lo que entra al contrato (1 clave)
`disabled-fg` — la tinta apagada de TODO el componente: preview, input y los
tres triggers comparten UNA regla, así que el nombre desnudo del catálogo es el
que toca (`label` y `select` ya lo llevan con este mismo valor). Era el único
knob que iba a un primitivo a pelo, y encima con un respaldo escrito a mano
—`var(--color-content-disabled, var(--color-content-muted))`— que estaba MUERTO:
el token sí está declarado, así que el respaldo no podía pintar nunca. Retirado
con él.
## Lo que sale (1 clave huérfana)
`control-border`: ninguna regla lo consumía. El borde en reposo del control es
`transparent` por diseño y el del input es el acento
(`--_editable-palette-border`), así que esa clave pública prometía un knob que no
existe — la deuda INVERSA del eje. Diff 0, y el centinela lo confirmaba muerto.
## Dos literales de identidad firmados
El `fit-content` del preview (es tan ancho como el texto que enseña; la ficha
proponía acuñarlo como `preview-width`, que sería acuñar la definición de la
parte) y el `100 %` del input (llena la celda que comparte con el preview).
## Lo que se queda privado (el techo honesto es 82 %)
Los SIETE knobs por privado son el puente de paleta THM-2
(`--_editable-palette-{solid,border,solid-hover}`), que la capa compartida
alimenta por instancia desde `[data-color]`: un público encima dejaría que un
tema lo fijara y matara el `color=` de cada instancia. Otros cuatro son sistema
transversal (anillo de foco y velo de hover), ya fuera del ratio.
## Las catorce adjudicaciones, en dos razones — y una tercera que casi se cuela
- El par `transition-*` **ES** la transición que el guard congela; medido sin
congelar: `0.12s → 4.321s` y `cubic-bezier(0.4, 0, 0.2, 1) → steps(3)`.
- Las doce claves de tono son la cascada de paleta, por PATRÓN y con el mecanismo
a la vista: el nodo lleva `--palette-solid` de la capa por instancia, el bloque
genérico `[data-editable][data-color]` —emitido EL ÚLTIMO, misma
especificidad— lo lee, y el respaldo que nombra la clave del componente no
llega nunca. Quítale `data-color` a la misma instancia y
`--editable-primary-solid` vuelve a pintar (`rgb(1, 2, 3)`).
- ⚠ **`risk-border` NO cae en ese patrón**: las tres reglas de inválido la leen
DIRECTAMENTE, así que su silencio tiene otra causa —la demo es válida— y
entrada propia: forzando `data-invalid` alcanza en el input Y en el control.
Una excepción por patrón que engulle una clave con vida propia es una
adjudicación falsa.
## Artefactos
- Sonda: **0 diffs** en las dos caras — modo preview (1.696 valores, 7 estados) y
modo edición (2.528 valores, 8 estados).
- Captura 2× del reposo idéntica al byte. La de edición NO es comparable por
bytes: la traza de la demo imprime un TIMESTAMP, así que dos corridas del mismo
código difieren (comprobado antes de creerme el diff). Revisada a ojo.
- `component-audit --only editable`: **PASS** · `eidos-lint` 0 invalid ·
`vitest src/uix/eidos` sin rojos nuevos (el conocido `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED, 0 errores en
`editable`.
- README «Talla y tema» + pestaña `Tokens` (56 filas, verificada en el navegador
con `networkidle` y clic real).
## Defecto anotado, NO arreglado
El tinte inválido ES la clave de tono (`--editable-risk-border`): dos knobs con
una sola llave — un tema que quiera otro color de error mueve también las
instancias `color="risk"`. El arreglo es una clave de estado por parte
(`invalid-input-border` / `invalid-control-border`, la forma que `switch` acuñó
el mismo día) y es contrato: se lista.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its whole edit surface — input, submit, cancel — is `display: none` until the
|
|
|
|
|
// component enters edit mode, and the demo boots in PREVIEW. But the opening is
|
|
|
|
|
// FRAGILE: leaving edit mode is what a blur does (whatever `submitMode` says —
|
|
|
|
|
// that flag only decides whether the value commits), and the guard blurs right
|
|
|
|
|
// after opening, so clicking the edit trigger measured a surface that was
|
|
|
|
|
// already closed again (`submit-fg` read dead and moves 1234 when it is really
|
|
|
|
|
// open). Switching the demo to `activationMode=focus` makes FOCUS the opening,
|
|
|
|
|
// which is the one the guard does not undo.
|
|
|
|
|
editable: {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("activationMode")) [data-uix-chip]:text-is("focus")'
|
|
|
|
|
],
|
|
|
|
|
openBy: 'focus',
|
|
|
|
|
openWith: ['[data-editable-preview]'],
|
|
|
|
|
openMarker: '[data-editable][data-editing]'
|
|
|
|
|
},
|
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía
31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global
48 %, sin contrato 43 → 42.
La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta».
Mirarlo cambió tres cosas del encargo:
1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de
list-surface o calendar-surface no hay hook de capa ni vocabulario prestado
— color-picker, date-picker y chronos importan sus `.svelte` directamente.
No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien.
2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre
`[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa
cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`,
que no casa nunca), así que las coordenadas por talla quedan como públicos
PLANOS y la receta las consume desde sus propios selectores. Es el único
componente del eje donde el patrón de nombres resueltos no aplica; la regla
base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de
tiempo lleva un TERCER eje, `data-size` sobre su propia clase.
3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0
nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la
vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker
anfitrión y detrás de su popover), y sus partes se llaman
`data-picker-header` / `-body` / `-footer` en vez de
`data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so
every picker gets the same visual contract for free» —, así que todo filtro
por prefijo del componente ve sólo la raíz.
CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía:
- Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos
reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0
diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**.
- Los knobs que la demo no monta —el header entero y la fila de tiempo— se
midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del
header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su
picker-size).
- El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES`
(prefijo de atributo + selector de apertura) acotado a este componente.
Verificado que los otros CATORCE con ledger no se mueven ni una cifra.
Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en
el ledger; los 25 que siguen mudos llevan cada uno su razón medida.
Header y footer comparten la regla que los separa del cuerpo → UN par de knobs
(`border-width` + `border`), no dos.
Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en
next-features §13 junto con la pregunta de fondo — si el precio de los nombres
genéricos (un contrato visual común para todos los pickers) compensa la ceguera
del instrumental — y con el eje portalado como caso de prueba si el TSC llega a
ganar un scope configurable.
Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido
skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio ·
`component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
'picker-shell': {
|
|
|
|
|
attrPrefix: 'data-picker',
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
openWith: ['[data-uix-stage-area] [data-popover-trigger]'],
|
|
|
|
|
// It has no route of its own — `/uix/components/picker-shell` is a 404 —
|
|
|
|
|
// so the default URL measured NOTHING and the guard reported 0/31 with six
|
|
|
|
|
// unadjudicated. Named here so nobody has to know: it is measured inside a
|
|
|
|
|
// host picker (6/31, the figure its own commit `67b4c810c` recorded).
|
|
|
|
|
urls: ['/uix/components/date-picker']
|
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its WHOLE surface exists only under `:focus` — sr-only until Tab reaches
|
|
|
|
|
// it, a pill afterwards — and there is no trigger to click: the guard read
|
|
|
|
|
// 0/8, every token of a recipe that works. Focusing it is the opening, and
|
|
|
|
|
// the blur that follows a click opening is exactly what would undo it.
|
|
|
|
|
// Clicking is NOT an option either: its handler moves the focus to the
|
|
|
|
|
// destination region, so the pill would vanish on the way in.
|
|
|
|
|
'skip-link': { openBy: 'focus', openWith: ['[data-skip-link]'] },
|
uix(collapsible): temable — 90 % → 100 %, y el que ANIMA no tiene un knob de motion
Una sola clave, y es la COSTURA (PLAN §2-A): el `gap` del encabezado leía
`var(--space-2)` A PELO. El valor es del sistema; el knob no era de nadie, así
que un tema no podía apretar esta cabecera sin mover el paso de espaciado en
toda la app. El nombre lo decide el CATÁLOGO, no la propuesta de la ficha:
`trigger-gap` ya existe ocho veces en `base.ts`, cinco de ellas con exactamente
`var(--space-2)`.
Qué entra:
- `--collapsible-trigger-gap: var(--space-2)` — valor verbatim, cero píxel.
Qué NO entra, y por qué:
- Los dos knobs restantes son SISTEMA TRANSVERSAL, fuera del ratio: el velo de
hover es `linear-gradient(var(--state-hover), var(--state-hover))` (la capa de
`archetypes.css`, nunca una invención por componente) y el anillo de foco es
`--focus-ring-*`.
- **No hay clave de motion que acuñar.** Este componente «anima su apertura» y
aun así su receta no declara un solo `@keyframes`: la firma es el par de
eventos `emerge-expand` / `emerge-collapse` que materializa
`EidosConfig.motion.signatures` (recipe-contract §2, R-4.5). Lo único suyo es
el par `transition-*`, que ya estaba en el contrato.
Censo: 12 knobs · público 9 → 10 · global 1 → 0 · sistema 2 · 90 % → 100 %.
Contrato 11 → 12 claves. Global del catálogo 68 %, sin bajar.
Diff de computed: VACÍO — 736 valores en 8 estados (reposo, cinco tallas,
abierto y hover) sobre 3 nodos, que son las TRES partes del morfo, en cerrado y
en abierto. Capturas 2× antes/después, reposo y abierto: idénticas.
Centinela R-5.4: 10/12, dos adjudicadas, cero muertas sin razón.
- `transition-duration` / `transition-ease` SON la transición que el guard
congela para poder leer todo lo demás (la clase que `color-picker` documentó
el 2026-08-23). Medidas sin congelar sobre el encabezado real:
`0.18s, 0.18s → 4.321s, 4.321s` y `cubic-bezier(0.4, 0, 0.2, 1) ×2 →
steps(3) ×2` — dos entradas porque el trigger transiciona `background` Y
`color`. Al ledger con esa razón.
- `disabled-opacity` NO se adjudica: se ARREGLA. Encender el interruptor
`disabled` de la demo para toda la corrida se habría llevado el tinte de hover
por delante (`pointer-events: none` en el provider — «montar más mide menos»),
así que el estado se mide como SUPERFICIE EXTRA por la URL de la demo
(`?perm.disabled=true`), precedente `radio-group`. 8/11 → 10/12.
Guards: censo `--only` 100 % · `component-audit --only collapsible` PASS ·
`eidos-lint collapsible` 7 morfo-backed / 0 invalid · `vitest run src/uix/eidos`
35/36 ficheros (el rojo es `skin-media-player`, el conocido) · `rtl:check` 0 ·
`docs:check` 0 · `check` COMPLETED sin un solo error atribuible a este
componente · prettier: mi bloque limpio, la deriva del README y de la demo es
PREEXISTENTE en HEAD y no se toca.
Artefactos de documentación del protocolo: README con su `## Talla y tema`
derivada del contrato, pestaña `Tokens` en la demo (12 claves resueltas en vivo,
verificada en navegador) y veredicto §5 de la ficha.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its opacity floor is the only knob behind a state, and switching the demo's
|
|
|
|
|
// `disabled` toggle ON for the whole run would take the hover tint with it
|
|
|
|
|
// (`pointer-events: none` on the provider — the "mounting more can measure
|
|
|
|
|
// less" trap). The demo reads the state from the URL, so the disabled chrome
|
|
|
|
|
// is measured as an EXTRA SURFACE instead of a ledger line (the radio-group
|
|
|
|
|
// precedent): 8/11 -> 9/11, with the hover key still alive on the first URL.
|
|
|
|
|
collapsible: {
|
|
|
|
|
urls: ['/uix/components/collapsible', '/uix/components/collapsible?perm.disabled=true']
|
|
|
|
|
},
|
uix(link-preview): temable — 0 % → 100 %, y el plano overlay que traía tipografía
33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → **100 %**: no
queda un knob de apariencia fuera del contrato (los tres restantes son sistema
— anillo de foco y los dos del plano de profundidad que pintan la flecha).
Global 48 %, sin contrato 42 → 41, al 100 % 7 → 8.
1. **Los per-talla llevan `parts: ['content']`**, no el host: el panel está
PORTALADO y su `data-size` vive en el content, así que una cascada colgada
del host no lo alcanza nunca. La talla mueve sólo dimensiones físicas
(padding, min-width, tipo); familia, peso e interlineado son constantes a
propósito, como ya decía la receta.
2. **La flecha no se tokeniza**: su fill y su stroke consumen el plano
`overlay` para casar siempre con el panel; acuñar `arrow-fg` rompería justo
esa garantía. El subrayado del trigger es un `color-mix` sobre
`currentColor` — la mezcla ES el aspecto, así que el knob es la declaración
entera, no el 35 %. Y el fallback encadenado del color del trigger viaja
verbatim: la coma es parte del valor, no un segundo knob.
EL HALLAZGO — el plano `overlay` impone TIPOGRAFÍA (→ next-features §12.9):
`[data-depth='overlay']` (`generated/base.css:7088`) no declara sólo fondo,
borde y sombra: también `font-family` y `line-height`. El panel estampa ese
plano para su elevación, así que la declaración del plano empata a (0,1,0) con
la de la receta y **gana por orden de carga** (la hoja generada va después del
chunk de la receta). Medido con CDP `getMatchedStylesForNode`: el par
`font-family` / `line-height` de esta receta **no había pintado nunca**.
Retirado con **0 diffs**, que es la prueba.
Afecta a los **veinte** componentes que estampan el plano. Lo que hay que
decidir —aparte, mueve píxel— es si un plano de profundidad tiene competencia
sobre la tipografía o sólo sobre la superficie. Cuarta ocurrencia de la clase
«empate resuelto por orden de carga».
CÓMO SE MIDIÓ, porque el instrumento estándar tampoco servía aquí:
El panel abre en HOVER con retardo y está portalado → la sonda genérica ve **1
nodo**, y una sonda que compara un nodo pasa en falso. Sonda dirigida (abre por
hover, captura los cinco nodos reales en las cinco tallas): determinismo
verificado y **gate antes/después = 0 diffs sobre 550 valores**, repetido tras
la retirada.
El guard R-5.4 pasó de **3/35 a 31/33** con dos extensiones: `openBy: 'hover'`
para este componente (un hover card no se abre con clic — clicar su `<a>`
navega) y `text-decoration-color` en la lista de propiedades, que faltaba pese
a estar en `KNOB_PROPS` del censo. Verificado que los otros QUINCE componentes
con ledger no se mueven ni una cifra.
Los dos tokens que siguen mudos son un conflicto ESTRUCTURAL, no un hueco de
demo: para abrir el panel hay que tener el puntero en el trigger, y el hover es
justo el estado cuya regla pisa la tinta y el subrayado de reposo — el token de
hover lee vivo en la misma corrida. Adjudicados con esa razón.
Gates: censo --only 100 % · component:audit PASS · eidos-lint invalid 0,
class-hooks 0 · suite eidos sin rojos nuevos (el conocido skin-media-player) ·
rtl:check 0 · docs:check 0 · check por fichero limpio · README con «Talla y
tema» y tab `Tokens` en la demo (33 claves, en vivo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A hover card opens on POINTER-OVER, not on click: clicking its trigger
|
|
|
|
|
// (an `<a>`) navigates instead of revealing the panel, so the portaled
|
|
|
|
|
// content never enters the document and 32 of its 35 tokens read dead.
|
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
|
|
|
'link-preview': { openBy: 'hover' },
|
uix(tags-input): temable — 88 % → 90 % CERRADO, y su tinta no ha pintado nunca
Los cinco knobs fuera de alcance se clasificaron UNO A UNO antes de tocar una
línea, que es la mitad del trabajo:
- **uno era la COSTURA**: `[data-tags-input-control][data-invalid]` leía
`var(--color-risk-border)` A PELO. Entra como
`--tags-input-invalid-control-border`, valor verbatim, con el nombre Y el
default de `field.invalid-control-border` — la misma parte del mismo chasis.
NO se reusó el `risk-border` de la paleta: la cascada `[data-color]` lo
re-enruta por instancia y se llevaría con él el estado inválido, que no es
una elección de tono. `global` a CERO.
- **los otros cuatro son el MISMO puente THM-2**: el borde del control
enfocado y los tres slots de la etiqueta activa leen
`--_tags-input-palette-{border,track,text}`. Ni conmutador ni escala por
talla. No se acuñan (CONTINUE §3.pre): un público encima dejaría que un tema
los fijara y matara en silencio el `color=` por instancia.
**Así que 90 % ES el techo, y es doctrina bien aplicada, no deuda.** Los siete
privados dimensionales ya derivan de públicos; sus bloques `[data-size]` del
CSS SE QUEDAN porque aquí no los emite el TSC (las claves de talla son planas,
sin `scope: 'size:{k}'`) — retirarlos rompería el eje. Migrar esa escala al TSC
es cambio de mecanismo, no de alcance, y queda fuera.
Censo: 46 knobs · público 37 → 38 · global 1 → 0 · privado 4 (el puente) ·
sistema 4 (fuera del ratio) · 88 % → 90 %. Contrato 90 → 91 claves.
`--names`: 0 desviadas (67 conformes + 24 role-slot canónicas).
Diff de computed: VACÍO en las dos superficies. 2.496 valores × 8 estados en el
escenario por defecto (válido, sonda estándar, 10 nodos) y 459 valores × 5
tallas + 3 variantes + hover en el estado INVÁLIDO, con una sonda de un solo
uso porque la demo arranca válida. Captura 2× antes/después byte a byte
idéntica. No se tocó ninguna regla `:hover`, así que el paso 12 no aplica.
Centinela R-5.4: **63/91**, 28 adjudicadas, cuatro razones y todas MEDIDAS:
1. **Las 24 claves de tono — la cascada de paleta** (excepción por PATRÓN). El
wrapper resuelve `color` y estampa `data-color` en TODA instancia, incluida
la del default, así que gana el bloque genérico
`[data-tags-input][data-color], [data-tags-input][data-color-custom]`, que
se emite el ÚLTIMO a la misma (0,2,0) que los ocho por tono. Medido sobre la
etiqueta real forzada a `data-state=active`: `--tags-input-primary-border`
no mueve nada (el borde sigue en `oklch(0.8043 0.0918 309.69)`) mientras
`--palette-border` en el mismo nodo lo repinta (`rgb(4, 5, 6)`); quita
`data-color` y `--tags-input-primary-border` vuelve a pintar
(`rgb(1, 2, 3)`).
2. **`transition-duration` / `transition-ease`** SON la transición que el guard
congela. Medidos sin congelar: 0.12s → 4.321s y
`cubic-bezier(0.4, 0, 0.2, 1)` → `steps(3)`.
3. **`placeholder-fg`**: soma sólo pone el atributo `placeholder` mientras el
input está VACÍO (`isEmpty ? opts.placeholder : undefined`) y el escenario
arranca con dos etiquetas — y `::placeholder` sobre un input SIN placeholder
devuelve el estilo del ELEMENTO (la trampa ya medida en `field`). Vaciar el
escenario no vale para esa corrida: borra los nodos de etiqueta, donde pinta
un tercio del contrato. Medido de las dos formas: pulsando el propio `Clear`
de la demo el input queda con `placeholder="Add tag"`, y forzando el
atributo sobre el input real el token alcanza (`rgb(1, 2, 3)`; default
`oklch(0.61 0 0)` = `--color-content-muted`).
4. **`item-fg` — y ésta NO es del instrumento, es del COMPONENTE.** El morfo
sella `aria-selected='true'` como LITERAL en CADA etiqueta, así que la regla
de `archetypes.css`
`[data-archetype='item'][aria-selected='true']:not([data-state='checked']):not([data-state='on'])`
**(0,4,0)** gana siempre a `[data-tags-input-item]` **(0,1,0)**: la tinta de
la etiqueta es la del arquetipo y su velo de estado se pinta en TODAS las
etiquetas en reposo. Medido: con el atributo tal como se envía el token no
mueve nada (`oklch(0.2435 0 0)`, la del arquetipo); con `aria-selected=false`
sobre la etiqueta real, la misma escritura alcanza (`rgb(1, 2, 3)`). Los
tres slots `*-text` del tono llevan esta SEGUNDA causa encima de la primera.
Es la clase de `table.selected-row-fg` y la tinta de `listbox`. Toca morfo Y
mueve píxel ⇒ se mide, se REGISTRA (next-features §12, con su cara de a11y:
un lector anuncia todas las etiquetas como seleccionadas) y se sigue.
Lo que el instrumento aprendió: **el guard ABRE haciendo clic en el control**,
lo que estampa `data-focus`, y la regla de foco re-tinta el borde a la misma
(0,2,0) pero más tarde — así que `control-border`, un token perfectamente vivo,
leía muerto. Es la clase de `command.input-border`, otra vez, y la cura es
barrer la AUSENCIA (`null`) de `data-focus`, `data-invalid` y `data-disabled`:
**60/91 → 63/91 sin adjudicar una clave de más**, y de paso reviven
`invalid-control-border` y `disabled-opacity`. Es una entrada por componente:
no toca a nadie más. `fab` re-verificado tras el cambio del guard: 11/12, sin
regresión ni STALE.
Guards: censo `--only tags-input` 90 % (global 68 %, no baja) · `--names` 0
desviadas · `component-audit --only tags-input` PASS · `eidos-lint tags-input`
0 invalid (22 morfo-backed, 6 eidos-only) · `vitest run src/uix/eidos` 35/36
ficheros, el único rojo es el conocido `skin-media-player` en `lint.test.ts` ·
`rtl:check` 0 · `docs:check` 0 · `npm run check` COMPLETED, cero errores en los
ficheros tocados (los 72 globales son de otras sesiones) · prettier limpio
sobre el contenido normalizado a LF.
README con su sección «Talla y tema», pestaña `Tokens` en la demo verificada en
navegador (91 claves, cero errores de página) y veredicto §5 escrito en la
ficha.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Three states the stage never wears at once, and the guard's own opening
|
|
|
|
|
// creates the third: it CLICKS the control, which stamps `data-focus`, and
|
|
|
|
|
// the focus rule re-tints the border at the same (0,2,0) but later — so the
|
|
|
|
|
// resting `control-border` read dead on a token that is perfectly alive
|
|
|
|
|
// (the `command.input-border` class, measured again here 2026-08-24).
|
|
|
|
|
// Sweeping the absence (`null`) of each is what measures the resting form.
|
|
|
|
|
'tags-input': {
|
|
|
|
|
sweepAttr: [
|
|
|
|
|
{ attr: 'data-focus', values: [null, ''] },
|
|
|
|
|
{ attr: 'data-invalid', values: [null, ''] },
|
|
|
|
|
{ attr: 'data-disabled', values: [null, ''] }
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(fab): temable — 88 % → 100 %, y el modo va DETRÁS, no delante
Le faltaban DOS knobs y los dos son del pill extendido: el FAB circular no
declara ni `padding-inline` ni `gap`, pero la regla
`[data-button][data-fab][data-extended]` sí, y leía `var(--space-5)` /
`var(--space-2)` A PELO. La COSTURA del PLAN §2-A: el valor sigue siendo del
sistema, el knob pasa a ser del componente. `global` a CERO.
Claves que entran (2, defaults verbatim):
- `--fab-padding-inline-extended: var(--space-5)`
- `--fab-gap-extended: var(--space-2)`
El nombre lleva el cualificador DETRÁS. `extended` es un MODO del FAB, no un
estado interactivo (el vocabulario cerrado es hover · active · selected ·
disabled · checked · open · focus · invalid · current), así que cae en la otra
mitad de la frase firmada en D-TH.6 —«delante lo interactivo, detrás lo
dimensional y contextual»—, igual que `sidebar.width-icon` y
`radio-group.gap-vertical`. Un `--fab-padding-inline` a secas habría prometido
a TODO FAB un padding que sólo tiene el pill.
Lo que NO se acuña, y por qué el 100 % no lleva las tres claves de la §4:
el `font-size: 1em` del glifo es identidad de contexto y ya lleva su anotación
`/* literal: */` (fuera del ratio por recipe-contract §3), y la colocación
flotante es de la capa `viewport-placement`, cuya ranura de override Fab
escribe sin acuñar vocabulario paralelo (`--fab-offset` / `--fab-z` se
retiraron por eso en 2026-08-15).
La composición NO le quita nada, y valía la pena medirlo: el mismo `<button>`
lleva `data-button` Y `data-fab`, la forma exacta del hallazgo de
`gradient-picker` (36 claves muertas bajo `popover.css`). Aquí no ocurre por
construcción — las reglas dimensionales del FAB casan
`[data-button][data-fab][data-fab-size]` (0,3,0) contra los (0,2,0) de
`[data-button][data-size]`, así que ganan sea cual sea el orden de carga de los
chunks. Medido: padding computado 20px = `--space-5`, no el del Button.
Tampoco lleva `data-depth`, así que §12.9 no le aplica.
Censo: 16 knobs · público 14 → 16 · global 2 → 0 · excepción 1 (fuera del
ratio) · 88 % → 100 %. Contrato 10 → 12 claves.
Diff de computed: VACÍO en las dos formas. 416 valores × 7 estados en el
escenario por defecto (circular, sonda estándar) y 188 valores × 4 tallas +
hover en el pill extendido, con una sonda de un solo uso porque la demo
arranca circular. Capturas 2× antes/después byte a byte IDÉNTICAS en las dos.
No se tocó ninguna regla `:hover`, así que el paso 12 no aplica.
Centinela R-5.4: **11/12**, 1 adjudicada. `lift` es la laguna del INSTRUMENTO,
no del token: la receta lo consume con la propiedad `translate` (no
`transform`, para no pisar el press-squeeze del Button) dentro de una regla
`:hover`, y el guard ni fotografía `translate` —la misma laguna ya adjudicada
tres veces sobre `transform` en background / rating-group / card— ni pasa el
ratón en su pasada estática. Medido a mano con puntero real: `--fab-lift:
1234px` escrito ANTES de entrar da `translate: 0px -1234px` a los 300 ms →
alcanza. Y trae una trampa nueva: el centinela SE AUTO-CANCELA — un lift de ese
tamaño se lleva el nodo de debajo del cursor, `:hover` cae y el valor vuelve a
`none` hacia t+900 ms. Añadirle una pasada de hover al guard no lo mediría.
El instrumento aprendió dos cosas de este componente, las dos por el §2
«cuenta los nodos»: un FAB es UN nodo, así que su segunda superficie pintada
—el hueco de icono del Button compuesto— entra por `EXTRA_NODES` (1 → 2
nodos); y su eje de talla es `data-fab-size`, NO `data-size`, así que el
barrido genérico del guard no sellaba nada. Con el barrido cartesiano
`data-fab-size` × `data-extended` el guard pasa de **3/12 a 11/12** sin
adjudicar una sola clave de más. Los dos interruptores son entradas por
componente: no tocan a nadie más y no exigen re-verificar el ledger.
Guards: censo `--only fab` 100 % (global 68 %, no baja) · `--names` 12
conformes, 0 desviadas · `component-audit --only fab` PASS · `eidos-lint fab`
0 invalid (14 eidos-only) · `vitest run src/uix/eidos` 35/36 ficheros, el único
rojo es el conocido `skin-media-player` en `lint.test.ts` · `rtl:check` 0 ·
`docs:check` 0 · `npm run check` COMPLETED, cero errores en los ficheros
tocados (los 72 globales son de otras sesiones) · prettier limpio sobre el
contenido normalizado a LF.
README con su sección «Talla y tema» (tabla derivada del contrato), pestaña
`Tokens` en la demo verificada en navegador (12 claves, cero errores de
página) y veredicto §5 escrito en la ficha.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Two axes, and the guard knew NEITHER of them (3/12 without this entry).
|
|
|
|
|
// (1) The FAB's size axis is `data-fab-size`, not `data-size` — it is an
|
|
|
|
|
// eidos-only visual attr with its own scale off the control ladder, so the
|
|
|
|
|
// generic size sweep stamped nothing and the six xs/sm/lg steps read dead.
|
|
|
|
|
// (2) Its EXTENDED pill is a boolean mode whose absence is the default: the
|
|
|
|
|
// demo boots circular, so the two keys that only paint under
|
|
|
|
|
// `[data-extended]` had no node. Sweeping the two as a product measures the
|
|
|
|
|
// circular form AND the pill instead of trading one for the other.
|
|
|
|
|
fab: {
|
|
|
|
|
// A FAB is ONE node; its second painted surface is the composed Button's
|
|
|
|
|
// icon slot, sized by descendant selector and carrying Button's attr.
|
|
|
|
|
extraNodes: '[data-fab] [data-button-icon]',
|
|
|
|
|
sweepAttr: [
|
|
|
|
|
{ attr: 'data-fab-size', values: ['md', 'xs', 'sm', 'lg'] },
|
|
|
|
|
{ attr: 'data-extended', values: [null, ''] }
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(tooltip): temable — 52 % → 75 %, y el plano ANULA SUS TRES VARIANTES
3 claves nuevas y 4 literales anotados. Subiría más, pero medir destapó que cinco
de sus claves no pintan: el plano `overlay` le gana la superficie ENTERA.
Acuñados el desenfoque del velo `ghost` y las dos tintas de la flecha `outline`,
que se pintan por selector HERMANO desde el contenido.
⚠⚠ EL PLANO NO SÓLO SE QUEDA LA TIPOGRAFÍA: SE QUEDA EL FONDO, EL BORDE Y LA
SOMBRA. `[data-depth='overlay']` declara las tres con la misma especificidad que
`[data-tooltip-content]` y más tarde en la cascada. Medido: escribir
`--tooltip-bg`, `--tooltip-border` o `--tooltip-shadow` no mueve NADA, mientras
`--depth-overlay-surface` / `-border` / `-shadow` repintan el panel. Con
`font-family` y `line-height`, son cinco públicos que no alcanzan.
Y LA CONSECUENCIA VISUAL ES PEOR QUE LA CONTABLE: LAS TRES VARIANTES SON LA
MISMA. Medidas `solid`, `outline` y `ghost` con el panel abierto: fondo, borde y
sombra IDÉNTICOS en las tres. `outline` es indistinguible de `solid`; `ghost`
sólo se diferencia por un `backdrop-filter` que no se ve porque la superficie que
tiene delante es opaca. La máquina de variantes existe, alimenta privados, y el
plano la anula entera. → §13.
Por eso no se acuñó `ghost-bg`: lo intenté, el centinela lo delató y se retiró
antes de commitear. Las cinco que ya estaban quedan ADJUDICADAS, no retiradas:
son la FUENTE de esa máquina de variantes, y borrarlas dejaría inválidos los
privados sin arreglar nada. La corrección de fondo es la precedencia del plano —
la misma firma §12.9 que arrastran menubar, dropdown-menu y combobox.
`content-z` sí está vivo, pero en otro nodo: aterriza en el ENVOLTORIO flotante
(90 → 4321), que no lleva ningún `data-tooltip-*` y queda fuera del conjunto
medido.
Instrumento: un tooltip abre por HOVER, no por clic (`openBy: 'hover'`, como
link-preview) — con eso pasó de 0/24 a 14/24. El resto se midió con el
interruptor `open` de la demo: un panel abierto por puntero exige el ratón quieto
sobre el disparador toda la pasada, y no es medida fiable.
Gates: sonda 0 diffs en 256 valores × 8 estados · centinela 15/23 con las ocho
adjudicadas · censo 75 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0
· `docs:check` 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A tooltip opens on POINTER-OVER too: clicking its trigger does nothing and
|
|
|
|
|
// the panel never enters the document — 24 of its 24 tokens read dead
|
|
|
|
|
// (measured 2026-08-23).
|
|
|
|
|
tooltip: {
|
|
|
|
|
openBy: 'hover',
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['solid', 'outline', 'ghost'] }
|
|
|
|
|
},
|
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
|
|
|
// Not a panel to open but a VARIANT to switch on: the demo boots with
|
|
|
|
|
// `showBorder=false`, and the frame owns three of the seven tokens. The
|
|
|
|
|
// chip is outside the component, so clicking it cannot poison a hover
|
|
|
|
|
// state (the pointer is parked right after, as for every other opener).
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Chart is ONE recipe whose surface is spread over SIXTEEN demo routes, one
|
|
|
|
|
// per chart type: `/chart` mounts a line+area chart and NOTHING else, so
|
|
|
|
|
// two thirds of the contract (bar list, stacked bar, radar, smith, gauge,
|
|
|
|
|
// funnel, polar, heat, pie…) reads dead on it. Measured 2026-08-22: 58
|
|
|
|
|
// chart nodes on `/chart` against 302 in the whole page and the rest in
|
|
|
|
|
// sibling routes.
|
uix(prose): temable — 1 % → 83 %, la costura por ELEMENTO y dos cegueras del instrumento
35 claves nuevas (2 → 37). Censo prose 1 % → **83 %** con `global` a CERO;
global del eje **59 % → 60 %** (1.064 → 1.021 knobs `global` en el catálogo).
Prose estiliza HTML CRUDO (`h1`, `code`, `table`, `kbd`…) a través de
`:where([data-prose] el)`, así que sus «partes» son elementos y la costura va
POR PARTE, como en `result`: `heading-*` (+ `h1-font-weight`, lo único que
distingue al h1 de sus hermanos), `link-*`, `strong-`/`dt-`/`th-font-weight`,
`mark-*`, `blockquote-*`, `code-*`, `pre-*`, `kbd-*`, `media-radius`, `hr-*`,
`table-*`, más `fg`, `fg-muted` y el eje `font-size-{sm,md,lg}`. Los valores
siguen siendo los del canon; lo que faltaba era el knob (PLAN §2-A).
LA ESCALA EDITORIAL NO ENTRA, Y ES DOCTRINA FIRMADA: los `2.25em`, `1.5em`,
`0.9em`, sus interlineados y su tracking son `em`-relativos a propósito —un
`font-size` raíz re-deriva encabezados y márgenes, por eso NO hay una escala
duplicada por talla como en tailwind-typography (su README: «No 5× duplicated
scale»)— y van anotados `/* literal: */`, la válvula de recipe-contract §3. Son
las 21 excepciones firmadas que el censo aprendió a leer esta misma sesión.
Dos literales se cambiaron por su paso de escala, con el mismo valor: el `3px`
del borde-acento del blockquote ES `--border-width-thick`, y los `1px` de kbd,
`hr` y la tabla son `--border-width`.
EL INSTRUMENTO ESTABA CIEGO DE DOS MANERAS, arregladas ANTES de medir:
1. **Sonda y centinela filtran por `data-{c}-*`** y aquí NINGUNA parte lo lleva:
la sonda medía **1 nodo** y el guard daba **27 de 37** tokens por muertos.
Los dos ganan un selector de nodos extra (`EXTRA_NODES` en la sonda,
`extraNodes` en `COMPONENT_OVERRIDES`) — con él, 63 nodos y un gate que
significa algo.
2. **El centinela no leía el color de un borde que no fuera el de arriba.** El
blockquote tiñe su `border-inline-start` y la tabla sus bordes de bloque:
tres tokens vivos leían muertos. Añadidos los cuatro lados lógicos y
`borderBottomColor`.
OCHO ADJUDICACIONES, TODAS MEDIDAS: cinco las tapa el harness de la
documentación —`[data-uix-docs] code|pre` (0,2,0) gana a `:where(…)` (0,0,0),
que es la incidencia ya escrita para `code-block` un piso más arriba— y tres son
elementos que el HTML de ejemplo no usa (`<mark>`, `<dl>`), montados DENTRO del
contenedor real: un `<mark>` a pelo es exactamente lo que esta receta estiliza,
así que el nodo es fiel (la lección de `drag-drop` cumplida).
Gates: sonda antes/después **0 diffs en 11.910 valores** (63 nodos × 7 estados) ·
centinela 29/37 con las ocho adjudicadas · censo --only 83 % · `--names` 37/37
conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados. Componentes del ledger re-verificados tras
tocar el guard (text-gradient, chart, result, tree-view, textarea): sin
regresiones ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Prose styles RAW HTML through `:where([data-prose] el)`, so its parts carry
|
|
|
|
|
// no attribute of its own: without this the guard measures ONE node (the root)
|
|
|
|
|
// and 27 of its 37 tokens read dead. Measured 2026-08-22.
|
|
|
|
|
prose: { extraNodes: '[data-prose] *' },
|
uix(radio-group): temable — 77 % → 81 %, y su punto NUNCA ha pintado su escala
Ocho claves acuñadas, diecisiete bloques `[data-size]` retirados y veinticuatro
adjudicaciones medidas. El default no se mueve: diff de computed 0 sobre 25.263
valores en 47 estados (20 combinaciones de variante x estado x talla con su
pasada de hover, más las 7 de la sonda estándar) y capturas 2x idénticas.
Censo: 77 % → 81 % (knobs 75 → 59, público 56 → 46, global 7 → 1, privado 10
invariante, sistema 2). Contrato 61 → 69 claves. Global no baja (68 %).
QUÉ ENTRA (8 claves)
item-size · dot-size · font-size — los tres ejes RESUELTOS por talla en el
TSC, con `scope: 'size:{k}'` sobre el proveedor, que es quien lleva
`data-size`. Con ellos se van los DIECISIETE bloques `[data-size]` del CSS
(§7.4-4: dejarlos vivos hace que el diff dé 0 por la ruta vieja) y el privado
`--_radio-item-size`, que llevaba el nombre del componente ABREVIADO —
theming §6 r5 lo prohíbe, es la clase de `--_mp-*` y `--gp-*` — y por eso el
censo contaba el ancho y el alto del ítem como deuda.
invalid-indicator-border · invalid-ring · invalid-ring-gap ·
invalid-item-label-fg — el tinte de validación leía `--color-threat-element`
y `--color-threat-text` a pelo. El trío del anillo toma el vocabulario del
gemelo `checkbox`, que ya lo tenía; el borde se llama `indicator-*` porque en
ESTE componente «indicator» es el círculo exterior y `dot-*` el punto.
segmented-segment-padding-block — el hermano que le faltaba a
`-padding-inline`.
UNA CLAVE ACUÑADA Y RETIRADA EN EL ACTO
`row-min-height-touch: var(--touch-target)` para el suelo táctil de la fila.
La suite `recipe-css-contract` la rechazó como referencia FANTASMA: nadie
emite `--touch-target`, es una constante ergonómica escrita a mano en
`archetypes.css` («a hard ergonomic constant — the fingertip, NOT a theme
value»). Y recipe-contract §2 ya lista el touch-target entre los sistemas
transversales que una receta CONSUME y nunca acuña, así que la lectura cruda
es el contrato cumpliéndose. El censo la cuenta `global` sólo porque su lista
`system` aún no conoce el token — el pendiente de firma «extender la clase
system a las capas compartidas».
POR QUÉ 81 % ES EL TECHO
Los diez knobs que quedan fuera leen `--_radio-group-palette-*`: el puente
THM-2 que la capa compartida alimenta por instancia desde `[data-color]`. Un
público encima dejaría que un tema lo fijara y matara el `color=` de cada
instancia. No es deuda; es el componente cerrado en su número.
TRES DECLARACIONES QUE NO PINTAN (medidas, y las tres mueven píxel → firma)
1. La escala del punto NUNCA ha pintado. `<SvgDot>` escribe
`style="width: 1em; height: 1em"` y un inline gana a cualquier regla: el
punto computa 13,3281px en las CINCO tallas mientras el token dice
5/6/7/9/11px, el valor centinela no lo mueve, y quitando el estilo inline
el MISMO token pinta 1234px. Clase «canal de valor inline» del trío
`carousel.item-gap*`, pero con arreglo posible: la forma `!important` que
recipe-contract §3 autoriza justo para saltarse un estilo inline.
2. `[data-radio-group-label][data-disabled]` no casa jamás. Con el grupo
entero deshabilitado la etiqueta lleva `[id, data-radio-group-label,
data-archetype]` y nada más: soma no estampa `data-disabled` en la parte
`Label` (el morfo declara `data: []`). Forzado a mano el token alcanza, o
sea que falta el ATRIBUTO, no la clave — decisión de MORFO.
3. El fondo de un radio marcado Y deshabilitado no llega: la regla de
variante es (0,4,0) y `[data-radio-group-item][data-disabled][data-state=
'checked']` sólo (0,3,0). Medido con el centinela escrito: solid da el
acento a plena intensidad, outline y segmented transparente, ghost la
mezcla al 18 %. Quitar `data-variant` no ayuda: el gemelo
`:not([data-variant])` también es (0,4,0).
Y una cuarta, de escala del SISTEMA y por tanto fuera de este eje (D-TH.5):
el `:hover` del círculo es (0,4,0) y el tinte inválido (0,3,0), así que pasar
el ratón por encima TAPA el borde de error. Las cuatro, en next-features §13.
EL INSTRUMENTO MEDÍA 27 DE 61
No veía el punto (es un `<svg data-svg='dot'>` sin atributo del componente),
ni la variante `segmented` (doce claves), ni los estados disabled / invalid.
El guard lleva ahora `extraNodes` para el punto, el barrido de `data-variant`
y DOS URL más — `?perm.disabled=true` y `?perm.invalid=true`, que esta demo
honra: once adjudicaciones sustituidas por medición real.
CENTINELA 45/69, 24 adjudicadas: 6 del punto (estilo inline), 8 tonos por la
cascada de paleta (mismo patrón que su gemelo checkbox, medido aquí: la clave
no mueve nada, `--palette-solid` en el nodo repinta), 4 del par de transición
congelado, 2 del anillo inválido (necesita `:focus-visible` con modalidad de
teclado), `gap-horizontal` (el par de gap, sólo pinta la mitad en vigor),
`segmented-pill-shadow` (la píldora sólo la renderiza el envoltorio bajo la
variante real), `disabled-item-label-fg` y `checked-disabled-indicator-bg` (las
dos declaraciones muertas de arriba).
GUARDS
censo --only radio-group 81 % · global 68 % (no baja) · component-audit PASS ·
eidos-lint 0 invalid (13 morfo-backed, 24 eidos-only) · vitest src/uix/eidos
sin rojos nuevos (el único es `skin-media-player`, conocido) · rtl:check 0 ·
docs:check 0 · prettier OK sobre el contenido normalizado a LF · check sin
errores en ficheros del componente.
README con su sección «Talla y tema» y la demo con su pestaña Tokens (69
claves, verificada en navegador).
Índice construido a mano desde HEAD sobre los seis ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel.ts,
theming-sentinel-exceptions.ts, PLAN-theming.md, next-features.md): otras
cuatro sesiones los escriben a la vez. Fuera del commit, docs/audit/theming/
README.md — es un agregado global que mezcla trabajo en vuelo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Three blind spots at once (measured 2026-08-23, 27 of 61 tokens moved
|
|
|
|
|
// before this): the DOT is a bare `<svg data-svg='dot'>` inside the
|
|
|
|
|
// indicator, so the five `dot-size-*` steps had no node in the filter; the
|
|
|
|
|
// whole SEGMENTED chrome (track, segment, pill — twelve keys) only paints
|
|
|
|
|
// under `data-variant='segmented'` and the demo boots `solid`; and the
|
|
|
|
|
// disabled / invalid chrome needs a STATE the stage does not wear — this
|
|
|
|
|
// demo reads both from the URL (`perm.disabled`, `perm.invalid`), so they
|
|
|
|
|
// are measured as extra surfaces instead of eleven ledger lines.
|
|
|
|
|
'radio-group': {
|
|
|
|
|
extraNodes: "[data-radio-group-indicator] svg[data-svg='dot']",
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['solid', 'outline', 'ghost', 'segmented'] },
|
|
|
|
|
urls: [
|
|
|
|
|
'/uix/components/radio-group',
|
|
|
|
|
'/uix/components/radio-group?perm.disabled=true',
|
|
|
|
|
'/uix/components/radio-group?perm.invalid=true'
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(toggle): temable — 81 % → 100 %, y el guard sólo sabía barrer UN eje
Tres knobs fuera de alcance, ninguno por doctrina.
- **El radio** (1 knob, 6 fuentes). La receta leía `--radius-default` y la
escala global `--radius-*` A PELO, así que la única forma de redondear un
toggle era mover el radio de todo el sistema — el agujero que tenía
`navigation-menu`, y el único de los CINCO componentes con escala `rounded`
que no tenía la suya (button, badge, image y color-swatch la declaran).
Entran `radius` (el reposo, default `var(--radius-default)`: sigue siguiendo
el default del arquetipo) y `radius-{sm,md,lg,xl,full}`. El desacople de
`size` se conserva intacto: son pasos de `rounded`, no de talla.
- **El tinte inválido**: `var(--color-threat-element)` a pelo →
`invalid-border` con ese valor (la costura, PLAN §2-A).
- **Los tres literales quedan FIRMADOS** con su anotación y salen del ratio:
el `100%` de `data-block` es identidad y el par `1px` del cuerpo ES la
técnica sr-only.
Censo: 28 knobs · 22 públicos · 81 % → 25 knobs · 24 públicos · **100 %**
(3 excepciones firmadas fuera del ratio). Contrato 119 → 126 claves. Global
del catálogo sin bajar (68 %).
Diff de computed: **VACÍO**. 416 valores en 7 estados (sonda estándar, 2
nodos) más 1.150 valores en 25 celdas de variante / rounded / on / invalid /
disabled / block / icon-only / tono, con `transition` y `animation`
congeladas — porque el escenario de la demo monta UN toggle solid, apagado y
válido, y la sonda no barre esos ejes. Capturas 2× antes y después, miradas.
Centinela R-5.4: **70/126, 56 adjudicadas**, verde. Empezó en 36/126 y la
diferencia NO es adjudicación, es instrumento: las claves `{variante}-*` y
`{variante}-on-*` son las mismas tres variantes en dos ESTADOS, y forzar el
estado con `prepareWith` cambiaba una mitad por la otra (36 → 46, y murieron
las de reposo) — «montar más puede medir menos», esta vez dentro de un
componente. El guard aprende tres cosas, cada una nacida de una medición:
- barrer el **producto cartesiano** de varios ejes (un solo eje da la misma
secuencia de siempre, así que nada de lo que ya tenía `sweepAttr` cambia);
- **quitar** un atributo con `null`, porque sellar `data-invalid` toda la
corrida hacía que el borde inválido ganase a los otros seis bordes;
- aplicar el barrido también en el **pase de hover**, que no barría nada —
que es literalmente la razón escrita en el ledger de
`radio-group.hover-segmented-segment-fg`.
Con eso, 58 y luego 70. Los **12 componentes con `sweepAttr` re-verificados
uno a uno**: cero regresiones, cero STALE (tres dieron exit=1 falso corriendo
doce seguidos contra el mismo dev server; a solas, verdes).
De las 56 adjudicadas, 48 son la cascada de paleta POR PATRÓN, medida aquí:
con `data-color=primary`, `--toggle-primary-solid` no mueve nada y
`--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez. Las ocho `neutral-*` caen con las
demás por una razón que conviene escribir: el propio guard ESTAMPA
`data-color=neutral` para probarlas, que es justo lo que enciende el bloque
genérico. Las otras 8 están medidas una a una sobre el nodo real (los cinco
pasos de `rounded`, que la demo no expone; `disabled-opacity`; y el par de
transición, que ES lo que el guard congela).
Guards: censo `--only toggle` 100 % · `component-audit --only toggle` PASS ·
`eidos-lint toggle` 0 invalid · `vitest run src/uix/eidos` sin rojos nuevos
(el conocido `skin-media-player` en `lint.test.ts`) · `rtl:check` 0 ·
`docs:check` 0 · `check` sin errores en ningún fichero tocado · prettier sin
desviaciones NUEVAS sobre el árbol indexado.
Anotado y FUERA por D-TH.5: `[data-toggle]:hover` y
`[data-toggle][data-invalid]` pesan lo mismo (0,2,0) y el hover se declara
antes, así que un toggle inválido bajo el puntero pierde su tinte. Arreglarlo
mueve píxel y es una decisión de escala del sistema — los cuatro controles de
formulario comparten el patrón.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its opacity floor is the only knob behind a state, and the demo has no URL
|
|
|
|
|
// switch for it: the toggle is the way in, and turning it on costs nothing
|
|
|
|
|
// (the one hover token of the recipe moves `transform`, which the guard does
|
|
|
|
|
// not read anyway — adjudicated).
|
|
|
|
|
'rating-group': {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("disabled")) input[type=checkbox]'
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(toolbar): temable — 91 % → 94 % CERRADO, y ceder el cromo del botón SALE GRATIS
Cero claves acuñadas, y eso es el resultado: el censo daba `global 0` — la
receta ya lee su propio contrato en cada declaración, con el eje `size`
resuelto por el TSC a través de siete privados que sólo reapuntan claves
públicas por talla. Lo que quedaba fuera del alcance eran tres cosas
CLASIFICADAS, no tres deudas. La mitad del trabajo de este eje es adjudicar.
1. Los DOS privados son CONMUTADORES por variante, medidos variante a variante
sobre el nodo real: `--_toolbar-bg` vale `var(--toolbar-bg)`
(oklch(0.9821 0 0)) en `surface` y `transparent` en `outline` y `ghost`;
`--_toolbar-shadow` vale `var(--toolbar-shadow)` y `none` en las mismas. Ese
segundo valor es la IDENTIDAD de la variante, no un knob: una barra `ghost`
ES la que no tiene relleno ni sombra, y un público encima dejaría que un tema
la rellenara y matara la variante en silencio. Se quedan privados (clase
«conmutador» de F2-B, sexto caso tras textarea, spinner, skeleton, code y
label). Ni escala por talla ni puente THM-2: esta barra no tiene paleta.
2. El único literal es una IDENTIDAD y ahora está FIRMADO: el
`inline-size: 100%` del separador horizontal abarca el eje transversal de la
barra — el mismo trabajo que `align-self: stretch` hace en el gemelo vertical
(medido: 112 px sobre un contenedor de 126 px). Su grosor y su suelo sí son
tokens. Con su anotación `/* literal: */` sale del ratio.
3. LA CASCADA DEL COMPONENTE COMPUESTO NO LE CUESTA UNA SOLA CLAVE, y es la
lección. `Toolbar.Button` compone el `<Button>` canónico, así que sus nodos
llevan `data-button` Y `data-toolbar-button`, y `[data-button]` casa con la
misma especificidad (0,1,0) pero se emite DESPUÉS. La diferencia con `form`
—que conserva 24 declaraciones que Button anula— es que esta receta YA CEDIÓ
el cromo del botón (A-112) y no escribe ni una regla `[data-toolbar-button]`.
Medido ANTES de tocar nada: 51/54 vivos, y ninguna de las tres silenciosas
era del botón. Las claves `control-*` visten el Link y el GroupItem, que sí
son superficie de la barra.
Censo: 35 → 34 knobs · público 30 · privado 2 · literal 1 → 0 · excepción
0 → 1 · sistema 2 · 91 % → 94 %. Contrato 54 claves, intacto. Global del
catálogo 68 %, sin bajar.
Diff de computed: VACÍO — 2.464 valores en 7 estados sobre 11 nodos (la barra,
sus tres botones compuestos, dos separadores, el grupo, sus tres items y el
enlace). Capturas 2× antes/después: idénticas.
Centinela R-5.4: 52/54, dos adjudicadas, cero muertas sin razón.
- `transition-duration` / `transition-ease` SON la transición que el guard
congela para poder leer todo lo demás. Medidas sin congelar sobre el Link
real, que transiciona CUATRO propiedades: `0.12s ×4 → 4.321s ×4` y
`cubic-bezier(0.4, 0, 0.2, 1) ×4 → steps(3) ×4`. Al ledger con esa razón.
- `disabled-opacity` NO se adjudica: se ARREGLA. El guard enciende ahora el
interruptor «group disabled» de la demo, y comprobado que no tapa nada — la
regla de hover es `[data-toolbar-link]:hover,
[data-toolbar-group-item]:hover:not([data-disabled])` y el Link no pertenece
al grupo, así que conserva las dos claves de hover. Corrido antes y después
del control: 51/54 → 52/54, el conjunto muerto encoge en uno y no gana
ninguno.
Guards: censo `--only` 94 % · `component-audit --only toolbar` PASS ·
`eidos-lint toolbar` 16 morfo-backed / 5 eidos-only / 0 invalid ·
`vitest run src/uix/eidos` 35/36 ficheros (el rojo es `skin-media-player`, el
conocido) · `rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un solo
error atribuible a este componente · prettier: la anotación `/* literal: */`
sobrevive en la MISMA línea y mi bloque del README queda limpio; los 12 hunks
de deriva del README y los 12 de la demo son PREEXISTENTES en HEAD y no se
tocan.
Artefactos de documentación del protocolo: README con su `## Talla y tema`
derivada del contrato (los nueve ejes + la clasificación de los privados),
pestaña `Tokens` en la demo (54 claves resueltas en vivo, verificada en
navegador) y veredicto §5 de la ficha.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its opacity floor is the only knob behind a state, and this demo has no URL
|
|
|
|
|
// switch for it. Turning the GROUP's toggle on costs nothing measurable: the
|
|
|
|
|
// hover rule of the bar is `[data-toolbar-link]:hover, [data-toolbar-group-item]:hover:not([data-disabled])`,
|
|
|
|
|
// and the Link is not part of the group — it keeps both hover keys alive
|
|
|
|
|
// (verified by running the guard before and after this control: the dead set
|
|
|
|
|
// shrank by one and gained nothing).
|
|
|
|
|
toolbar: {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("group disabled")) input[type=checkbox]'
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(form): temable — 71 % → 86 %, y 24 claves suyas NO PINTAN por una cascada
Diez claves nuevas (54 → 64), un literal firmado, veinte adjudicaciones medidas
y un hallazgo que espera firma. Diff de computed 0 sobre 832 valores en 7
estados; capturas 2× antes/después.
LAS DIEZ IBAN A LOS PRIMITIVOS A PELO
El resumen de errores (error-summary-{bg,fg,border}) —es el señalizador de nivel
de página y su superficie, su tinta y su borde SON el tono risk—, el acento de
formulario inválido (invalid-accent, un privado con UNA sola fuente, no un
conmutador), las tres superficies del árbol de AutoFields, las dos del widget y
el subrayado a media tinta de los enlaces del resumen (link-underline-fg, el par
del -offset que ya existía).
⚠ R-5.3 CAZÓ MI NOMBRE EN EL ACTO: lo acuñé como link-underline-color y la tinta
es `fg` (theming §6.7 r7, firmado el 20). Renombrado antes de commitear. El
guard de nombres funciona — es el vocabulario DIMENSIONAL el que no cubre (ver
badge, hoy mismo).
⚠⚠ VEINTICUATRO CLAVES DEL CONTRATO NO PINTAN, Y ES UNA CASCADA
[data-form-submit], [data-form-reset] y los dos botones del array llevan también
data-button porque COMPONEN el Button canónico, y [data-button] casa con la
MISMA especificidad (0,1,0) pero carga DESPUÉS: gana button.css. Medido
propiedad a propiedad sobre el nodo real —altura, padding-inline, tamaño y peso
de letra, interlineado, hueco, radio, grosor de borde, el par de transición y la
opacidad deshabilitada—: NINGUNA se mueve.
Es el defecto que gradient-picker documentó (36 de sus 45 claves) y que
emoji-picker encontró hoy en su trigger. Allí la respuesta fue RETIRAR las
declaraciones muertas; aquí son 24 claves públicas, y eso es una decisión del
autor, no del que ejecuta. Quedan adjudicadas POR PATRÓN (una razón
arquitectónica escrita una vez) y anotadas en §13 con la pregunta de fondo, que
es la misma de §12.9 con el plano overlay: qué manda, el componente compuesto o
la receta que lo compone. Si mandan las recetas, se sube la especificidad y no
se retira nada; si mandan los compuestos, hay ~63 claves públicas que retirar en
tres componentes.
LAS DEMÁS ADJUDICACIONES SON RAMAS QUE LA DEMO NO MONTA
El layout grid (forzado alcanza — y ojo: su grid-template-columns sólo CAMBIA si
el valor altera el número de columnas; con 90px pasa de una columna de 502px a
cuatro de 110,5), el resumen de errores VACÍO (sin <ul> que indentar ni <a> que
subrayar) y toda la rama de ARRAY de AutoFields: el modo `auto` de la demo monta
group y field, nunca array, item ni widget.
Fuera por doctrina: los tres privados del panel son un CONMUTADOR (variante
plain contra panel) y los cuatro --_form-action-* leen el forward de paleta
THM-2.
VERIFICACIÓN
Sonda antes/después 0 diffs (832 valores · 7 estados) · R-5.4 22/64 con las 20 +
el patrón adjudicados, exit 0 · component:audit PASS (tras el renombrado) ·
--names 0 desviadas · censo --only 86 % · eidos-lint 36 morfo-backed / 0 invalid
/ 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo
conocido) · el ledger sigue en sus 63 hunks de prettier previos.
Rama con cuatro sesiones en paralelo: índice construido desde HEAD + sólo mis
hunks (base.ts por bloque), verificado con `git diff --cached`. No entra
docs/audit/theming/README.md — es un agregado global que ahora mismo mezcla
trabajo en vuelo de otras sesiones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Half its contract lives in parts the default stage does not mount: the
|
|
|
|
|
// AutoFields tree (array, item, widget) only exists in the demo's `auto`
|
|
|
|
|
// mode, and the panel chrome only under `variant='panel'`. 18 of 54 tokens
|
|
|
|
|
// moved before this.
|
|
|
|
|
form: {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-matches('^demo mode')) [data-uix-chip]:text-is('auto')"
|
|
|
|
|
],
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['plain', 'panel'] }
|
|
|
|
|
},
|
uix(spin-field): ADJUDICADO — 67 % es su techo, y lo dice la cabecera de su propio CSS
CERO claves nuevas, un literal firmado, siete adjudicaciones medidas. Diff de
computed 0 sobre 2.400 valores en 8 estados.
EL TECHO ESTABA ESCRITO, Y ES DOCTRINA
Ocho de sus knobs leen los privados RESUELTOS del Field
(--_field-control-height, --_field-control-padding-inline, --_field-control-gap)
porque los steppers y el scrubber IGUALAN la fila del input a cualquier talla.
La cabecera del CSS lo dice con estas palabras: «never a parallel --spin-field-*
chrome that would alias the Field base». Acuñar ahí sería el antipatrón que el
fichero prohíbe por su nombre. Préstamo con dueño —se tema en la ficha de
field— y el censo lo puntúa como deuda porque no sabe distinguirlo: el mismo
artefacto que listbox (68 %) y radio-cards (61 %) ya registraron.
Los dos factores del apilado (* 0.62 para el ancho del botón, * 0.34 para el
glifo) tampoco se acuñan: viven dentro de un calc() sobre ese mismo privado de
Field, así que un token propio no cambiaría quién manda.
SIETE ADJUDICACIONES, Y UNA SORPRESA
Los tres active-control-* no responden a :active NI forzando el pseudo por CDP:
su estado es el [data-scrubbing] que soma pone en el SCRUBBER mientras se
arrastra. Forzado el atributo sobre el nodo real, los tres alcanzan. Más
focus-control-bg (pseudo forzado por CDP), control-disabled-opacity (0.48 →
0.137) y el par de transición sin congelar (0,12s → 11,5s).
ES UNA CAPA, NO UN COMPONENTE
No tiene wrapper ni ruta propia, así que el guard lo mide dentro de
number-field — declarado ahora en `urls`, para que nadie tenga que saberlo.
⚠ Y una corrida de sonda dio 5 «nodos ausentes» que no eran nada: una recarga
que no montó la talla xl. Repetida, 0 diffs y 0 ausentes contra el mismo ANTES.
Un «missing node» tampoco se cree a la primera.
VERIFICACIÓN
Sonda antes/después 0 diffs · R-5.4 13/20 con las siete adjudicadas, exit 0 ·
component:audit sin hallazgos · censo 67 % con las ocho filas de préstamo
explicadas · docs:check 0 · el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A shared LAYER, not a component: no wrapper, no route of its own. It paints
|
|
|
|
|
// the stepper affordances of NumberField / CssField, so it is measured inside
|
|
|
|
|
// one of them.
|
|
|
|
|
'spin-field': { urls: ['/uix/components/number-field'] },
|
uix(meter): ADJUDICADO — 64 % → 75 %, su techo, y el guard medía la MITAD
CERO claves nuevas: su contrato de 28 ya cubría todo lo que es suyo. Dos
literales firmados, cuatro adjudicaciones medidas y tres filas que se quedan
fuera A PROPÓSITO. Diff de computed 0 (416 valores, 7 estados), capturas
antes/después idénticas.
EL GUARD MEDÍA LA MITAD DEL COMPONENTE
La demo arranca en shape='linear', así que los CATORCE tokens del anillo —doce
pasos de tamaño y grosor, más su pista y su centro— no tenían nodo que pintar:
12 de 28. Con el barrido de data-shape sube a 24/28.
LOS DOS border-radius DEL CÍRCULO NO SE ACUÑAN
Son lo que hace circular a shape="circular": el exterior del anillo y el agujero
del centro. Un --meter-circular-radius dejaría que un tema descirculara una
forma que el consumidor pidió POR NOMBRE — identidad, no knob, la misma lectura
que los dos 50 % de cropper.
Nota de coherencia con badge, donde SÍ acuñé dot-radius para un --radius-full:
allí el punto es un adorno cuya forma es estética (y knob.indicator-radius vale
--radius-sm, que lo prueba); aquí el redondeo ES el valor del atributo. La
prueba sigue siendo qué DECIDE el token, no qué valor tiene.
--_meter-value-pct es CANAL DE VALOR: el wrapper lo escribe inline con el
porcentaje, y de él salen la anchura de la barra y la parada del conic-gradient.
Y los dos 100 % son identidad (la barra lineal ES el ancho de su contenedor; el
indicador llena el alto de la pista), firmados en su declaración.
Adjudicado con medida: las dos tintas de zona fuera de `optimum` (forzadas
alcanzan) y el par de transición (sin congelar: 0,18s → 11,5s).
VERIFICACIÓN
Sonda antes/después 0 diffs · R-5.4 24/28, exit 0 · component:audit PASS · censo
75 % con las tres filas explicadas · eidos-lint 4 morfo-backed / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo
conocido) · capturas 2× miradas · el ledger sigue en sus 63 hunks previos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Half of it is the CIRCULAR shape (the ring: 12 size / thickness steps plus
|
|
|
|
|
// its track and centre) and the demo boots `linear`, so 14 of its 28 tokens
|
|
|
|
|
// had no node to paint.
|
|
|
|
|
meter: { sweepAttr: { attr: 'data-shape', values: ['linear', 'circular'] } },
|
uix(progress): temable — 69 % → 87 %, y el instrumento medía UNA de sesenta caras
Dos claves acuñadas, seis literales FIRMADOS y cuatro knobs adjudicados por
escrito. El default no se mueve: diff de computed 0 sobre 60 configuraciones y
capturas 2x idénticas byte a byte.
Censo: 69 % → 87 % (35 knobs → 30; público 24 → 26, global 3 → 2, literal 6 → 0,
excepción firmada 0 → 6). Contrato 41 → 43 claves. Global 67 % (no baja).
QUÉ ENTRA (2 claves)
row-gap (--space-2) + column-gap (--space-3). Era el ÚNICO knob de apariencia
que iba a un primitivo a pelo, y se parte en dos porque el atajo llevaba DOS
valores distintos: el hueco de fila separa la línea de texto de la barra, el
de columna separa el rótulo del valor. Un gap único habría nombrado un knob
para dos. El catálogo tiene las dos palabras (row-gap x10, column-gap x4) y el
molde exacto es chart.legend-row-gap / -column-gap.
QUÉ SE FIRMA EN VEZ DE ACUÑARSE (6 literales, fuera del ratio)
Cuatro 100 % y dos max-content: la geometría de la barra, no una decisión de
tema — llena la fila que le dan, o se encoge a su propio grosor / diámetro.
Cada uno con su anotación en la declaración (válvula de recipe-contract §3).
QUÉ SE RECHAZA DE LA PROPUESTA §4 (dos de sus tres filas)
indicator-radius — el --radius-full del anillo. Su GEMELO meter ya dejó el
veredicto escrito EN EL CSS: es lo que hace circular a shape='circular', y un
token dejaría que un tema des-redondease una forma que el consumidor pidió por
su nombre. Identidad, no knob; se copia el comentario del gemelo. Son los dos
únicos global que quedan, y el censo no puede sacarlos del ratio: la válvula de
la anotación sólo reclasifica la clase literal, nunca un global (mismo techo
que meter).
width: max-content — la §4 fundía en UN nombre dos nodos distintos (la raíz
vertical y la raíz circular) y los nombraba por el primer selector. Es el
literal de identidad de arriba.
EL TECHO HONESTO ES 87 %, NO 100 %
Los dos knobs private que quedan son el CANAL DE VALOR:
--_progress-value-pct lo escribe soma en el estilo inline del provider en cada
render (0-100 saturado) y la receta lo lee para el ancho del relleno lineal y
el alto del vertical. Un público encima mentiría — un tema no puede fijar el
progreso de una tarea. Misma clase que el z-index inline de drag-drop.
GUARD R-5.4: 40/43, TRES ADJUDICADOS CON SU MEDIDA
indicator-bg-loaded — sólo pinta bajo [data-state='loaded'], y la corrida
mantiene indeterminate encendido para alcanzar los tokens del barrido; forzado
sobre el nodo real: oklch(0.6406 0.1329 157.68) -> rgb(1,2,3).
transition-duration / -ease — los congela el guard por diseño; pasada sin
congelar: 0.18s -> 11.5s y cubic-bezier(0.4, 0, 0.2, 1) -> steps(7).
EL INSTRUMENTO MEDÍA UNA DE SESENTA CARAS
La sonda estándar ve 4 nodos — que son TODAS las partes del morfo — pero una
sola configuración: lineal, horizontal, loading, md. Medio contrato (el anillo,
el eje vertical, el barrido indeterminado, la tinta de completado) queda fuera
del diff, y el gate pasa en verde sin haberlo mirado. El guard lo dijo antes:
22 de 41 en la página por defecto. Con sweepAttr data-shape — el interruptor
que su gemelo meter YA tenía — sube a 34; con el interruptor indeterminate de
la demo, a 37; con el chip vertical, a 38. Cada control se comprobó por
separado, antes y después, porque montar más puede medir menos: aquí ninguno
tapó nada.
Y una trampa NUEVA, medida aquí: una sonda que CACHEA los nodos y luego mide en
bucle da 8.352 falsos diffs si el HMR reemplaza el subárbol a mitad de corrida.
getComputedStyle sobre un nodo DESACOPLADO devuelve la cadena vacía en TODAS
las propiedades, y el diff lee cada vacío como un cambio. Se arregla
re-consultando los nodos en cada instantánea, y se detecta haciendo fallar la
sonda ante un computed vacío. Pariente de hmr-stale-tab-phantom-findings.
ARTEFACTOS
Sonda estándar: 4 nodos, 800 valores x 7 estados -> 0 diffs.
Sonda de variantes (2 formas x 2 orientaciones x 3 estados x 5 tallas, elemento
y ::before): 17.280 valores -> 0 diffs.
Capturas 2x antes/después, lineal y circular: idénticas byte a byte.
Censo --only progress 87 % · component:audit PASS 1/1 · eidos-lint 0 invalid
(13 morfo-backed, 12 eidos-only) · vitest src/uix/eidos 439/440 (el único rojo
es el conocido skin-media-player en lint.test.ts) · rtl:check 0 · docs:check 0
· npm run check sin errores atribuibles a progress · prettier limpio en lo
tocado (progress.css estaba limpio en HEAD y sigue limpio; los dos ficheros del
centinela ya venían sucios de antes y mis líneas no añaden nada).
README de eidos con su sección «Talla y tema» y pestaña Tokens en la demo,
verificada en el navegador: 43 filas resueltas en vivo, 0 errores de consola.
NOTA DE ÁRBOL COMPARTIDO
Otra sesión trabaja sobre form en los mismos ficheros (base.ts,
generated/base.css, los dos del centinela y el índice de fichas). Lo indexado
se construyó a mano desde HEAD + sólo mis hunks, así que el commit no arrastra
su trabajo en curso; docs/audit/theming/README.md lleva sólo el delta de
progress y conserva la fila de form tal como está en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Same split as its twin: half the contract is the RING (12 size / thickness
|
|
|
|
|
// steps plus its track and its centre) and the demo boots `linear`.
|
|
|
|
|
progress: {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-is('indeterminate')) input[type=checkbox]",
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-is('orientation')) [data-uix-chip]:text-is('vertical')"
|
|
|
|
|
],
|
|
|
|
|
sweepAttr: { attr: 'data-shape', values: ['linear', 'circular'] }
|
|
|
|
|
},
|
uix(badge): temable — 59 % → 75 %, y subir un privado al contrato lo somete a reglas nuevas
Seis claves nuevas (65 → 71), dos literales firmados, cinco privados
adjudicados. Diff de computed 0 sobre 992 valores en 7 estados.
LA ALTURA DEL CHIP ERA EL HUECO
Badge monta su altura en la escala de CONTROL a propósito —para que `md`
signifique lo mismo lleve o no un control dentro (el ✕ removible es un
IconButton: medido, el mismo badge md daba 30px sin él y 46px con él)— pero lo
hacía desde un privado con cuatro fuentes por talla que leía --control-height-*
A PELO. Eso es una ESCALA, no un conmutador: sube al TSC como
min-block-size-{xs,sm,md,lg} + el resuelto, el molde que sus vecinas
padding-inline / gap / font-size ya tenían.
Y AL ENTRAR EN EL CONTRATO, LA SUITE LO CORRIGIÓ
El valor verbatim del CSS era el primitivo CRUDO (--control-height-{k}) y
recipe-css-contract exige la coordenada del BUNDLE (--size-{k}-control-height,
theming §5). Es la trampa que el handoff nombra —«la propuesta hereda el
incumplimiento del CSS de partida»— y que ya mordió a code-block: SUBIR UN
PRIVADO AL CONTRATO SOMETE SU VALOR A REGLAS QUE EN EL CSS NO SE LE APLICABAN.
Corregido; el diff sigue en 0.
dot-radius ES EL PAR QUE FALTABA
Redondo es el DEFECTO, no la definición: knob.indicator-radius vale
--radius-sm, así que el catálogo ya trata el radio de un marcador como knob.
Los dos 100% del glifo son identidad (el icono ES su ranura), firmados.
LOS CINCO PRIVADOS SON LAS TRES CLASES DE F2-B EN UN SOLO COMPONENTE
--_badge-bg / --_badge-fg son un CONMUTADOR (cambian de fuente con la variante:
track · solid · transparent, y aplanarlos obligaría a duplicar cada regla);
--_badge-fill-finish es el canal que el generador de degradados deriva de la
paleta de ESTA instancia; y --_badge-palette-* son forwards THM-2 que la capa de
color alimenta por instancia — un público encima dejaría que un tema los fijara
y matara el color= de cada chip.
LAS 40 CLAVES DE TONO NO MUEVEN NADA, Y YA SABÍAMOS POR QUÉ
El bloque genérico [data-badge][data-color] del forward de paleta se emite EL
ÚLTIMO y gana por orden a igual especificidad: la pintura sale del --palette-*
global. Es el hallazgo de cascada de paleta medido en button, badge y callout el
mismo día (§13, pendiente de firma) y entra como EXCEPCIÓN POR PATRÓN — una
razón arquitectónica escrita UNA vez, no cuarenta accidentes. Con ella y los dos
pasos de radius adjudicados (forzando data-rounded: 4px → 37px y 10px → 37px),
el guard queda en 29/71 y limpio.
EL INSTRUMENTO NO VEÍA TRES DE SUS CINCO PARTES
Dot, icon y remove son interruptores OPT-IN apagados por defecto: la sonda medía
2 nodos —raíz y etiqueta— y ninguna de las partes que la receta pinta.
Encendidos, 5. Misma clase que las capas opt-in de background.
⚠ component:audit da NEEDS-WORK y es PREEXISTENTE: R-1.5, sin tratamiento de
foco en la receta (grep focus-visible = 0 tanto en HEAD como después). El ✕ trae
el suyo del IconButton que compone; el chip no es focalizable. No se toca aquí:
darle foco mueve píxel y es decisión de diseño.
VERIFICACIÓN
Sonda antes/después 0 diffs (992 valores · 7 estados · 5 nodos con las tres
partes encendidas) · R-5.4 29/71 con el resto adjudicado (dos entradas propias +
una excepción por patrón), cero STALE · censo 75 % · eidos-lint 5 morfo-backed /
14 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite
eidos 439/440 (el rojo es skin-media-player, el conocido).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its dot / icon / remove parts are OPT-IN switches, all off by default.
|
|
|
|
|
badge: {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-is('dot')) input[type=checkbox]",
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-is('icon')) input[type=checkbox]",
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-is('removable')) input[type=checkbox]"
|
|
|
|
|
],
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['solid', 'soft', 'outline', 'ghost'] }
|
|
|
|
|
},
|
uix(navigation-menu): temable — 44 % → 100 %, y el guard cerraba el panel que medía
Bloque B1 de D-TH.4. Contrato 26 → 44 claves: 19 nuevas y UNA retirada.
Diff de computed 0 sobre 2.752 valores en 8 estados; R-5.4 43/44 con una
adjudicación escrita. Global 65 % (al 100 % 25 → 26).
LOS NOMBRES LOS DIO EL CATÁLOGO, NO YO
Ninguno se inventó. Los gemelos cerrados el mismo día (dropdown-menu 96 %,
context-menu 87 %) dan content-{min-width,padding,radius,fg} y
chevron-{size,stroke}; accordion da trigger-font-family y disabled-trigger-fg.
Las cuatro recetas que declaran un chevron lo dimensionan con un LITERAL
(0.42em · 0.5em · 0.625rem): un glifo no es un paso de la escala de espacio.
Al revés con el subrayado del indicador: su 2px ES --border-width-medium, y
dropdown-menu.separator-thickness dice que un grosor decorativo se nombra por
esa familia.
LA §4 GENERADA FALLA DE CINCO MANERAS, UNA NUEVA
Llamaba trigger-width / trigger-height al CHEVRON — nombrando por el primer
selector de la regla ([…-trigger]::after) y colisionando con trigger-height,
que YA EXISTE y es la altura del control. Fundía en content-radius dos nodos
con dos valores. Cruzaba los nombres: content-padding-* para lo que es de la
FILA, y el padding real del panel a «sin nombre mecánico». Llamaba
content-width a un min-inline-size. Y la nueva: abría exigiendo «consume la
capa compartida menu-indicator» — HOMONIMIA, esa capa es el check de los
CheckboxItem de dropdown/context-menu y no toca el subrayado de esta barra
(LAYER_VOCABULARY sólo registra calendar-surface).
LAS FILAS DEL PANEL NO SON item- NI link-
En los gemelos, `item` ES la fila. Aquí `item` es el <li> de la barra (parte
del morfo) y el Link de barra viste el cromo del trigger: --…-item-radius o
--…-link-radius prometerían a un tema un nodo que no alcanzan. Se acuñan
content-link-*, que nombra el nodo por donde vive. Su tinta sigue leyendo
trigger-fg, como venía.
EL PRIVADO ERA UNA ESCALA, NO UN CONMUTADOR
--_navigation-menu-list-gap tenía tres fuentes (0 / --space-1 / --space-2) por
data-size: sube al TSC como list-gap-{sm,md,lg} + el resuelto, el molde que el
bundle del trigger ya tenía al lado. Los dos bloques [data-size] del CSS se
retiran — los emite el TSC, y dejarlos vivos da un diff de 0 por la ruta vieja.
indicator-x ERA DECLARACIÓN MUERTA
Soma escribe las cuatro coordenadas del rect INLINE sobre el indicador
(-x, -y, -w, -h) y la receta lee sólo -w y -h; su propio comentario registra
que sumar translateX(var(--…-x)) DUPLICABA el desplazamiento. No la leía
nadie. Retirada, 0 diffs — la prueba de que estaba muerta (precedente: el
re-point de --command-radius, el trío item-gap* de carousel). -w se queda:
vive, y es canal de valor, no superficie de tema.
EL PANEL NO ACUÑA TIPOGRAFÍA, A PROPÓSITO
Lleva [data-depth='overlay'], y ese plano declara font-family / line-height a
la misma especificidad (0,1,0) y más tarde en la cascada: un token ahí leería
muerto. Es §12.9, pendiente de firma — la misma que hizo retirar claves en
menubar, dropdown-menu, context-menu y tooltip. Sí se acuña la familia de la
BARRA (trigger-font-family), que no es overlay y alcanza.
TRES COSAS QUE APRENDIÓ EL INSTRUMENTO (registradas en next-features §13)
1. Sonda y centinela eran CIEGOS A LA MITAD DEL COMPONENTE. Las filas del
mega-menú son los <a> DESNUDOS del consumidor, estilados por descendencia:
el filtro data-{c}-* veía 10 nodos y NINGUNO era el panel ni una fila — 9
de los 19 knobs fuera del diff. Con EXTRA_NODES / extraNodes la sonda pasa
a 16 nodos abiertos. Y el paso `open` de la sonda no honraba EXTRA_NODES.
2. EL GUARD CERRABA EL PANEL QUE MEDÍA. Este mega-menú abre con pointerenter
y pointerleave programa el cierre; el guard abría con clic y luego aparcaba
el puntero en (0,0) — justo el gesto que lo cierra. Dos corridas del mismo
código discrepaban. Y daba las DOS caras del error: falso negativo en los
tokens de la fila, falso positivo en content-link-padding-inline, que leía
«vivo» por ensanchar el panel que sí se medía. Con openBy: 'hover', dos
corridas idénticas.
3. `color` está EN TRANSICIÓN en esta parte: forzar data-disabled y leer en el
acto devuelve el valor VIEJO — tres diagnósticos falsos («ni el inline lo
mueve») hasta congelar. La trampa del cuentagotas del color-picker, en otro
componente. Congelado: reposo oklch(0.2435 0 0) → disabled oklch(0.61 0 0).
Y una de contabilidad, del instrumento y no del componente: el censo decide el
eje `size` grepeando data-size en el CSS, y el protocolo §7.4 ORDENA retirar
esos bloques. Todo componente que lo aplique bien aparece «sin eje». Ya le
pasa a sidebar y nav-tree. En §13.
VERIFICACIÓN
Sonda antes/después 0 diffs (2.752 valores · 8 estados · 16 nodos con el panel
abierto) · R-5.4 43/44 en dos corridas idénticas, adjudicación única
(disabled-trigger-fg: la demo no monta el estado; forzado alcanza en trigger Y
link, rgb(1,2,3)) · component:audit PASS · censo --only 100 % · eidos-lint 30
morfo-backed / 3 eidos-only / 0 invalid / 0 class-hooks · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido)
· check sin error nuevo en los ficheros tocados · Chrome con el panel abierto:
panel 8/10px/224px, fila 8/12/6px, trigger 36px/6px/gap 4px, indicador 2px.
Tras tocar el guard, command, table y tooltip re-verificados sin regresión ni
STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its mega-menu rows are the consumer's bare `<a>`, styled through
|
|
|
|
|
// `[data-navigation-menu-content] :is(a, …)` — no attribute of the
|
|
|
|
|
// component's own, so the filter never measured them. Measured 2026-08-23:
|
|
|
|
|
// `content-link-padding-block` and `-radius` read dead, while
|
|
|
|
|
// `-padding-inline` read LIVE off the panel it widens — a false negative and
|
|
|
|
|
// a false positive from the same blind spot. And its panel is a HOVER panel:
|
|
|
|
|
// soma opens on `pointerenter` and `pointerleave` schedules the close, so the
|
|
|
|
|
// pointer parking the guard does after a click (`mouse.move(0, 0)`) shut it
|
|
|
|
|
// MID-RUN — two runs of the same code disagreed on one token.
|
|
|
|
|
'navigation-menu': { openBy: 'hover', extraNodes: '[data-navigation-menu-content] a' },
|
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its parts are independent OPT-IN layers, each behind its own demo control,
|
|
|
|
|
// and only ONE pattern renders at a time: on the default stage the guard saw
|
|
|
|
|
// a single `glow` layer and 3 of 37 tokens moved. Switch the extra layers on,
|
|
|
|
|
// then sweep the pattern axis.
|
|
|
|
|
background: {
|
|
|
|
|
// ONLY the layers that do not COVER what is already being measured:
|
|
|
|
|
// switching the spotlight, the parallax speed and the pointer depth on
|
|
|
|
|
// repaints the pattern layer's own `background-image` and its transform,
|
|
|
|
|
// and the run went BACKWARDS (17 → 9 tokens). A guard that mounts more
|
|
|
|
|
// can measure less.
|
|
|
|
|
prepareWith: [
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-matches('^scrim')) input[type=checkbox]",
|
|
|
|
|
"[data-uix-control]:has([data-uix-control-label]:text-matches('^blur')) [data-uix-chip]:text-is('xl')"
|
|
|
|
|
],
|
|
|
|
|
sweepAttr: {
|
|
|
|
|
attr: 'data-pattern',
|
|
|
|
|
values: ['glow', 'mesh', 'grid', 'dots', 'noise', 'vignette', 'lines', 'rings']
|
|
|
|
|
}
|
|
|
|
|
},
|
uix(waveform): temable — 55 % → 86 %, y el playhead era el ÚNICO primitivo crudo
UNA clave nueva, cuatro literales firmados, un préstamo adjudicado. Diff de
computed 0 sobre 1.600 valores en 7 estados; R-5.4 11/11, CERO adjudicaciones.
LA CLAVE NUEVA NO LA PIDIÓ EL CENSO, LA PIDIÓ EL FICHERO
`playhead-bg`. Todas las demás líneas del re-tinte del Slider embebido leen ya
un token propio (--slider-secondary-bg: var(--waveform-buffered-fg)) o una
identidad (transparent / none / 0px / 1); el thumb era la ÚNICA que pasaba un
primitivo CRUDO (--color-content-primary) a un componente compuesto. Con eso,
repintar el playhead exigía mover --slider-thumb-bg (todos los sliders de la
app) o --color-content-primary (todo). Ahora es el par natural de
`playhead-width`, que ya existía. El molde es el re-tinte de media-player, que
reenvía --media-player-* en cada línea.
El censo no lo veía: una custom property reenviada a un componente compuesto no
entra en KNOB_PROPS (D-TH.2). La clase entera queda anotada en §13 — toda receta
que re-tinta un embebido puede pasarle primitivos crudos sin que nada lo vea.
EL 14 % QUE QUEDA ES UNA FILA, Y ES DOCTRINA
La onda deshabilitada lee var(--slider-disabled-opacity). La prueba de si un
token es tuyo no es qué valor tiene sino QUÉ DECIDE: aquí decide cuánto se apaga
un control deshabilitado, y eso es del Slider — la onda vive fuera de su caja
pero es la misma superficie a la vista. Un --waveform-disabled-opacity daría un
segundo mando para UNA decisión y permitiría desincronizar cromo y pintura.
Préstamo con dueño: se tema en la ficha de slider, como el 82 % de time-picker
se tema en la de field.
LOS CUATRO LITERALES SON IDENTIDAD, Y AHORA LO DICEN
La pintura ES la caja del provider (100% sobre un position:absolute; inset:0),
el área de arrastre ES la caja entera («scrubbing means grabbing anywhere on
it»), y un playhead abarca todo el alto por definición. Anotación
/* literal: */ en su declaración: desviación firmada, fuera del ratio.
EL INSTRUMENTO, CIEGO POR UNA CAUSA NUEVA: EL COMPONENTE COMPUESTO
La sonda medía 4 nodos —provider, svg y los dos paths— y NINGUNO era el
playhead: el cromo entero es un Slider embebido cuyos nodos llevan data-slider*.
Con extraNodes, 8. Es la TERCERA cara del mismo agujero: HTML crudo (prose),
partes que compone el consumidor (navigation-menu) y partes de un componente
compuesto (waveform). En §13, con la pregunta de fondo: hoy hay que sospecharlo
y contar los nodos, el instrumento no lo detecta solo.
VERIFICACIÓN
Sonda antes/después 0 diffs (1.600 valores · 7 estados · 8 nodos) · R-5.4 11/11
sin adjudicaciones · component:audit PASS · censo 86 % con la única fila
restante explicada · eidos-lint 4 morfo-backed / 16 eidos-only / 0 invalid / 0
class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es
skin-media-player, el conocido) · Chrome: playhead 2px × 40px, onda 544 × 40,
trazo 0.6 — idénticos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Same class: its chrome is an embedded Slider the recipe re-tints, and the
|
|
|
|
|
// playhead IS that slider's thumb — `data-waveform*` matched 4 nodes and none
|
|
|
|
|
// of them was it.
|
|
|
|
|
waveform: { extraNodes: '[data-waveform] [data-slider], [data-waveform] [data-slider] *' },
|
uix(audio-player): temable — 37 % → 56 %, y el instrumento medía CERO nodos
3 claves nuevas (5 → 8) y un literal anotado. El 44 % restante es PRÉSTAMO CON
DUEÑO, no deuda: ocho filas que leen `--media-player-*` porque este componente ES
la piel de audio de ese chasis, y una ficha nunca propone duplicar un token
prestado con prefijo propio (§5.3-3).
Acuñado lo único que ESTE reproductor añade sobre el chasis: el peso del título y
la insignia LIVE en versalitas espaciadas (peso + tracking). Los dos `600` pasan
a `var(--font-weight-semibold)`: el mismo valor, con nombre.
Y EL INSTRUMENTO ESTABA CIEGO DEL TODO: MEDÍA 0 NODOS. Tres razones encadenadas:
1. `/uix/components/audio-player` es un 404 — el CUARTO canónico sin ruta propia
(picker-shell, mockup, surface y éste). Se mide en la demo del chasis.
2. Sus partes no llevan `data-audio-player-*`: son `data-media-player-*`. Con
`extraNodes`, la sonda pasa de 0 a 48 NODOS.
3. Sólo existe tras el chip `media: audio` de esa demo: sonda (`DEMO_VARIANTS`) y
guard (`openWith` + `openMarker`) lo pulsan ahora, y el guard barre además las
cuatro variantes (card/row/bar/inline).
Con eso, 0/8 → 6/8. Las dos que quedan son la insignia LIVE, que sólo existe
mientras el proveedor refleja un directo: montada con `[data-live]`, alcanza
(600 → 333 y 1,28px → 7px).
Gates: sonda con 48 nodos (antes 0) · centinela 6/8 con las dos adjudicadas ·
censo 56 % · suite eidos con el rojo conocido ajeno · `rtl:check` 0 ·
`docs:check` 0 · equivalencia comprobada en el computado (título 600, tracking
1,28px).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// No route of its own (`/uix/components/audio-player` = 404, the FOURTH canon
|
|
|
|
|
// component without a demo). It is the audio skin of the media-player chassis:
|
|
|
|
|
// its parts are `data-media-player-*` and it only renders after the demo's
|
|
|
|
|
// `media: audio` chip. Measured there.
|
|
|
|
|
'audio-player': {
|
|
|
|
|
urls: ['/uix/components/media-player'],
|
|
|
|
|
openWith: ['[data-uix-control]:has([data-uix-control-label]:text-is("media")) [data-uix-chip]:text-is("audio")'],
|
|
|
|
|
openMarker: '[data-media-player][data-variant]',
|
|
|
|
|
extraNodes: '[data-media-player][data-variant], [data-media-player][data-variant] *',
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['card', 'row', 'bar', 'inline'] }
|
|
|
|
|
},
|
uix(surface): ADJUDICADO — el 25 % es su techo, y su ruta de demo es un 404
Cero claves nuevas: sus cuatro knobs son, uno a uno, doctrina bien aplicada.
Surface es la cara de PINTURA de Box, y lo que pinta no es suyo: `bg` y `fg`
salen del PUENTE DE PALETA (THM-2) —que es exactamente cómo un tema llega a
ellos, por el color— y `--_surface-fill-finish` es el CANAL DE VALOR del acabado
de degradado, que el generador deriva por instancia (§39). La rama `inherit` del
`fg` suave es intencionada: la tinta del contenido se queda en la global porque
el tinte del track es discreto, y está escrito en la receta.
PERO NO TIENE RUTA PROPIA: `/uix/components/surface` es un 404, como
`picker-shell` y `mockup`. El guard medía una página VACÍA y reportaba 0/25 —
incluido `radius`, que está vivísimo—. Apuntado a `/temas/gradientes` (cuatro
surfaces, dos variantes, tres colores) y a `/blocks/cta`: 1/25, y el que mueve es
el radio (10px → 1234px). Ya son TRES los componentes del canon sin demo → §13.
Sus 24 claves de tono son la SUPERSESIÓN de la cascada de paleta medida hoy en
`button`: el bloque genérico va el último y gana por orden. Adjudicadas con el
patrón compartido.
Gates: centinela 1/25 con las 24 adjudicadas · censo 25 %, que es el techo ·
`docs:check` 0 · sin cambios de código.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// No route of its own — `/uix/components/surface` is a 404, like picker-shell
|
|
|
|
|
// and mockup — so the guard measured an EMPTY page and reported 0/25. It is
|
|
|
|
|
// the paint side of Box: measured where it actually renders.
|
|
|
|
|
surface: { urls: ['/temas/gradientes', '/blocks/cta'] },
|
uix(banner): temable — 30 % → 100 %, y el guard pasa de 4/42 a 53/53
11 claves nuevas (42 → 53). Su contrato ya era grande —40 claves de intención—
pero su CROMO estaba a pelo: la densidad por talla, la interlínea y el trazo del
anillo de `outline`, que era la última fila que impedía al privado del borde
derivar de un público.
Dos ausencias firmadas: los `transparent` de `outline` y `ghost` y los tres
`border-width: 0`. La variante ES la ausencia de cromo — mismo criterio que
button, y por la misma razón: diez claves de `transparent` no son un contrato,
son ruido.
EL INSTRUMENTO NO VEÍA 38 DE SUS 42 CLAVES, Y NINGUNA ESTABA MUERTA. Dos
cegueras a la vez:
1. Su paleta resuelve por `data-intent`, NO por `data-color`. El barrido de tono
del guard —añadido esta mañana para button— estampaba sólo `data-color`. Ahora
estampa los DOS nombres: un componente selecciona por uno y el otro queda
inerte.
2. Y luego por VARIANTE: las ranuras `solid`/`contrast` sólo pintan en `solid`,
la de borde sólo en `outline`, y la demo monta una de cada. Con `sweepAttr`
sobre `data-variant`, el guard las recorre.
Con las dos, 4/42 → 53/53. Es la diferencia entre «38 tokens que mienten» y «38
tokens que nadie había mirado con el atributo puesto»: exactamente lo que R-5.4
existe para distinguir.
Gates: sonda 0 diffs en 192 valores × 7 estados (la demo monta UNA franja) ·
centinela 53/53 sin una sola adjudicación · censo 100 % · `component:audit` PASS
· suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 ·
captura de las cuatro variantes con cuatro intenciones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its palette resolves per INTENT and then per VARIANT: the solid/contrast
|
|
|
|
|
// slots only paint under `solid`, the border slot only under `outline`, and
|
|
|
|
|
// the demo mounts one of each. 38 of its 42 tokens read dead without the
|
|
|
|
|
// sweep (2026-08-23).
|
|
|
|
|
banner: { sweepAttr: { attr: 'data-variant', values: ['soft', 'solid', 'outline', 'ghost'] } },
|
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA
7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES
filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en
una regla inválida.
Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el
propio comentario del CSS dice que el hover usa «el mismo color que el anillo de
selección, más claro» y lo que los separa es la opacidad, que ya tenía token—,
la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos
piezas del glifo `+`→`×`. Dos limpiezas de paso: un
`var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí
mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)`
con un respaldo que no puede ocurrir.
Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN
un atenuado (sin atenuar es 1 por definición) y la caja del icono.
⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de
SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta
la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el
`70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el
centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito
encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL.
Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa
—la lección estaba escrita y se repitió—. → §13.
Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el
motor de color, como los rellenos de cada sector. No es superficie de tema.
TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas:
1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir»
lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya
casa, no se pulsa nada (8 → 14 de golpe).
2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden
con `extraNodes` — 37 nodos en reposo contra los 5 con atributo.
3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO:
`rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la
animación congelada computa 0,45 y sigue al token. → §13.
Los cuatro tokens del rim son del look `luminous`, que la demo del componente no
renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo
que forzar): medidos en `/active/docs/agnt`, que sí lo usa.
Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19
con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos
con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin
errores propios · capturas de reposo, sector en hover y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Same class-hook blindness as prose, inside an SVG: sectors, labels, icons
|
|
|
|
|
// and the trigger glyph carry no `data-onion-*`.
|
|
|
|
|
'onion-menu': { openMarker: '.onion-menu-sector', extraNodes: '.onion-menu-sector, .onion-menu-label, .onion-menu-icon, .onion-menu-icon svg, .onion-menu-trigger, .onion-menu-trigger-glyph' },
|
uix(field): temable — 73 % → 98 %, y sus 18 rojos estaban VIVOS
8 claves nuevas (60 → 68) y 8 literales ANOTADOS. Lo que falta es el PUENTE DE
PALETA (`_palette-text`, THM-2), que no es deuda. Global 63 %.
Field es el componente más COMPUESTO del sistema: lo que otras fichas llaman
«préstamo con dueño» se cobra aquí, así que su techo pesa más que el de nadie.
Acuñados el DISPARADOR DE CONTROL —el icono que abre el calendario, el reloj o
la muestra, y revela la contraseña: `size`, `radius`, `fg` y sus dos estados—,
el indicador `optional` (gemelo del `required`, que ya tenía token) y el lateral
de la etiqueta flotante, que es lo que CORTA el borde bajo el texto. La caja del
disparador se mide en `em` a propósito, así que el token guarda la RAZÓN
(`1.75em`) y no un píxel por talla.
OCHO LITERALES PASAN A EXCEPCIÓN FIRMADA POR IDENTIDAD, no para inflar el ratio:
cuatro `inline-size: 100%` (el campo, el control, el input y el control flotante
llenan su fila), dos `line-height: 1` (un glifo suelto no tiene interlínea) y
dos `1em` del icono dentro del disparador (el glifo ES el tamaño del texto).
LOS 20 ROJOS DEL GUARD ESTABAN VIVOS, uno por uno, y tres enseñan algo:
- `disabled-opacity` vive en el CONTROL, no en la raíz: el guard leía el nodo
equivocado.
- `control-placeholder-fg` no leía porque el input de la demo NO TIENE
`placeholder`: sin el atributo no existe la caja `::placeholder`, y
`getComputedStyle(nd, '::placeholder')` devuelve entonces el estilo del
ELEMENTO. Con un placeholder puesto, alcanza. Es genérico → §13.
- `segment-height` es un token de FAMILIA que field posee y los pickers
consumen —en las rutas de field no lo lee nadie— y además es RESUELTO
(declarado por `[data-field][data-size]`), así que escribirlo en `:root`
pierde POR DISEÑO: el tema mueve la coordenada. Medido en `/time-picker`:
desde `:root` nada, desde el host del campo 28px → 1234px, y moviendo
`--field-control-height-md` 28px → 992px.
El resto son estado (invalid, disabled, readonly, foco), variante ghost,
orientación horizontal, la franja de segmentos, el texto de error —que sólo se
RENDERIZA con el interruptor de la demo: forzar el atributo no hace que Svelte
monte el nodo— y los dos de transición, que el guard congela.
Instrumento: el disparador de control no lo monta NINGUNA demo de field, así que
el guard mide ahora `field` sobre DOS rutas (`/field` + `/date-picker`).
Gates: sonda 0 diffs en 2.280 valores × 8 estados · equivalencia del disparador
comprobada aparte en `/date-picker` (28px = 1.75em con fuente 16px · 4px =
`--radius-sm` · misma tinta) · centinela 48/68 con las 20 adjudicadas · censo
98 % · `--names` 0 · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 ·
suite eidos con el rojo conocido ajeno · `check` sin errores propios · capturas
de reposo, invalid, disabled, etiqueta flotante y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The shared control trigger (the icon affordance a picker drops inside the
|
|
|
|
|
// field) is mounted by NO field demo: /field has plain inputs and the
|
|
|
|
|
// segmented fields have segments, not triggers. It lives on the pickers, so
|
|
|
|
|
// its four tokens read dead on the field route alone.
|
|
|
|
|
field: { urls: ['/uix/components/field', '/uix/components/date-picker'] },
|
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 bar drops TWO different surfaces and only one carries its public knobs:
|
|
|
|
|
// the first entry opens a role=menu Content (chrome owned by dropdown-menu),
|
|
|
|
|
// while the role=dialog Panel — z, min-width, max-height, padding, radius,
|
|
|
|
|
// typography — hangs off the "Format" entry. Opening the first trigger left
|
|
|
|
|
// every panel token dead. The marker is the panel, not `-content`: with the
|
|
|
|
|
// default marker the re-open guard closed it again on every token.
|
|
|
|
|
menubar: {
|
|
|
|
|
openWith: ["[data-menubar-trigger][data-menubar-value='format']"],
|
|
|
|
|
openMarker: '[data-menubar-panel]'
|
|
|
|
|
},
|
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: with a plain
|
|
|
|
|
// click the panel never enters the document and the guard measured ONE node.
|
|
|
|
|
'context-menu': {
|
|
|
|
|
openBy: 'contextmenu',
|
|
|
|
|
openWith: ['[data-uix-stage-area] [data-context-menu-trigger]']
|
|
|
|
|
},
|
uix(nav-tree): temable — 4 % → 96 %, y 18/18 en el guard sin una sola excepción
17 claves nuevas (1 → 18): tenía UNA sola pública (`indent`) y todo lo demás iba
a primitivo crudo. Censo nav-tree 4 % → **96 %**; global del eje 61 %.
LA FILA ES LINK + TRIGGER A LA VEZ. Las reglas los tratan juntos —mismo padding,
misma tinta, mismo radio, mismo hover— así que el knob es de la FILA (`row-*`),
no de cada parte: es una pseudo-parte, el molde firmado de las regiones de
`waveform`. Sus estados llevan el modificador DELANTE, que es lo que la gramática
pide para lo interactivo (`hover-row-fg`, `active-row-fg`, `disabled-row-fg`), y
la estela —los ancestros de la página actual— es contextual y va DETRÁS
(`trail-row-fg`).
El disparador sí tiene lo suyo: el suelo de 24 px del blanco táctil (WCAG 2.5.8)
y su padding. Y el galón es una escuadra dibujada con dos bordes (`::after` con
`border-inline-end` + `border-block-end` girado 45°): su lado es un knob y el
grosor del trazo otro — mismo molde que la esquina de `text-focus`.
LA SUITE CAZÓ UN INCUMPLIMIENTO HEREDADO, tercera vez en el eje: el contrato
copió `var(--font-size-sm)` verbatim del CSS, y ésa es la coordenada CRUDA que
`recipe-css-contract` prohíbe en un token de receta. Corregido al bundle
(`var(--size-sm-font-size)`), que computa idéntico. El «valor verbatim» arrastra
el defecto del CSS de partida siempre que el CSS ya lo tuviera.
DOS CEGUERAS MÁS DEL GUARD, medidas y arregladas:
1. **La fila es un `<div>` pelado sin atributo propio**, así que el `gap` entre
link y trigger leía muerto moviendo 4px → 1234px.
2. **No leía el ANCHO de un borde que no fuera el de arriba**: el galón tiñe los
de inline-end y block-end. Añadidos los tres lados que faltaban (los COLORES
por lado ya se añadieron con `prose`, hoy mismo).
Con las dos: **18/18 en vivo, sin una sola excepción**. Los nueve componentes con
ledger re-verificados tras tocar las PROPS del guard: sin regresiones ni STALE.
Lo que queda fuera: el `100%` de la fila (identidad) y el anillo de foco
(sistema).
Gates: sonda antes/después **0 diffs en 22.140 valores** (102 nodos × 8 estados) ·
centinela 18/18 · censo --only 96 % · `--names` sin desviadas · component-audit
PASS · eidos-lint 0 invalid · suite eidos con el único rojo conocido · rtl:check
0 · docs:check 0 · captura del escenario mirada (raíl, galones, estela y fila
activa intactos).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The row that holds link + trigger is a bare <div> with no attribute of its
|
|
|
|
|
// own, so the gap between them read dead (measured 2026-08-22).
|
|
|
|
|
'nav-tree': { extraNodes: '[data-nav-tree-item] > div' },
|
uix(aura): temable — 43 % → 80 %, y doce silencios que son diseño
13 claves (24 → 37). Censo aura 43 % → **80 %**; global del eje 61 %.
Su texto de estado y su detalle bajan UN paso del bundle por talla, y las TRES
coordenadas viajan juntas (tamaño, interlineado, tracking): doce declaraciones,
doce valores distintos, doce claves — 1:1, sin inflación. El valor sigue siendo
del bundle; el knob pasa a ser del componente. La decimotercera es
`idle-opacity`: el reposo no desaparece, se retira a media opacidad.
Fuera por identidad: el `--radius-full` del anillo (redondo por definición), los
cuatro `100%` del canvas y del SVG, y el `1px` × 2 de la región viva — la técnica
sr-only, la misma de `text-blur`.
DOCE ADJUDICACIONES, TODAS MEDIDAS, Y NINGUNA ES DEUDA:
- **Las seis tintas de estado y el halo pintan el FONDO DEL INDICADOR**, y la
demo corre con el canvas WebGL montado: la regla
`[data-aura-indicator]:has(canvas)` pone `background: none` A PROPÓSITO — el
orbe ES la presencia, y un disco detrás enturbia su silueta. Quitando el canvas
en vivo, el gradiente vuelve y las siete siguen al token
(oklch(0.6434 0 0) → rgb(1,2,3) dentro del radial-gradient).
- **El arco y el halo son SLOTS del anillo que sólo existen en los estados de
trabajo**; la demo se queda en la pista. Montados como
`<circle data-aura-ring-slot>` dentro del SVG real —que es exactamente lo que
el CSS selecciona—: 2px → 1234px, 1px → 1234px, 0.5 → 0.123.
- Las dos de transición, congeladas por el guard como en todo el catálogo.
Y una ceguera más del instrumento, la tercera de la sesión: **el guard no veía el
SVG del anillo** porque sus `<circle>` no llevan atributo `data-aura*`.
`extraNodes` los mete en el conjunto medido.
Gates: sonda antes/después **0 diffs en 1.710 valores** (9 nodos × 7 estados) ·
centinela 25/37 con las doce adjudicadas · censo --only 80 % · `--names` sin
desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The ring is an SVG: its arc and halo are `<circle>` children with no
|
|
|
|
|
// `data-aura*` attr, so three live tokens read dead (measured 2026-08-22).
|
|
|
|
|
aura: { extraNodes: '[data-aura-ring] svg *' },
|
uix(chat-log): temable — 77 % → 100 %, y el instrumento daba DIECISÉIS falsos muertos de 31
Protocolo PLAN-theming §7 sobre `chat-log`. Censo 77 % → **100 %**, contrato
31 → **35** claves, centinela **34/35** con 1 adjudicada. El default NO se
mueve: diff de computed VACÍO (1.920 valores · 7 estados · 9 nodos), capturas
2x antes/después idénticas.
## Lo caro no fue acuñar: fue VER
El guard leía **15/31** antes de tocar una línea de código, y las dieciséis
"muertas" estaban las dieciséis VIVAS. Tres cegueras, ninguna del componente:
1. **El viewport que scrollea es del componente COMPUESTO.** El log pinta su
`padding-inline` / `padding-block` sobre `[data-virtual-list-viewport]`, un
nodo sin un solo `data-chat-log-*`: fuera del filtro de la sonda y del
guard. Cuarta forma del mismo punto ciego (prose · navigation-menu ·
waveform).
2. **La demo no montaba TRES de las seis partes.** Separador de día y divisor
de no-leídos no existían en el escenario, y la fila de evento vivía en el
índice 0 de una lista virtualizada anclada al FINAL — fuera de la ventana.
Once claves sin nodo que pintar. La demo los monta ya cerca del final
(instrumento: lo que la demo no monta, nadie lo mide).
3. **La pastilla del separador es un `> span` sin atributo** — seis claves más.
Con las tres arregladas, **31/31 antes de acuñar nada**.
## Lo que entra (4 claves, la COSTURA de los cuatro globales)
`divider-padding-block` · `event-radius` · `pill-gap` · `to-latest-radius`.
El valor sigue siendo el del sistema; el knob pasa a ser del componente.
Los nombres salen del CATÁLOGO, no del generador: la ficha proponía
`divider-unread-padding-block` y `pill-new-gap` —nombra por el atributo de la
parte— cuando el contrato lleva `divider-fg` / `-line` / `-font-size` / `-gap`
y `pill-bg` / `-fg` / `-radius`. La familia manda.
## Lo que SALE: una regla que no ha pintado nunca
`[data-chat-log-to-latest] svg { block-size: 1em; inline-size: 1em }` no puede
ganar: el `Icon` compuesto emite `style="width: var(--icon-size-sm); height:
..."` EN LÍNEA, y un estilo en línea gana a todo selector. El glifo mide 16 px
(paso `sm` del icono), no los 14 px que `1em` daría sobre la fuente heredada
del botón. **Retirada: 0 diffs**, que es la prueba de que estaba muerta. Es la
clase `carousel.item-gap` / `drag-drop.preview-z`, con una vuelta más: aquí el
inline lo escribe un COMPONENTE COMPUESTO, no soma.
## Identidades firmadas (3, fuera del ratio)
`block-size: 100%` del shell y del viewport (el log ocupa la caja que la app le
da) e `inline-size: fit-content` de la pill (la dimensiona su contenido).
## Adjudicada (1)
`event-radius` — sólo pinta bajo `:focus-visible` y el guard hace blur tras
abrir. Medido enfocando con TECLADO la fila real, con `transition` congelada:
0 px en reposo, 4 px enfocada, 1234 px con el centinela.
## Guards
censo `--only chat-log` 100 % (global 67 %, no baja) · `component-audit` PASS ·
`eidos-lint` 0 invalid · `vitest run src/uix/eidos` sin rojos nuevos (los dos
vivos son ajenos: `skin-media-player` y `radio-group.row-min-height-touch`) ·
`rtl:check` 0 · `docs:check` 0 · `check` sin un solo error del componente ·
prettier limpio sobre el contenido normalizado a LF.
README con su sección «Talla y tema», demo con pestaña `Tokens` verificada en
navegador, ficha con su veredicto §5 y registro en PLAN §8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The scrolling viewport is the composed VirtualList's — it carries
|
|
|
|
|
// `data-virtual-list-viewport`, not a `data-chat-log-*` attr — so the shell's
|
|
|
|
|
// own inline/block padding, painted on it, had no node in the filter; the
|
|
|
|
|
// to-latest glyph is a bare `<svg>` (measured 2026-08-23).
|
|
|
|
|
'chat-log': {
|
|
|
|
|
extraNodes:
|
|
|
|
|
'[data-chat-log] [data-virtual-list-viewport], [data-chat-log-to-latest] svg, [data-chat-log-separator-day] > *'
|
|
|
|
|
},
|
uix(chat-composer): temable — 79 % → 100 %, y file-upload le gana el disabled desde FUERA
Protocolo PLAN-theming §7 sobre `chat-composer`. Censo 79 % → **100 %**,
contrato 31 → **33** claves, centinela **29/33** con 4 adjudicadas. El default
no se mueve.
## La mitad de su contrato no la montaba la demo
El guard leía 14 muertas de 31, y once eran la barra de contexto (`context-*`,
opt-in tras un chip) más la bandeja de adjuntos (`attachments-gap`, opt-in tras
un botón). Con las dos encendidas por `prepareWith`: **29/33 antes de tocar el
código**. La sonda mide ya 9 nodos, con los dos glifos.
## Lo que entra: DOS claves donde el generador pedía cuatro
`context-close-size` y `context-close-radius`. El chip de descarte es un
control CUADRADO, así que es UNA clave para los dos ejes — la forma que
`send-size` ya usa en esta misma receta. El aviso del generador ("`var(--size-
xs-control-height)` / `1em`, dos valores") era un falso conflicto: metía en el
mismo saco la CAJA (26 px) y su GLIFO.
## Lo que SALE: cuatro declaraciones que no han pintado nunca
`[data-chat-composer-context-close] > svg` (`1em`) y
`[data-chat-composer-send] > svg` (`1.1em`). Los dos glifos son `Icon`
compuestos y el `Icon` emite `style="width: var(--icon-size-{k}); height: ..."`
INLINE: ningún selector gana a un estilo en línea. Medido — el cierre pinta
14 px (`--icon-size-xs`) contra los 13,3 px que daría `1em`, y el envío 16 px
(`--icon-size-sm`) contra los 14,7 px de `1.1em`. Retiradas: **0 diffs**.
Misma lección que `chat-log` esta misma tarde, con dos glifos en vez de uno.
## Y una colisión ENTRE RECETAS, que no es del instrumento
`--chat-composer-disabled-opacity` no alcanza, y no por un hábito del guard:
`file-upload` barre DESCENDIENTES. `[data-file-upload] [data-disabled]` pesa
(0,2,0) — lo mismo que `[data-chat-composer-send][data-disabled]` — y su receta
se emite MÁS TARDE, así que gana sobre cualquier nodo deshabilitado dentro de
un FileUpload. Que es exactamente donde vive el composer en su composición
DOCUMENTADA (su propio README y su demo lo envuelven en uno).
Medido sobre los nodos REALES quitando el atributo del ancestro: fuera del
envoltorio alcanza (0.4 → 0.123 en el send Y en el shell). Los dos defaults son
`--opacity-disabled`, así que la colisión es INVISIBLE hasta que un tema mueva
uno de los dos. **No se arregla aquí** —tocar `file-upload` es cascadear—: se
adjudica con su medida.
Las otras tres adjudicaciones son hábitos conocidos del guard: `focus-border`
(sólo bajo `:focus-within`, y el guard blurea tras abrir; enfocado el textarea
real, `color(srgb 0.745 0.577 0.894 / 0.48)` → `rgb(1, 2, 3)`) y el par
`transition-duration` / `-ease`, que ES la transición que el guard congela para
poder medir todo lo demás (0,12 s → 4,321 s sin congelar).
## Identidad firmada (1)
El `inline-size: 100%` del textarea, que ocupa su celda de la rejilla.
## El diff de computed y su residuo, medido
2.208 valores, 8 estados, 9 nodos. Idénticos salvo 2-4 de
`[data-chat-composer-attach]` `blockSize`, que flota entre 30 px y 36 px —
**y flota igual corriendo dos veces el MISMO código** (control after vs after2:
2 diffs, mismo nodo, misma propiedad). La causa, medida: en una recarga de cada
ocho el `Button size="sm" variant="ghost"` que la demo compone en esa ranura
computa 36 px **y fondo `oklch(0.5556 0.1829 305.86)`** —el sólido primario—
con sus `data-size` y `data-variant` puestos: las reglas de talla y variante de
`button` llegan tarde en el servidor de desarrollo. Un nodo que esta receta no
dimensiona, y una carrera de CSS del dev server. Es también lo que salió en la
captura "antes" (el clip sujetapapeles relleno), y por eso la de "después" se
tomó cuatro veces: las cuatro idénticas entre sí.
## Guards
censo `--only chat-composer` 100 % (global 68 %, no baja) · `component-audit`
PASS · `eidos-lint` 0 invalid · `vitest run src/uix/eidos` sin rojos nuevos (el
único vivo es el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0
· `check` sin un solo error del componente · prettier limpio sobre el contenido
normalizado a LF.
README con su sección «Talla y tema», demo con pestaña `Tokens` verificada en
navegador, ficha con su veredicto §5 y registro en PLAN §8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its context bar (ten of its tokens) and its attachment tray are OPT-IN and
|
|
|
|
|
// the demo boots with neither: 14 of 31 read dead on the default stage
|
|
|
|
|
// (measured 2026-08-23).
|
|
|
|
|
'chat-composer': {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("context")) [data-uix-chip]:text-is("reply")',
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("simulate")) button:has-text("attach file")'
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(chat-message): temable — 84 % → 100 %, y las TREINTA muertas eran ejes que la demo no enciende
Protocolo PLAN-theming §7 sobre `chat-message`, el mayor de la familia chat.
Censo 84 % → **100 %**, contrato 75 → **81** claves, centinela **77/81** con 4
adjudicadas. El default NO se mueve: diff de computed VACÍO (4.192 valores · 8
estados · 16 nodos) y capturas 2x antes/después **idénticas byte a byte**, en
reposo y con la barra tapback abierta.
## Otra vez, lo caro fue VER
El guard leía **45/75** antes de tocar una línea de código. Una fila de chat es
una PILA de superficies opt-in (reply citado, read-by, estado de entrega,
mención) y de ejes de PRESENTACIÓN (`run`, `direction`, `emphasized`) que la
demo arranca en su posición más pobre: `solo` + `in`, sin ninguna de las cuatro.
Con las superficies encendidas por `prepareWith` y los cinco ejes barridos como
PRODUCTO: **71/75 antes de acuñar nada**. La sonda pasó de 12 nodos a 16 en
reposo, y a 24 con el panel abierto.
## Lo que entra (6 claves, la COSTURA de los seis globales)
`focus-radius` · `mentioned-fg` · `reply-fg` · `reaction-fg` ·
`reaction-inner-gap` · `quick-react-radius`. El valor sigue siendo el del
sistema; el knob pasa a ser del componente.
**Los nombres salen del CATÁLOGO, no del generador.** La ficha proponía `radius`
(colisiona con `emphasize-radius`, que ES la esquina en reposo de la MISMA
fila), `bubble-fg` (el knob es de la MENCIÓN: `mentioned-bg` y
`mentioned-accent` ya existían), `reaction-width` + `reaction-height` para el
glifo (`reaction-height` ya existe y es la altura del CHIP) y
`quick-reactions-width` para un `max-content` que es identidad.
## El caso que obligó a decidir: `reaction-gap` ya existía con otro sentido
La receta la pinta en la fila `reactions` — el hueco ENTRE chips — mientras el
knob nuevo es el ritmo INTERNO del chip, el emoji y su cuenta. Mismo valor
(`--space-1`), dos papeles: un chip más denso no es una fila más apretada.
Reusar la clave habría cambiado en silencio lo que mueve un tema que ya la usa,
así que entra como `reaction-inner-gap`. El resto del bloque `{parte}-gap` de
esta receta (`meta-gap`, `read-by-gap`, `header-gap`, `actions-gap`) significa
«dentro de la parte»: la anómala es la vieja, y renombrarla es churn con riesgo.
## Lo que SALE: una regla que no ha pintado nunca
`[data-chat-message-reaction] svg, [data-chat-message-reaction-add] svg` fijaba
`1.1em` en los dos ejes «porque un svg de sólo viewBox computa 0x0». El glifo
por defecto es el `Icon` compuesto, que emite `style="width:
var(--icon-size-sm); height: ..."` EN LÍNEA, y un estilo en línea gana a todo
selector. Medido: **16 px** (el paso `sm` del icono), no los 15,4 px que `1.1em`
daría sobre los 14 px heredados. La otra mitad del selector no tiene nodo: los
chips llevan un emoji de texto. **Retirada: 0 diffs.** Tercera vez en la familia
(chat-log y chat-composer, ayer).
## Identidades firmadas (4, fuera del ratio)
`inline-size: max-content` de la barra tapback (la barra ES su fila de emoji),
`line-height: 1` de la celda (la celda es la caja del glifo) y los dos
`opacity: 1` de la pastilla de acciones (el final del fundido 0 → 1).
## Adjudicadas (4) — las cuatro son límites del INSTRUMENTO, medidas a mano
- `bubble-tail-radius` — el guard fotografía UNA esquina (`borderTopLeftRadius`)
y la cola es la de ABAJO. Medido en las 8 combinaciones dirección x run:
4 px → 1234 px en `solo` y `last`, en las dos direcciones. Su hermana
`bubble-radius-run` lee viva porque `middle` aplana la esquina START, que sí
está en la lista.
- `emphasize-duration` — ES la transición que el guard congela para medir todo
lo demás. Sin congelar: 0,24 s → 4,321 s.
- `focus-radius` — la fila sólo es tabulable compuesta DENTRO de un Feed (soma
fusiona la identidad de `Feed.Article` cuando hay Feed ancestro) y su propia
demo la monta suelta: `:focus-visible` no tiene nodo ahí. Medido en la demo de
`chat-log`, donde las filas SÍ son artículos de feed (`tabindex=0`): reposo
6 px, enfocada 4 px → 1234 px, por foco programático y por recorrido con Tab.
- `quick-react-hover-scale` — `transform` no está en la lista de propiedades del
guard (punto ciego ya adjudicado en background / rating-group / card) y la
celda es un botón PORTALADO con regla `:hover`. Con puntero real:
`matrix(1.18…)` → `matrix(7.77…)`.
## Un defecto real medido y NO corregido aquí (mueve píxel)
El chip de añadir reacción lleva también `data-popover-trigger`, y
`[data-popover-trigger]:not([data-archetype='field-trigger'])` pesa (0,2,0)
contra los (0,1,0) de `[data-chat-message-reaction-add]`: su fondo, borde,
radio, tinta, altura, padding y tamaño de letra los pinta `popover.css`. Se ve
en la captura — los dos chips de reacción son píldoras redondas y el de añadir
es un cuadrado gris. Los `reaction-*` no mienten (alcanzan sobre los chips
normales), así que no hay nada que retirar: es una decisión de diseño. Clase
`gradient-picker` / `emoji-picker`. Queda REGISTRADO, no tocado (D-TH.5).
## Guards
censo `--only chat-message` 100 % (global 68 %, no baja) · `component-audit`
PASS · `eidos-lint` 0 invalid (37 morfo-backed / 41 eidos-only) · `vitest run
src/uix/eidos` sin rojos nuevos (el único vivo es el conocido
`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un
solo error del componente · prettier limpio sobre el contenido normalizado a LF.
README con su sección «Talla y tema», demo con pestaña `Tokens` (81 claves,
verificada en navegador) y ficha con su veredicto §5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A conversation row is a pile of OPT-IN surfaces and PRESENTATION axes, and
|
|
|
|
|
// the demo boots the plainest of them: no quoted reply, no read receipts, no
|
|
|
|
|
// delivery status, no mention accent, `run='solo'` and `direction='in'`.
|
|
|
|
|
// Thirty of its seventy-five tokens read dead on that stage — every one of
|
|
|
|
|
// them alive on a row that carries the axis. The four surfaces come from the
|
|
|
|
|
// demo controls; the five axes are swept as their product (the run corners,
|
|
|
|
|
// the outgoing palette, the two delivery tints, the mention and the
|
|
|
|
|
// jump-to-message flash). The quick-reaction tapback bar is PORTALED off the
|
|
|
|
|
// add-reaction chip, and the gap it paints sits on the composed
|
|
|
|
|
// `[data-popover-viewport]` (measured 2026-08-24).
|
|
|
|
|
'chat-message': {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("reply (quote)")) input[type=checkbox]',
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("read-by")) input[type=checkbox]',
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("delivery")) [data-uix-chip]:text-is("read")'
|
|
|
|
|
],
|
|
|
|
|
openWith: ['[data-chat-message-reaction-add]'],
|
|
|
|
|
openMarker: '[data-chat-message-quick-reactions]',
|
|
|
|
|
extraNodes:
|
|
|
|
|
'[data-chat-message-quick-reactions] [data-popover-viewport], [data-chat-message-reaction-add] > svg',
|
|
|
|
|
sweepAttr: [
|
|
|
|
|
{ attr: 'data-run', values: ['solo', 'middle', 'last'] },
|
|
|
|
|
{ attr: 'data-direction', values: ['in', 'out'] },
|
|
|
|
|
{ attr: 'data-delivery', values: ['delivered', 'read', 'failed'] },
|
|
|
|
|
{ attr: 'data-mentioned', values: [null, ''] },
|
|
|
|
|
{ attr: 'data-emphasized', values: [null, ''] }
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(chat-typing): temable — 89 % → 100 %, con UNA clave y siete de sus ocho ya vivas
Protocolo PLAN-theming §7 sobre `chat-typing`. Censo 89 % → **100 %**, contrato
7 → **8** claves, centinela **8/8** con **cero adjudicadas**.
## Lo que entra: una clave, y es la COSTURA
`dot-radius` (`var(--radius-full)`), el único knob que leía el sistema a pelo.
El valor sigue siendo el del sistema; el knob pasa a ser del componente.
**El nombre sale del CATÁLOGO, no del generador.** La ficha proponía
`indicator-radius` —nombra por el primer selector de la regla,
`[data-chat-typing-indicator] > span`— cuando el nodo que PINTA es el punto y el
contrato ya habla `dot-size` / `dot-gap` / `dot-fg`. Es el defecto de
`tree-view` («root-bg» para una guía de indentación) en pequeño.
## Lo que el instrumento no veía: 3 nodos de 8
Los tres puntos son `> span` DESNUDOS del indicador —sin `data-chat-typing-*`,
la clase de la pastilla del separador de `chat-log`— y la demo arranca con NADIE
escribiendo, así que `data-state='idle'` esconde a todos los hijos (presencia =
visibilidad, N-7). Con el selector extra y un typer encendido por `prepareWith`:
**8 nodos**, y el centinela ya daba **7/7 antes de tocar el código**.
## El diff de computed NO cerró en cero, y es RUIDO reproducido
18 diffs, los 18 de `opacity` y los 18 en los tres
`span[data-animation-style=pulse]`. Sus puntos montan el loop `pulse` del
SISTEMA, que anima `opacity` sin parar, así que cada lectura cae en una fase
distinta: corriendo la sonda dos veces sobre el MISMO código salen los mismos 18
(control `after` vs `after2`). Ninguna otra propiedad se mueve, la clave tocada
es `border-radius`, y las capturas 2x antes/después son **idénticas byte a
byte** (1.568 valores · 7 estados · 8 nodos).
## Guards
censo `--only chat-typing` 100 % (global 68 %, no baja; los que llegan al 100 %
pasan de 36 a 42) · `component-audit` PASS · `eidos-lint` 0 invalid (8
morfo-backed, 0 eidos-only) · `vitest run src/uix/eidos` sin rojos nuevos (el
único vivo es el conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 ·
`check` COMPLETED sin un solo error del componente · prettier limpio sobre el
contenido normalizado a LF.
README con su sección «Talla y tema», demo con pestaña `Tokens` (8 claves,
verificada en navegador) y ficha con su veredicto §5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Its three dots are bare `> span` children of the indicator — no attr of
|
|
|
|
|
// their own — and the demo boots with NOBODY typing, which hides every child
|
|
|
|
|
// (presence = visibility, N-7). Four of its eight tokens had no node to
|
|
|
|
|
// paint (measured 2026-08-24).
|
|
|
|
|
'chat-typing': {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("typers")) [data-uix-chip]:text-is("Ada")'
|
|
|
|
|
],
|
|
|
|
|
extraNodes: '[data-chat-typing-indicator] > span, [data-chat-typing-avatars] > *'
|
|
|
|
|
},
|
uix(color-swatch): temable — 70 % → 100 %, y el color del chip NO es un knob
Cuatro claves nuevas por una retirada (12 → 14 en el contrato), dos adjudicaciones
medidas. Diff de computed 0 sobre 832 valores en 15 estados y DOS rutas; capturas
2× byte a byte idénticas.
LA §4 PEDÍA UN NOMBRE Y LO QUE FALTABA ERA UNA ESCALA
La ficha marcaba `radius` con ⚠ y tres valores en colisión. No es una colisión: es
el eje `rounded` con cuatro pasos, y la clave que ya estaba en el contrato ERA el
paso `sm` bajo un nombre que no lo dice. Contado en base.ts: siete recetas
escriben `radius-{k}`, y una es `badge` — el gemelo que lee ESTE MISMO
`data-rounded` con el mismo conmutador privado. Así que entra la escala entera
(radius-sm/md/lg/full) más `--_color-swatch-radius`, exactamente la forma que su
propio eje `size` usaba dos líneas más arriba. Dejar `radius` al lado de
`radius-md` habría dejado el paso `sm` llamándose `radius`: un nombre que miente.
`fill` SALE DEL CONTRATO: ES UN CANAL DE VALOR, NO UN KNOB DE TEMA
El wrapper escribe `--color-swatch-fill: {color}` INLINE en cada render desde una
prop obligatoria. Medido sobre un chip real con transiciones congeladas: escribir
el token en :root deja el ::before en rgb(124, 58, 237) — la declaración inline
gana siempre. Un público encima sólo podía mentir. La forma correcta ya estaba
escrita en el guard de la capa: recipe-css-contract sanciona `var(--x, default)`
para las vars que el runtime escribe («el respaldo ES el default»), que es justo
lo que la receta tenía. Por eso el respaldo NO se retira: al salir del contrato
deja de ser código muerto y pasa a ser el default.
⚠ EL CENTINELA DABA `fill` POR VIVO — ES UN FALSO POSITIVO SUYO
El guard escribe el token en :root Y en el `style` inline de cada nodo del
componente, que es exactamente donde vive el canal de valor: lo pisa y lee
«mueve». Es la cara opuesta de drag-drop.preview-z, donde la adjudicación se
había medido sobre un nodo fabricado. Regla: un token que el componente escribe
INLINE no se puede juzgar con el guard — se mide a mano escribiendo sólo en :root.
EL INSTRUMENTO ESTABA CIEGO, DOS VECES
1. color-swatch NO TIENE RUTA DE DEMO (404): es el QUINTO canónico en ese estado,
tras picker-shell, mockup, surface y audio-player. La sonda medía 1 nodo. Se
mide donde de verdad se renderiza — gradient-builder (3 chips en el stage) y
color-picker (el del trigger).
2. El eje `rounded` no lo monta NADIE: ningún consumidor del repo pasa la prop, así
que los tres pasos que no son el default no tenían nodo que pintar. El guard los
barre con sweepAttr: data-rounded, como meter hace con su shape.
LAS DOS ADJUDICACIONES SON EL ANILLO OPT-IN
border-width y border-fg: `bordered` nace apagado y ningún consumidor lo enciende,
así que su ::after no existe en ninguna página. Forzado data-bordered sobre un chip
real, los dos llegan (inset 0 0 0 1px → 1234px; oklch(0.931 0 0) → rgb(1, 2, 3)).
ARTEFACTOS
Sonda antes/después en dos rutas (256 valores × 8 estados en color-picker, 576 × 7
en gradient-builder) → 0 diffs, 0 nodos perdidos · el eje `rounded` medido a mano
forzando el atributo: 4 / 4 / 6 / 10 / 9999 px antes y después · capturas 2×
idénticas byte a byte · R-5.4 12/14 con las dos adjudicadas, exit 0 · censo --only
70 % → 100 % (knobs 10 → 7: los tres border-radius por variante pasan a declarar
el privado) · component:audit PASS · eidos-lint 2 morfo-backed / 8 eidos-only / 0
invalid · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo conocido,
skin-media-player) · npm run check COMPLETED sin errores en ficheros tocados.
LO QUE NO ENTRA Y POR QUÉ
- Paso 9 del protocolo (pestaña Tokens en la demo): no hay demo que tocar.
- docs/audit/theming/README.md: es un agregado global y otra sesión tiene form y
progress en vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo,
de base.ts, generated/base.css, theming-sentinel.ts y el ledger sólo entran mis
hunks (verificado con git diff --cached).
Defectos anotados, no arreglados (mueven documentación ajena a este cambio): la
cabecera del CSS, el docblock del .svelte y types.ts siguen diciendo que la talla
sale de `--icon-size-*`, cuando desde la extracción tiene escala propia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The FIFTH canon component with no route of its own
|
|
|
|
|
// (`/uix/components/color-swatch` is a 404, like picker-shell / mockup /
|
|
|
|
|
// surface / audio-player): it is measured where it actually renders — the
|
|
|
|
|
// gradient-builder stop rows mount three chips in the stage, the color-picker
|
|
|
|
|
// trigger one more. And its `rounded` axis is mounted by NOBODY (no consumer
|
|
|
|
|
// in the repo passes the prop), so the three non-default corner steps have no
|
|
|
|
|
// node to paint: swept while measuring, like meter's shape.
|
|
|
|
|
'color-swatch': {
|
|
|
|
|
urls: ['/uix/components/gradient-builder', '/uix/components/color-picker'],
|
|
|
|
|
sweepAttr: { attr: 'data-rounded', values: ['sm', 'md', 'lg', 'full'] }
|
|
|
|
|
},
|
uix(color-picker): temable — 51 % → 75 %, y un prefijo ABREVIADO fuera de todo el árbol
20 claves de costura (54 → 71) y cinco nombres renombrados. Global del eje
60 % → **61 %**.
`--cp-*` VIOLABA theming §6 r5 («no abrevies el nombre del componente»). Cinco
nombres —`checker`, `checker-cell`, `current-color`, `channel-gradient`,
`trigger-color`— y **45 ocurrencias en 8 ficheros**, tres de ellos de SOMA: el
provider los estampa inline, su test los lee y sus tipos los declaran.
Renombrados a `--_color-picker-*` con `git ls-files` delante; dos de ellos —el
damero y el gradiente de canal— ya derivaban de un público, así que el censo los
reclasifica solo. Precedente: los `--gb-*` / `--_ntp-*` de F2-A.
Cosido: el ritmo del componente, la tinta de la etiqueta, la tipografía del
disparador y la del VALOR —que es metadato: mono, regular, tinta e interlineado
propios—, el borde de la muestra, el raíl del canal y su pulgar, el hueco del
anillo de la muestra y el borde del cuentagotas.
UNA SOMBRA QUE SON DOS Y HACEN UNA COSA: el pulgar del canal lleva contorno +
elevación en un solo `box-shadow`, así que se acuña como UN token y la regla de
foco lo compone con su tercer anillo, en vez de repetir las dos sombras.
EL INSTRUMENTO NO VEÍA DOS FAMILIAS DE NODO, y las dos se arreglaron midiendo:
las muestras del picker **son `<ColorSwatch>`** (llevan `data-color-swatch`, no
`data-color-picker-*`) y sus sliders de canal **son SLIDERS** (el pulgar y el
raíl llevan `data-slider-*`). Cuatro tokens vivos leían muertos por eso.
LO ROJO QUE QUEDA ES ANTERIOR A ESTE COMMIT: el guard reporta 23 sin adjudicar
—las muestras, la rueda de tono (`hue-0..360` + `saturation-floor`), el ancho del
panel por talla, el cuentagotas y las dos de transición—, todos del bloque que ya
existía. Antes eran **24**, así que la cifra baja. Su pase de medición queda
anotado en §13 con dos causas ya descartadas: el panel SÍ abre y sobrevive al
blur, al parqueo del ratón y al forzado de `data-size`, y el guard ya ve sus
nodos. Lo que este commit añadió está medido: dos adjudicadas montando el
disparador de muestra dentro del panel abierto, el resto en vivo.
Gates: sonda antes/después **0 diffs en 1.350 valores** (8 estados), medida DOS
veces —una tras el renombrado, otra tras la costura— · tests de soma del
color-picker **7/7** tras el renombrado · censo --only 75 % · `--names` sin
desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The picker's swatches ARE `<ColorSwatch>` components: they carry
|
|
|
|
|
// `data-color-swatch`, not `data-color-picker-*`, so the filter never saw the
|
|
|
|
|
// nodes its own `swatch-*` tokens paint (measured 2026-08-22).
|
|
|
|
|
'color-picker': {
|
|
|
|
|
// Two families of node the filter cannot see: its swatches ARE
|
|
|
|
|
// `<ColorSwatch>` components (`data-color-swatch`) and its channel sliders
|
|
|
|
|
// are SLIDERS — the thumb and the track carry `data-slider-*`, so four
|
|
|
|
|
// tokens that paint them read dead (measured 2026-08-22).
|
|
|
|
|
extraNodes: '[data-color-swatch], [data-color-picker-channel-slider] *'
|
|
|
|
|
},
|
uix(popover): temable — 46 % → 67 %, y un huérfano que el guard de huérfanos no veía
Once claves para el disparador + una RETIRADA (36 → 46). Global del eje 60 %.
EL DISPARADOR GENÉRICO ES CROMO PROPIO. El que no lleva el arquetipo
`field-trigger` —que tiene su tratamiento aparte— montaba alto, padding, borde,
radio, superficie y tinta sobre primitivos crudos. Dos notas: `trigger-height`
lee el BUNDLE de talla (`--size-md-control-height`) y no el primitivo que el CSS
traía, y hover y abierto llevan CADA UNO su par de claves aunque hoy compartan
valor — son dos estados, y un tema puede querer el abierto más marcado.
`bg` RETIRADO: ninguna regla CSS lo consumía. El único sitio del corpus que lo
nombraba era un COMENTARIO de `popover-arrow.svelte` («fill: var(--popover-bg)»),
y el test `does not leave declared public recipe variables orphaned` busca el
nombre como TEXTO en los ficheros del componente — así que un comentario mantiene
vivo un token para siempre. La superficie del panel la pinta el plano de
profundidad (`--depth-overlay-surface`), que es lo que un plano debe poseer; el
comentario queda corregido y el hueco del test, en §13.
LOS DOCE QUE SIGUEN FUERA SON LA FIRMA DE SALIDA, y no se cosen aquí:
`data-last-action` × 3 intents × 4 lados tiñendo el borde durante el cierre. La
misma matriz está repetida en `drawer` (22 reglas) y `dialog` (9) — y ya ha
derivado: dialog tiñe sólo `border-top-color` mientras popover y drawer usan el
shorthand con `--border-width-medium`, así que en dos de tres el GROSOR también
cambia al salir. La tabla morfo→eidos la sitúa como mapeo transversal
(«`events[].prewrite` → tinting the exit anim by cause»), misma fila que los
arquetipos: es canal visual de sema, no cromo de componente. Coserlo tres veces
sería triplicar un vocabulario que debe vivir una vez → §13.
DOS CEGUERAS DEL INSTRUMENTO, arregladas:
1. La página tiene CINCO disparadores y el guard abría el primero del documento
—un ejemplo de más abajo—, no el del escenario: las partes que el escenario
monta nunca entraban. `openWith` apuntando al escenario.
2. `content-z` se consume sobre el ENVOLTORIO FLOTANTE
(`:has(> [data-popover-content])`), un nodo sin atributo `data-popover*` que
el filtro no veía: leía muerto moviendo 80 → 4321.
TRECE ADJUDICACIONES, TODAS MEDIDAS: nueve son partes que la demo no monta
(Title, Description) y dos el overlay opcional —montados en el panel ABIERTO con
los atributos que el CSS selecciona, alcanzan—. **Las dos que no son de
montaje**: `font-family` y `line-height` no pueden ganar porque el plano
`overlay` impone la tipografía del panel — el hallazgo §12.9, que espera tu firma
y afecta a VEINTE componentes. Aquí queda medido otra vez.
Gates: sonda antes/después **0 diffs en 420 valores** (8 estados; el escenario
monta un disparador y el panel portala fuera, así que el estado `abierto` es el
que mide el panel: 7 nodos) · centinela 33/46 · censo --only 67 % · `--names` sin
desviadas · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido · rtl:check 0 · docs:check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Five triggers live on the popover page; the default `.first()` opens one of
|
|
|
|
|
// the EXAMPLES further down, not the stage's, so the parts the stage mounts
|
|
|
|
|
// (arrow, close) never enter the document. Measured 2026-08-22.
|
|
|
|
|
'popover': {
|
|
|
|
|
openWith: ['[data-uix-stage-area] [data-popover-trigger]'],
|
|
|
|
|
// `content-z` is consumed on the FLOATING WRAPPER (`:has(> [data-popover-content])`),
|
|
|
|
|
// a node that carries no `data-popover*` attr — invisible to the filter and
|
|
|
|
|
// dead-looking though it moves 80 -> 4321 (measured 2026-08-22).
|
|
|
|
|
extraNodes: '[data-floating-wrapper]'
|
|
|
|
|
},
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
chart: {
|
|
|
|
|
// The tooltip and the crosshair only exist WHILE the pointer is over the
|
|
|
|
|
// plot — there is no trigger to click and no state to latch. Hovering the
|
|
|
|
|
// plot is what mounts them, and `openBy: 'hover'` also keeps the pointer
|
|
|
|
|
// parked there instead of moving it away before each token.
|
|
|
|
|
openBy: 'hover',
|
|
|
|
|
openWith: ['[data-chart-plot]'],
|
|
|
|
|
urls: [
|
|
|
|
|
'/uix/components/chart',
|
|
|
|
|
'/uix/components/line-chart',
|
|
|
|
|
'/uix/components/area-chart',
|
|
|
|
|
'/uix/components/bar-chart',
|
|
|
|
|
'/uix/components/scatter-chart',
|
|
|
|
|
'/uix/components/bubble-chart',
|
|
|
|
|
'/uix/components/pie-chart',
|
|
|
|
|
'/uix/components/sparkline',
|
|
|
|
|
'/uix/components/bar-list',
|
|
|
|
|
'/uix/components/bar-segment',
|
|
|
|
|
'/uix/components/radar-chart',
|
|
|
|
|
'/uix/components/smith-chart',
|
|
|
|
|
'/uix/components/polar-area',
|
|
|
|
|
'/uix/components/funnel',
|
|
|
|
|
'/uix/components/gauge',
|
|
|
|
|
'/uix/components/heatmap'
|
|
|
|
|
]
|
|
|
|
|
},
|
uix(text-gradient): temable — 67 % → 100 %, y la ficha se equivocaba en las TRES
7 claves en una entrada NUEVA de `recipes/base.ts`. Censo 67 % → **100 %**
(10 knobs, 10 públicos, 0 literales). Global 2.634 → 2.638 públicos.
Tercero de la familia text-* con la forma de text-circular / text-focus: cuatro
públicos «de facto» consumidos con fallback y **ninguno declarado**, más tres
literales. Sin eje `size` ni privados propios, así que declarar no puede matar
una escala.
LO QUE LA PROPUESTA §4 NO ACERTABA — y se resolvió midiendo contra el CATÁLOGO,
no contra el CSS de partida:
- **`weight` era deriva de nombre.** El catálogo tiene 77 claves `font-weight`
contra 4 `weight` a secas — y dos de esas cuatro las escribí ayer en esta
misma familia. Pasa a `font-weight`, y su valor al paso de escala que ya
pintaba: `500` ES `var(--font-weight-medium)`.
- **El `calc(100% - 2px)` del hueco no son dos knobs de eje físico** (la ficha
los marcaba con ⚠ «pártelo en ejes lógicos») sino UNO: los 2 px son el trazo
del marco asomando a cada lado. El knob es `border-width` —con
`var(--border-width)`, el paso `thin`, no un literal `1px` igual a un paso de
la escala— y el hueco DERIVA de él.
- **`blur(4px)` va a la escala de blur.** `blur` es familia MÉTRICA del eje
`scaling` (theming §23): un literal es ciego al zoom global. `--blur-sm` es
`calc(4px * var(--scaling))` — el mismo píxel al 100 y el correcto al 90/110.
Precedente: `dialog`/`drawer.overlay-blur`, también backdrop-filter.
Y el shorthand físico `padding: var(--space-1) var(--space-2)` se parte en ejes
lógicos (`border-padding-block` / `-inline`), que es la normalización firmada el
2026-07-06; el público de facto `border-padding` era un knob con dos ejes dentro.
FUERA DEL CONTRATO POR NATURALEZA: `--text-gradient-duration` y
`--_text-gradient-play-direction` los escribe el wrapper INLINE desde
`animationSpeed` y `yoyo`. Ningún token gana a un estilo inline, así que
declararlos sería acuñar dos nombres que mienten — misma clase que los cuatro
ausentes de `knob`, el `item-gap` de `carousel` y el `preview-z` de `drag-drop`.
INSTRUMENTO — dos puntos ciegos arreglados ANTES de medir (la lección de F2-B
aplicada por adelantado, no después):
- La demo arranca con `showBorder=false`, así que sonda y centinela veían **2
nodos** y NINGUNO de los tres tokens del marco. La sonda gana un mapa
`DEMO_VARIANTS` (chips de variante re-pulsados tras CADA recarga — recarga una
vez por talla) y el centinela una entrada `openWith` que no abre un panel sino
que ENCIENDE UNA VARIANTE: uso nuevo del mismo gancho, sin tocar su lógica.
- La sonda no leía `backdropFilter`, que `KNOB_PROPS` del censo sí cuenta y el
centinela ya leía: un token de fondo podía moverse entre las dos corridas sin
que el diff lo viera. Con la propiedad y la variante: **4 nodos**.
Hover: 0 nodos medidos, y es correcto — las cuatro partes son `<span>` y el
único efecto de hover es `animation-play-state`, que no es knob.
LO QUE ABRE (→ next-features §13): **la escala de `font-size` no tiene
`2xl`/`3xl`/`4xl`** —es `xxs..xxxl`— y tres sitios los referencian: el token
`text-focus.word-size` que subí ayer (`var(--font-size-4xl, 3rem)`, pintando el
fallback: segundo fantasma de la clase `--radius-xs`, y en el contrato) y los
escenarios de las demos de `text-gradient` y `text-blur`, que sin fallback dejan
la declaración INVÁLIDA y enseñan un efecto tipográfico a 14 px. Y
`--uix-font-size-*` no existe: 37 referencias en el harness del sitio, cero
declaraciones. Ninguno se toca aquí: se miden, se anotan y van a su commit.
Demo: pestaña **Tokens** (`TokensPanel`, contrato vivo) y fuera la tabla de
tokens escrita a mano de la pestaña API — era una segunda fuente de verdad y
este renombrado la dejaba obsoleta al instante.
Gates: sonda antes/después **0 diffs** en 720 valores × 7 estados (4 nodos) ·
centinela **7/7 sin una sola excepción** · censo --only 100 % · --names 7/7
conformes · component-audit PASS · eidos-lint 0 invalid · suite eidos con el
único rojo conocido (`skin-media-player`, lint.test.ts) · rtl:check 0 ·
docs:check 0 · `check` con 72 errores globales y **0 en los ficheros tocados**.
Prettier: los seis ficheros editados ya estaban sucios en HEAD (el repo tiene
2.392), no se reformatean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
'text-gradient': {
|
|
|
|
|
openWith: [
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("showBorder")) [data-uix-chip]:text-is("true")'
|
|
|
|
|
]
|
uix(image-picker): temable — 71 % → 100 %, y el instrumento medía UN nodo
Una clave nueva (13 → 14), cuatro literales firmados y el interruptor que hace
medible el componente. Diff de computed 0 sobre 1.344 valores en 7 estados y 6
nodos; R-5.4 de 13/13 a 14/14 SIN una sola adjudicación; capturas 2×
antes/después idénticas.
EL PICKER ARRANCA VACÍO Y LA SONDA VEÍA UN NODO
Todo lo que la receta pinta —preview, canvas, toolbar y los dos botones de
icono— sólo existe en el estado ready, al que no llega ningún trigger: hay que
cargar un fichero. Con el chip «Load sample image» de la demo la sonda pasa de 1
a 6 nodos. La sonda lo enciende por DEMO_VARIANTS y el centinela por
prepareWith; éste necesita además openMarker, porque su re-apertura por token
volvería a pulsar el chip y remontaría la vista previa a mitad de corrida.
LA CLAVE ES hover-button-bg, Y EL NOMBRE LO DECIDIÓ EL CATÁLOGO
Era el único knob que iba a un primitivo global a pelo. La §4 lo nombraba
hover-rotate-bg por el PRIMER selector de la regla, y miente dos veces: pinta
rotate Y remove, y el contrato de este componente ya llama button-* a ese par
desde que nació. hover-{parte}-{slot} es la forma de las 60 claves hover-* de
base.ts.
NO ES EL VELO DEL SISTEMA — MEDIDO DESDE EL PÍXEL HACIA ARRIBA
Los dos botones flotan SOBRE la foto: su relleno de reposo es translúcido
(color-mix 88 %) y el hover lo vuelve OPACO. Es un cambio de RELLENO, no una
capa encima. Con las transiciones congeladas, elementsFromPoint sobre el centro
del botón: el cambio cae en el <button> —el nodo con forma, radio 6 px, de
srgb .988/.88 a oklch(.9911 0 0)— y NINGÚN ancestro recibe background-image,
porque el arquetipo action no trae velo (archetypes.css sólo vela trigger, item
y option). No se bendice un duplicado, que es lo que paró el hover-* de listbox.
LOS CUATRO LITERALES SE FIRMAN, NO SE ACUÑAN
El 100 % doble del hijo del canvas es identidad (la <Image> ES la caja del
canvas, ya con inset: 0), el line-height: 1 es un botón de un solo glifo, y el
Highlight de forced-colors es la paleta del sistema operativo: un valor de tema
ahí lo sustituiría el UA. Válvula de recipe-contract §3 — salen del ratio como
ausencia ESCRITA.
DEFECTO REAL, ANOTADO, NO ARREGLADO
Ese hover neutro es una invención por componente y §38 / R-4.3 mandan que el
hover neutro sea la capa --state-*. Migrarlo cambia el default (velo translúcido
en vez de relleno opaco) y D-TH.5 lo prohíbe aquí. Y no basta con cambiar la
receta: el arquetipo del par es action, que HOY no recibe velo — o action gana
velo en archetypes.css (mueve píxel en todo el catálogo) o el par cambia de
arquetipo, que es morfo. Escrito en el veredicto §5 y en el README.
CIFRAS Y GUARDS
Censo 71 % → 100 % (20 knobs → 16: público 12 → 13, global 1 → 0, literal 4 → 0,
excepción firmada 0 → 4; sistema 3, fuera del ratio). Contrato 13 → 14 claves.
component-audit PASS · eidos-lint invalid 0 (12 morfo-backed, 5 eidos-only) ·
vitest src/uix/eidos 35/36 (único rojo skin-media-player, preexistente) ·
rtl:check 0 · docs:check 0 · npm run check sin errores en ficheros de este
componente. README con su sección «Talla y tema» y pestaña Tokens en la demo
(14 claves, verificada con clic de Playwright tras networkidle).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// It boots EMPTY — a FileUpload dropzone — and every part the recipe paints
|
|
|
|
|
// (preview, canvas, toolbar, the two icon buttons) exists ONLY in the `ready`
|
|
|
|
|
// state, which no trigger reaches: the demo's sample chip is the only way in.
|
|
|
|
|
// The marker keeps the re-open guard from clicking that chip before every
|
|
|
|
|
// token, which would rebuild the File and remount the preview mid-run.
|
|
|
|
|
'image-picker': {
|
|
|
|
|
prepareWith: ['[data-uix-chip]:text-is("Load sample image")'],
|
|
|
|
|
openMarker: '[data-image-picker-preview]'
|
uix(search-field): temable — 71 % → 100 %, y sus 10 tokens sin medir NO mienten
Cero claves nuevas. Su 29 % de deuda eran CUATRO literales que son identidad, y
ahora van firmados. Diff de computed 0 sobre 1.216 valores en 8 estados y 5
nodos; R-5.4 de 7/17 a 17/17 SIN una sola adjudicación, dos corridas de acuerdo;
capturas 2× antes/después idénticas byte a byte.
LOS CUATRO LITERALES SON IDENTIDAD, NO KNOBS
El line-height: 1 del icono —un glifo solo no tiene interlineado, la misma
anotación que su hermana field.css—, el 100 % doble del ::before —el anillo ES la
caja del indicador, que ya mide por --_search-field-loading-size— y el
border-radius: 50 %. Ese último la §4 pedía acuñarlo como
loading-indicator-radius: sería un knob que SÓLO puede romper el componente. El
arco es un BORDE que gira; con cualquier otro valor la ruleta deja de leerse como
una. Es la clase 4 de F2-B, «el 50 % de un círculo», al pie de la letra.
DIEZ DE SUS DIECISIETE TOKENS NO SE HABÍAN MEDIDO NUNCA
El centinela leía 7/17 porque la demo no monta dos estados: el indicador de carga
sólo existe con loading (ocho tokens) y la X sólo se desvanece con el campo
VACÍO, y el escenario arranca con valor (clear-hidden-opacity). Los dos
interruptores entran como prepareWith, y EL ORDEN IMPORTA: el clic en la X tiene
que ir PRIMERO, porque la propia regla que enciende le quita después el
pointer-events al botón que la disparó. La sonda necesita el de loading por lo
mismo: 4 → 5 nodos, sin perder ninguno.
LOS DOS PRIVADOS NO SON DEUDA
--_search-field-icon-size y --_search-field-loading-size son el conmutador por
talla y sus cinco fuentes son, cada una, una clave pública. Se quedan: 58 de las
162 recetas resuelven así su eje size —incluidas las cerradas al 100 % este mes,
empty-state y banner—, y convertirlas al ámbito size: del TSC cambiaría la
especificidad de la emisión sin ganar un punto de alcance.
⚠ UN DEFAULT MEDIDO, ANOTADO, NO CAMBIADO
Las dos escalas del mismo renglón divergen en los dos últimos peldaños. Medido en
Chrome con transiciones y animación congeladas, forzando data-size sobre el nodo
real, icono → indicador: xs 14→14, sm 16→16, md 18→18, lg 20→18, xl 32→20. El
contrato lo declara de frente (loading-indicator-size-lg apunta a
--size-md-icon-size y -xl a --size-lg-icon-size), así que en xl la ruleta es DOCE
píxeles menor que la lupa que tiene al lado. No es deuda de alcance —las diez
claves alcanzan—, es diseño que huele a copia a medias. Mueve píxel: D-TH.5 lo
saca de este eje. Escrito en el veredicto §5 y en el README.
CIFRAS Y GUARDS
Censo 71 % → 100 % (15 knobs → 11: público 10, literal 4 → 0, excepción firmada
0 → 4; sistema 1, fuera del ratio). Contrato 17 claves, intacto — no hay cambio
en base.ts ni en generated/. component-audit PASS · eidos-lint invalid 0 (9
morfo-backed, 4 eidos-only) · vitest src/uix/eidos 35/36 (único rojo
skin-media-player, preexistente) · rtl:check 0 · docs:check 0 · npm run check sin
errores en ficheros de este componente. README con su sección «Talla y tema» y
pestaña Tokens en la demo (17 claves, verificada con clic de Playwright tras
networkidle).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// Two states the default stage does not show, and NINE of its seventeen
|
|
|
|
|
// tokens live in them: the loading indicator renders only while `loading`
|
|
|
|
|
// (eight tokens), and the clear affordance only fades while the field is
|
|
|
|
|
// EMPTY — and the demo boots with a value. Clearing it is what empties it,
|
|
|
|
|
// and the click has to come FIRST: the same rule then drops
|
|
|
|
|
// `pointer-events` on the very button that fired it.
|
|
|
|
|
'search-field': {
|
|
|
|
|
prepareWith: [
|
|
|
|
|
'[data-search-field-clear-trigger]',
|
|
|
|
|
'[data-uix-control]:has([data-uix-control-label]:text-is("loading")) input[type=checkbox]'
|
|
|
|
|
]
|
uix(select): temable — 83 % → 92 %, y un token que ALCANZA y aun así no pinta
Dos claves de COSTURA (PLAN §2-A: el valor es del sistema, el knob es del
componente) y tres literales de identidad firmados. Ni un píxel movido.
Qué entra en el contrato (101 → 103 claves):
- invalid-border — las TRES declaraciones del estado inválido (el borde en
reposo, el borde con foco/abierto y el anillo box-shadow que lo dobla) leían
--color-risk-border A PELO. Es UNA clave, no dos: la propuesta §4 pedía
invalid-trigger-border + open-invalid-trigger-border, y las filas agrupadas
por VALOR son una sola. Bare como sus vecinas disabled-* (las ranuras de
estado de esta receta son las del trigger) y como las siete invalid-border
del catálogo.
- group-gap — el hueco entre filas de un grupo iba a var(--space-1) directo.
Nombre CONTADO en base.ts antes de acuñarlo: toolbar.group-gap.
Tres literales firmados con su razón (recipe-contract §3), fuera del ratio:
los dos inline-size: 100% (identidad — el select ES la anchura de su
contenedor, el trigger ES la caja del select) y el opacity: 1 del check
revelado, que deshace el 0 de la regla base.
Censo: 83 % → 92 % (72 knobs, 54 públicos, 3 globales, 3 literales →
69 knobs, 57 públicos, 0 globales, 0 literales, 3 excepciones). Los 5 que
quedan fuera son los forwards de paleta THM-2 leídos a pelo: no se acuñan.
CERRADO en su 92 %. Global 68 %, no baja.
Diff de computed: sonda antes/después, 2.176 valores en 8 estados = 0 diffs.
Capturas 2× antes/después (cerrado y abierto): IDÉNTICAS byte a byte.
Centinela R-5.4: 69/103, 34 adjudicadas en el ledger, todas MEDIDAS sobre el
nodo real con las transiciones congeladas. Verde (exit 0).
Lo que enseñó, todo del PORTAL:
1. El guard tiene que mantener el panel ABIERTO para medir los ~30 tokens de
dentro, y el estado abierto TAPA los de reposo: trigger-border y
trigger-indicator-fg leen muertos abiertos y MUEVEN con el panel cerrado.
El instrumento sólo puede estar en un estado a la vez.
2. Tres knobs viven en nodos SIN data-select-*: la z del panel está en el
[data-floating-wrapper] que soma porta (el caso popover.content-z) y el
ritmo del viewport lo pinta el ScrollArea compuesto. Con extraNodes,
65/101 → 69/103. Nodos: 4 en reposo, 42 con el panel abierto.
3. La cascada de paleta anula sus 24 tonos, aquí en su forma MULTI-PARTE
(trigger + content, porque el panel se porta fuera). Medido sobre el
trigger abierto: --select-primary-border no mueve nada, --palette-border
sobre el mismo nodo repinta.
4. separator-size ALCANZA y aun así no pinta: flex-shrink 1 en la columna del
ScrollArea y un panel que desborda (scrollHeight 670 vs clientHeight 310)
aplastan su 1px a 0px; con flex-shrink 0 el token sigue (1px → 9px). Un
token vivo sobre un nodo aplastado lee igual que uno muerto. Defecto de
píxel → next-features §13, no se arregla en este eje.
§12.9 respetado: el contenido lleva data-depth=overlay, así que no se acuñó
font-family ni line-height para el panel.
Guards: censo --only select 92 % · component-audit PASS · eidos-lint 0
invalid · vitest src/uix/eidos sin rojos nuevos (los dos rojos son ajenos:
skin-media-player, el conocido, y un fantasma de radio-group de otra sesión) ·
rtl:check 0 · docs:check 0 · npm run check COMPLETED con 0 errores en ficheros
de select · prettier limpio sobre el contenido normalizado a LF.
README con su sección «Talla y tema» y pestaña Tokens en la demo (103 claves,
verificada en navegador: 0 errores de consola).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// Three of its knobs are painted on nodes that carry NO `data-select-*` attr:
|
|
|
|
|
// the panel's z sits on the floating WRAPPER soma portals it into (the
|
|
|
|
|
// `popover.content-z` case, same fix), and the viewport rhythm — gap between
|
|
|
|
|
// options and the block-end inset — is painted on the composed ScrollArea's
|
|
|
|
|
// viewport. Measured 2026-08-24: without this the three read dead while
|
|
|
|
|
// moving 80 -> 4321, 4px -> 1234px and 4px -> 1234px.
|
|
|
|
|
select: {
|
|
|
|
|
extraNodes:
|
|
|
|
|
'[data-floating-wrapper]:has(> [data-select-content]), [data-select-content] [data-scroll-area-viewport]'
|
uix(calendar): temable — 84 % → 89 %, y una entrada de FAMILIA no se juzga en la ruta de UN componente
CERO claves acuñadas: el censo no tenía nada que coser. Su columna `global` ya
era 0 desde la capa calendar-surface (2026-08-21), así que lo único fuera de
alcance eran cuatro literales de IDENTIDAD. Todo el trabajo fue MEDIR, y ahí
estaba el hallazgo.
Qué entra:
- Cuatro literales firmados con su razón (recipe-contract §3), fuera del
ratio: el inline-size: max-content del panel y de la tabla (su anchura ES
day-size × 7 más el espaciado — precedente progress), el min-inline-size:
max-content del panel de mes y el inline-size: 100% de la cabecera. La
propuesta §4 pedía acuñar width / month-panel-width / grid-width para ellos:
sería dar a un tema la anchura de una caja cuya anchura es su contenido.
- Ninguna línea de CSS nueva. Las 74 claves del contrato quedan como estaban.
Censo: 84 % → 89 % (71 knobs, 57 públicos, 4 literales → 67 knobs, 57
públicos, 0 literales, 4 excepciones). Los 7 que quedan fuera son los forwards
de paleta THM-2: no se acuñan. CERRADO en su 89 %. Global 68 %, no baja.
Centinela R-5.4: 45/74 → 68/74, 6 adjudicadas. Verde (exit 0).
Lo que enseñó:
1. Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN
componente. Esta entrada es el vocabulario de las cuatro superficies de
rejilla de fechas (lo dice lib/calendar-surface.css): range-calendar,
month-grid y year-grid consumen --calendar-* y no acuñan nada — de ahí su
0 % HONESTO. Sus partes llevan data-month-grid-* / data-range-calendar-*,
invisibles al filtro data-calendar: 23 de sus 29 «tokens muertos» eran eso.
Con urls a las tres rutas + extraNodes + barrido de variant, 45/74 → 68/74.
Los tres consumidores NO se han tocado y siguen en su 0 % honesto.
2. Y la propia demo tapa tres claves más: su cabecera monta SELECTORES de mes
y año donde iría el heading, así que heading-{fg,font-size,font-weight} no
tienen nodo aquí; los tiene month-grid.
3. HALLAZGO — el tamaño de letra de los selectores de mes / año es NO
DETERMINISTA. La sonda dio 36 diffs contra un cambio que era SÓLO
comentarios; corrida dos veces sobre el MISMO código dio 24, en los mismos
dos nodos y las mismas dos propiedades. Causa medida:
[data-calendar-month-select][data-button] (calendar-select.css, la regla
que existe precisamente para fijar la letra del Button a la del calendario)
y [data-popover-trigger]:not([data-archetype='field-trigger'])
(popover.css) empatan a (0,2,0), así que gana el módulo que Vite inyecte el
ÚLTIMO: cuatro cargas dieron 14 / 16 / 14 / 14 px, con el orden de las
hojas invertido en la que dio 16. Es la clase del empate por orden de carga
de tree-grid (§12); arreglarlo mueve píxel → next-features §13.
Regla que confirma: ante un diff inesperado, corre la sonda dos veces sobre
el mismo código ANTES de sospechar de tu cambio.
4. calendar.select-min-width no tiene nodo en NINGUNA página del repo: su
único consumidor es [data-range-calendar-month-select] / -year-select, y la
demo de range-calendar renderiza un heading de texto mientras
DateRangePicker importa los selectores del CALENDARIO. Hueco de demo → §13.
Diff de computed: sonda antes/después, 22.656 valores en 7 estados. Los 36
diffs son ÍNTEGRAMENTE el no-determinismo del punto 3 (misma corrida contra sí
misma: 24) y caen en dos nodos y dos propiedades; fuera de ellos, 0. Capturas
2× antes/después: IDÉNTICAS byte a byte.
Guards: censo --only calendar 89 % · component-audit PASS · eidos-lint 0
invalid · vitest src/uix/eidos sin rojos nuevos (los dos son ajenos:
skin-media-player, el conocido, y un fantasma de radio-group de otra sesión) ·
rtl:check 0 · docs:check 0 · npm run check COMPLETED con 0 errores en ficheros
de calendar · prettier limpio sobre el contenido normalizado a LF.
README «Talla y tema» actualizado y pestaña Tokens en la demo (74 claves,
verificada en navegador: 0 errores de consola).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// The `calendar` recipe entry is the FAMILY's vocabulary, not this
|
|
|
|
|
// component's: `lib/calendar-surface.css` says so, and `month-grid`,
|
|
|
|
|
// `year-grid` and `range-calendar` consume `--calendar-*` while minting
|
|
|
|
|
// NOTHING of their own (0 % honest in the census). So a token of this entry
|
|
|
|
|
// lives if ANY surface of the family follows it — and their parts carry
|
|
|
|
|
// `data-month-grid-*` / `data-range-calendar-*`, invisible to the
|
|
|
|
|
// `data-calendar` filter. The header chrome (`control-*`, `heading-*`) has no
|
|
|
|
|
// node on THIS route either: the demo puts month / year SELECTORS where the
|
|
|
|
|
// text heading would be, and prev / next compose the system IconButton.
|
|
|
|
|
calendar: {
|
|
|
|
|
urls: [
|
|
|
|
|
'/uix/components/calendar',
|
|
|
|
|
'/uix/components/month-grid',
|
|
|
|
|
'/uix/components/range-calendar'
|
|
|
|
|
],
|
|
|
|
|
extraNodes:
|
|
|
|
|
'[data-month-grid], [data-month-grid] *, [data-range-calendar], [data-range-calendar] *',
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['surface', 'outline', 'ghost'] }
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// ONE instance driven by chips, and the whole palette hangs off the PRESSED
|
|
|
|
|
// state: the 8 `palette-*` slots — and the 48 tone forwarders behind them —
|
|
|
|
|
// are consumed only by the `*-on-*` keys, so an OFF toggle leaves 56 tokens
|
|
|
|
|
// without a node to paint. `invalid` is a second opt-in the stage does not
|
|
|
|
|
// boot with. With the variant swept on top, the guard sees solid / outline /
|
|
|
|
|
// ghost, pressed, invalid (measured 2026-08-23: 36/126 → 70/126, and the
|
|
|
|
|
// remaining 56 are the palette cascade plus six states the demo cannot mount).
|
|
|
|
|
toggle: {
|
|
|
|
|
sweepAttr: [
|
|
|
|
|
{ attr: 'data-variant', values: ['solid', 'outline', 'ghost'] },
|
|
|
|
|
{ attr: 'data-state', values: ['off', 'on'] },
|
|
|
|
|
{ attr: 'data-invalid', values: [null, ''] }
|
|
|
|
|
]
|
uix(image-adjustments): temable — 67 % → 100 %, y medir de MÁS midió menos
Sus cuatro knobs fuera de alcance eran LA COSTURA (PLAN §2-A) y ninguno una
clave nueva de diseño: el valor se queda donde estaba, el knob pasa a ser del
componente.
Entran cuatro claves (contrato 12 → 16), valor verbatim:
- `head-gap` = `var(--space-2)` y `label-line-height` = `var(--leading-ui)`,
que la receta leía a pelo — un tema no podía apretar la línea de cabecera sin
mover esos primitivos en toda la app.
- `hover-reset-fg` = `var(--color-content-primary)`. NO es un hover inventado
por componente: es TINTA, y el velo del sistema es `background-image`, que un
control sin superficie no puede recibir. Molde del catálogo: once
`hover-{parte}-fg` ya escritos así.
- `focus-reset-radius` = `var(--radius-sm)`. Precedente exacto
`rating-group.focus-radius`, la misma declaración dentro de la misma regla
`:focus-visible`. La §4 generada proponía `reset-radius` (el control no tiene
radio en reposo) e `item-label-line-height` (el bloque ya habla `label-*`):
el catálogo decide el nombre, no la propuesta.
El literal que quedaba se FIRMA, no se acuña: `outline-color: Highlight` bajo
`@media (forced-colors: active)` es la palabra clave del sistema — la paleta del
SO manda ahí. Pasa a `exception`, fuera del ratio.
El canal de VALOR sigue sin acuñarse y aquí no había ninguno que confundir: la
salida del panel es la CADENA `filter` que soma entrega por `onFilterChange`,
nunca toca el CSS de la receta.
Censo: 18 knobs · 10 públicos · 4 global · 1 literal · 3 sistema · **67 %**
→ 17 knobs · 14 públicos · 0 global · 0 literal · 3 sistema · 1 excepción
· **100 %**.
Diff de computed **VACÍO**: 5.440 valores, 7 estados, 26 nodos, sonda antes y
después. Capturas 2× antes/después, miradas: idénticas.
Píxel hacia arriba (§7.4-12, obligatorio porque toca una regla `:hover`): el
píxel bajo el puntero ES el `<button data-image-adjustments-reset>`; el hover
mueve `color` y SÓLO `color` en ese nodo (oklch(0.5032 0 0) → oklch(0.2435 0 0))
y ni él ni un solo ancestro pintan `background-image`, ni en reposo ni en hover.
Centinela R-5.4 **15/16**, 1 adjudicada — y el instrumento enseñó dos cosas:
1. `hue-track` leía muerto (11/12) y estaba vivísimo: pinta en el `::before` del
`<Slider>` COMPUESTO, cuyo nodo lleva `data-slider*`. Clase de `waveform`.
`extraNodes` en `COMPONENT_OVERRIDES`.
2. Ese selector apunta SÓLO a la fila `hue`, y la precisión es funcional: con
las seis filas el conjunto medido pasó a 32 nodos y el reset se volvió el
nº 32, fuera del tope de 30 del pase de hover — `hover-reset-fg` leía muerto
por una razón ajena al token. **Medir de más midió menos.**
La adjudicación restante es de estado: `focus-reset-radius` sólo pinta bajo
`:focus-visible` y el guard desenfoca a propósito; medido a mano sobre el
reset real, 4px → 1234px.
Guards: censo `--only image-adjustments` 100 % · `component-audit` PASS ·
`eidos-lint` invalid 0 · `vitest run src/uix/eidos` 35/36 ficheros (el rojo
conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `check` sin un
solo error atribuible a este componente · `--names` sin desviadas suyas ·
prettier limpio con el contenido normalizado a LF.
Más el README con su sección «Talla y tema», la pestaña `Tokens` de la demo
(16 claves, verificada en navegador) y el veredicto §5 de su ficha.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// The selector names the HUE row alone on purpose: it is the only row the
|
|
|
|
|
// recipe re-tints, and widening it to every row pushed the reset out of the
|
|
|
|
|
// hover pass's 30-node cap (it became the 32nd match, so its ink hover read
|
|
|
|
|
// dead for a reason that had nothing to do with the token).
|
|
|
|
|
'image-adjustments': {
|
|
|
|
|
extraNodes: "[data-image-adjustments-item][data-adjustment='hue'] [data-slider]",
|
|
|
|
|
sweepAttr: { attr: 'disabled', values: [null] }
|
uix(separator): temable — 67 % → 100 %, SIN acuñar una sola clave
Su alcance no subía porque le faltaran tokens: subía firmando lo que nunca fue
un knob. Los dos que quedaban fuera eran `inline-size: 100%` en el horizontal y
`block-size: 100%` en el vertical — la IDENTIDAD del primitivo. Una regla ocupa
su contenedor; un separador que no lo hiciera sería otro componente, y su propio
README ya lo decía en sus Gaps («Horizontal already fills 100%»). Llevan ahora su
anotación `/* literal: … */` (recipe-contract §3) y salen del ratio.
Contrato **3 → 3 claves**: no entra ninguna, y ésa es la decisión. `thickness`
(el mismo token alimenta `block-size` en horizontal e `inline-size` en vertical),
`fg` (el slot 6 `--color-neutral-separator`, que existe para retocar divisores
sin tocar bordes de control) y `min-length` son TODA su apariencia. Acuñar un
`--separator-length` habría sido inventar un eje que nadie pidió.
Censo: 6 knobs · 4 públicos · 2 literales · **67 %**
→ 4 knobs · 4 públicos · 0 literales · 2 excepciones · **100 %**.
Diff de computed **VACÍO**: 448 valores, 7 estados, sonda antes y después
(el cambio es de comentarios: no puede mover un píxel, y se midió igual).
Capturas 2× antes/después, miradas: idénticas.
Centinela R-5.4 **2/3 → 3/3, cero adjudicadas**, y ahí estuvo el trabajo:
`min-length` leía MUERTO y no mentía. La demo arranca en el ejemplo `stacked`,
que monta sólo reglas horizontales, y la mitad de la receta cuelga de
`[data-orientation='vertical']`. Se arregla midiendo, no adjudicando: un
`sweepAttr` sobre `data-orientation` en `COMPONENT_OVERRIDES` recorre las DOS
orientaciones sobre los mismos nodos. Cambiar el chip de la demo al ejemplo
`toolbar` no habría servido — cambia una orientación por la otra en vez de
sumarlas, la trampa de «montar MÁS puede medir MENOS» otra vez.
Guards: censo `--only separator` 100 % · `component-audit` PASS ·
`eidos-lint` invalid 0 · `vitest run src/uix/eidos` 35/36 ficheros (el rojo
conocido `skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `check` sin un
solo error atribuible a este componente · prettier limpio con el contenido
normalizado a LF.
Más el README con su sección «Talla y tema», la pestaña `Tokens` de la demo
(3 claves, verificada en navegador) y el veredicto §5 de su ficha, que escribe
la cifra cerrada: el techo de este componente ES el 100 %.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
|
|
|
|
// Half its recipe hangs off `[data-orientation='vertical']` — the thickness on
|
|
|
|
|
// the inline axis and `min-length` — and the demo boots the `stacked` sample,
|
|
|
|
|
// which mounts horizontal rules only: `min-length` had no node and read dead
|
|
|
|
|
// (2/3). Sweeping the attribute measures BOTH orientations on the same nodes,
|
|
|
|
|
// which switching the demo chip could not do (it trades one for the other).
|
uix(tabs): temable — 96 % → 97 %, y su indicador es CANAL DE VALOR, no tema
El contrato ya estaba entero (79 claves) y NO se acuña ninguna: las tres
declaraciones que quedaban fuera lo están por doctrina, y así se firma.
Lo que entra:
- `width: 100%` de `fitted` firmado con su anotación `/* literal: */`
(recipe-contract §3): es IDENTIDAD — `fitted` ES «la lista llena el eje
inline», y un token ahí sólo podría tomar un valor sin desdecir la prop.
Sale del ratio: 96 % → 97 %.
- La propuesta §4.1 de la ficha (acuñar `indicator-width` / `-height` sobre
`var(--indicator-w, 0px)`) se RECHAZA por escrito. Esas vars son el canal
de VALOR de soma: la capa `MeasuredIndicator` escribe el rect del trigger
activo en cada movimiento, así que un público encima sería un knob que un
tema puede FIJAR — y fijarlo rompe el deslizamiento. Misma adjudicación
que `navigation-menu` lleva escrita para su propio `indicator-w` (y la
razón por la que su `-x` se retiró, muerto). El comentario de la receta lo
dice ahora en el sitio donde se lee.
- Guard R-5.4: entrada nueva en `COMPONENT_OVERRIDES` (barrido de
`data-variant` line/surface/pills/segmented + las dos superficies de URL
del propio demo, `perm.orientation=vertical` y `perm.disabled=true`):
40/79 → **70/79** sin adjudicar una clave de más.
- Ledger: 9 adjudicaciones, todas MEDIDAS a mano sobre los nodos reales —
los cuatro tokens de transición (el guard congela `transition`), los tres
del anillo de foco (el guard hace `blur` a propósito), `trigger-bg` (la
demo monta el Indicator y la regla que sube la etiqueta por encima de la
píldora gana 0,2,0; sin indicador alcanza) y `scrollable-fade`, que además
pinta por `mask-image`, fuera del juego de propiedades del guard.
- README con su sección «Talla y tema» (tabla derivada del contrato) y
pestaña `Tokens` en la demo (79 claves, verificada en el navegador).
- Ficha `docs/audit/theming/tabs.md` con su veredicto §5.
Comprobado y refutado: el trigger NO compone el `Button` canónico
(`tabs-trigger.svelte` → `Tabs.Trigger` de soma, un `<button>` desnudo), así
que la clase «el compuesto gana a la receta que lo compone» de toolbar / form
no le aplica y no hay declaración muerta que ceder.
Censo: 96 % → 97 % (77 knobs · 74 públicos · 2 globales · 0 literales ·
1 excepción firmada · 1 sistema). Sin cambios en `recipes/base.ts` ni en
`generated/base.css` — no hacía falta regenerar.
Diff de computed: **0 diffs sobre 2.016 valores en 8 estados** (8 nodos en
reposo, 7 en hover). Capturas 2× antes/después, idénticas.
Guards: centinela 70/79 verde · `component-audit --only tabs` PASS ·
`eidos-lint tabs` 0 invalid (13 morfo-backed, 44 eidos-only) ·
`vitest run src/uix/eidos` 35/36 (el rojo conocido es `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un solo error en
ficheros de tabs · prettier limpio sobre el contenido normalizado a LF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
separator: { sweepAttr: { attr: 'data-orientation', values: ['horizontal', 'vertical'] } },
|
|
|
|
|
// Half its contract is VARIANT chrome (the `surface` frame, the `pills` track,
|
|
|
|
|
// the `segmented` rail) and the demo boots `line`, so 39 of 79 read dead on
|
|
|
|
|
// the default stage. Two more axes are URL-borne on this demo: a vertical
|
|
|
|
|
// tablist is the only shape that gives the rail a WIDTH, and the disabled
|
|
|
|
|
// opacity floor needs a trigger wearing the state (the radio-group precedent
|
|
|
|
|
// — an extra surface instead of three ledger lines).
|
uix(toast): temable — 98 % → 100 %, y el plano se lleva la TARJETA ENTERA
El contrato ya tenía sus 85 claves y `global` era CERO: no se acuña ninguna.
Lo único fuera de alcance era un literal, y era identidad.
Lo que entra:
- `opacity: 1` de la tarjeta firmado con su anotación `/* literal: */`
(recipe-contract §3): es el polo EN REPOSO del par abierto/cerrado, y el
que lleva tokens es el otro (`closed-offset`, `closed-scale`). «Presente
del todo» no tiene un segundo valor que sostener. 98 % → 100 %.
- Guard R-5.4: entrada nueva en `COMPONENT_OVERRIDES` que dispara la tarjeta
con el botón del demo — y que DELIBERADAMENTE no pone `openMarker`. Una
tarjeta se auto-descarta a los 5 s, así que «¿ya está abierta?» no tiene
respuesta útil: con `openMarker` tres corridas sobre código idéntico
discreparon en QUÉ token leía muerto. Sin él, el guard cae al marcador por
defecto (una parte que este componente no tiene) y dispara una tarjeta
nueva antes de CADA token: **51/85 en tres corridas seguidas**, determinista.
- Ledger: 34 adjudicaciones, ninguna retirada, todas MEDIDAS.
- La sonda aprende a disparar el toast (`DEMO_VARIANTS`): medía UN nodo (el
viewport) porque un toast no EXISTE hasta que algo lo lanza. Ahora 8.
EL HALLAZGO — §12.9 con una cara nueva y con coste de PRODUCTO:
`toast-item.svelte` sella `data-depth='overlay'`, y el plano declara
`background`, el ATAJO `border` y `box-shadow` con la misma especificidad
(0,1,0) y más tarde. Medido sobre la tarjeta real con transiciones congeladas:
escribir `--toast-{tono}-surface` / `-border`, `--toast-provider-border-width`,
`--toast-accent-width` o `--toast-provider-shadow` no mueve NADA; escribir
`--depth-overlay-surface` / `-border` / `-shadow` y `--border-width` en la
misma raíz lo repinta todo. Son 21 claves de 85, y a diferencia de `tooltip`
esto se VE: un toast `risk` y uno `fulfill` visten la misma tarjeta gris, y la
franja de acento —un `border-inline-start` que el atajo posterior pisa en los
cuatro lados— no aparece nunca en reposo.
Matiz que sólo aparece midiendo: los seis `{tono}-accent` NO están muertos.
Bajo `[data-loading]` la regla del pulso es (0,2,0) y su `@keyframes` gana a
toda declaración normal — 100 fotogramas del pulso real: sin escribir, el
color interpolado corre en oklab L 0.5032..0.8514; con el token en rgb(1,2,3),
0.0823..0.5032. La misma clave miente en reposo y dice la verdad en animación.
§12.9 sigue SIN FIRMAR, así que las 21 se ADJUDICAN con `PLANE_SUPERSEDED` y
la incidencia se registra en `next-features.md` §12. No se toca desde este eje.
Las 13 restantes: la pareja de transición (el guard congela `transition`),
`closed-offset` / `closed-scale` (estado de salida + `transform`, que el guard
no fotografía), los tres del anillo de foco de la acción (el guard hace
`blur`), los cinco `{tono}-action-hover-bg` (el guard estampa el tono en su
pasada estática y no en la de hover) y `provider-max-width`, cuyo centinela de
1234px se capa contra el `min(100%, …)` — medido desde el otro lado, 120px
encoge la tarjeta de 420 a 280.
Censo: 98 % → 100 % (45 knobs · 45 públicos · 0 globales · 0 privados ·
1 excepción firmada). Global 68 %, al 100 % 42 → 45. Sin cambios en
`recipes/base.ts` ni en `generated/base.css`.
Diff de computed: **0 diffs sobre 1.760 valores en 7 estados** (8 nodos en
reposo, 7 en hover). Capturas 2× de la tarjeta byte a byte idénticas — y van
por un script propio, porque su viewport es `position: fixed` y cae fuera del
recorte del escenario que usa `__shot`.
Más README con su sección «Talla y tema», pestaña `Tokens` en la demo (85
claves, verificada en el navegador) y ficha con veredicto §5.
Guards: centinela 51/85 verde (exit 0, sin STALE) · `component-audit --only
toast` PASS · `eidos-lint toast` 0 invalid (24 morfo-backed, 6 eidos-only) ·
`vitest run src/uix/eidos` 35/36 (el rojo conocido es `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un solo error en
ficheros de toast · prettier limpio sobre el contenido normalizado a LF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A toast does not EXIST until something fires it: the stage mounts an empty
|
|
|
|
|
// `<Toaster>`, there is no `[data-toast-trigger]` for the default opener to
|
|
|
|
|
// find, and the guard measured the viewport alone. The demo's play button is
|
|
|
|
|
// the way in — and this entry deliberately does NOT set `openMarker`, which
|
|
|
|
|
// is the opposite of every other portaled component here. A card
|
|
|
|
|
// auto-dismisses after 5 s, so "is it already open?" has no useful answer:
|
|
|
|
|
// with `openMarker: '[data-toast-item]'` the run was NON-DETERMINISTIC (three
|
|
|
|
|
// runs on identical code disagreed on WHICH token read dead — the card
|
|
|
|
|
// expired mid-measure). Falling through to the default marker
|
|
|
|
|
// (`[data-toast-content]`, a part this component does not have) makes the
|
|
|
|
|
// re-open fire a FRESH card before EVERY token, which is the only way to
|
|
|
|
|
// outrun the timer.
|
|
|
|
|
toast: { openWith: ['[data-uix-play]'] },
|
uix(tabs): temable — 96 % → 97 %, y su indicador es CANAL DE VALOR, no tema
El contrato ya estaba entero (79 claves) y NO se acuña ninguna: las tres
declaraciones que quedaban fuera lo están por doctrina, y así se firma.
Lo que entra:
- `width: 100%` de `fitted` firmado con su anotación `/* literal: */`
(recipe-contract §3): es IDENTIDAD — `fitted` ES «la lista llena el eje
inline», y un token ahí sólo podría tomar un valor sin desdecir la prop.
Sale del ratio: 96 % → 97 %.
- La propuesta §4.1 de la ficha (acuñar `indicator-width` / `-height` sobre
`var(--indicator-w, 0px)`) se RECHAZA por escrito. Esas vars son el canal
de VALOR de soma: la capa `MeasuredIndicator` escribe el rect del trigger
activo en cada movimiento, así que un público encima sería un knob que un
tema puede FIJAR — y fijarlo rompe el deslizamiento. Misma adjudicación
que `navigation-menu` lleva escrita para su propio `indicator-w` (y la
razón por la que su `-x` se retiró, muerto). El comentario de la receta lo
dice ahora en el sitio donde se lee.
- Guard R-5.4: entrada nueva en `COMPONENT_OVERRIDES` (barrido de
`data-variant` line/surface/pills/segmented + las dos superficies de URL
del propio demo, `perm.orientation=vertical` y `perm.disabled=true`):
40/79 → **70/79** sin adjudicar una clave de más.
- Ledger: 9 adjudicaciones, todas MEDIDAS a mano sobre los nodos reales —
los cuatro tokens de transición (el guard congela `transition`), los tres
del anillo de foco (el guard hace `blur` a propósito), `trigger-bg` (la
demo monta el Indicator y la regla que sube la etiqueta por encima de la
píldora gana 0,2,0; sin indicador alcanza) y `scrollable-fade`, que además
pinta por `mask-image`, fuera del juego de propiedades del guard.
- README con su sección «Talla y tema» (tabla derivada del contrato) y
pestaña `Tokens` en la demo (79 claves, verificada en el navegador).
- Ficha `docs/audit/theming/tabs.md` con su veredicto §5.
Comprobado y refutado: el trigger NO compone el `Button` canónico
(`tabs-trigger.svelte` → `Tabs.Trigger` de soma, un `<button>` desnudo), así
que la clase «el compuesto gana a la receta que lo compone» de toolbar / form
no le aplica y no hay declaración muerta que ceder.
Censo: 96 % → 97 % (77 knobs · 74 públicos · 2 globales · 0 literales ·
1 excepción firmada · 1 sistema). Sin cambios en `recipes/base.ts` ni en
`generated/base.css` — no hacía falta regenerar.
Diff de computed: **0 diffs sobre 2.016 valores en 8 estados** (8 nodos en
reposo, 7 en hover). Capturas 2× antes/después, idénticas.
Guards: centinela 70/79 verde · `component-audit --only tabs` PASS ·
`eidos-lint tabs` 0 invalid (13 morfo-backed, 44 eidos-only) ·
`vitest run src/uix/eidos` 35/36 (el rojo conocido es `skin-media-player`) ·
`rtl:check` 0 · `docs:check` 0 · `check` COMPLETED sin un solo error en
ficheros de tabs · prettier limpio sobre el contenido normalizado a LF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
tabs: {
|
|
|
|
|
sweepAttr: { attr: 'data-variant', values: ['line', 'surface', 'pills', 'segmented'] },
|
|
|
|
|
urls: [
|
|
|
|
|
'/uix/components/tabs',
|
|
|
|
|
'/uix/components/tabs?perm.orientation=vertical',
|
|
|
|
|
'/uix/components/tabs?perm.disabled=true'
|
|
|
|
|
]
|
|
|
|
|
}
|
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía
31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global
48 %, sin contrato 43 → 42.
La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta».
Mirarlo cambió tres cosas del encargo:
1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de
list-surface o calendar-surface no hay hook de capa ni vocabulario prestado
— color-picker, date-picker y chronos importan sus `.svelte` directamente.
No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien.
2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre
`[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa
cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`,
que no casa nunca), así que las coordenadas por talla quedan como públicos
PLANOS y la receta las consume desde sus propios selectores. Es el único
componente del eje donde el patrón de nombres resueltos no aplica; la regla
base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de
tiempo lleva un TERCER eje, `data-size` sobre su propia clase.
3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0
nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la
vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker
anfitrión y detrás de su popover), y sus partes se llaman
`data-picker-header` / `-body` / `-footer` en vez de
`data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so
every picker gets the same visual contract for free» —, así que todo filtro
por prefijo del componente ve sólo la raíz.
CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía:
- Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos
reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0
diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**.
- Los knobs que la demo no monta —el header entero y la fila de tiempo— se
midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del
header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su
picker-size).
- El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES`
(prefijo de atributo + selector de apertura) acotado a este componente.
Verificado que los otros CATORCE con ledger no se mueven ni una cifra.
Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en
el ledger; los 25 que siguen mudos llevan cada uno su razón medida.
Header y footer comparten la regla que los separa del cuerpo → UN par de knobs
(`border-width` + `border`), no dos.
Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en
next-features §13 junto con la pregunta de fondo — si el precio de los nombres
genéricos (un contrato visual común para todos los pickers) compensa la ceguera
del instrumental — y con el eje portalado como caso de prueba si el TSC llega a
ganar un scope configurable.
Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido
skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio ·
`component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
};
|
|
|
|
|
|
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
|
|
|
function sentinelFor(key: string): string {
|
|
|
|
|
if (/z$/.test(key)) return '4321';
|
|
|
|
|
if (/font-family/.test(key)) return 'Zapfino, cursive';
|
|
|
|
|
if (/font-weight/.test(key)) return '123';
|
|
|
|
|
if (/line-height/.test(key)) return '3.77';
|
|
|
|
|
if (/opacity|scale/.test(key)) return '0.123';
|
|
|
|
|
if (/duration/.test(key)) return '11.5s';
|
|
|
|
|
if (/letter-spacing/.test(key)) return '4.5px';
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// BEFORE the colour test on purpose: a DIMENSION whose name merely contains a
|
|
|
|
|
// colour word was getting `rgb(1, 2, 3)` and, being invalid for a length,
|
|
|
|
|
// moved nothing and read dead — measured on `chart.slice-stroke-width`
|
|
|
|
|
// (2026-08-22), which the substring `stroke` was capturing.
|
|
|
|
|
if (/(width|size|radius|gap|height|padding|offset|thickness|inset)$/.test(key)) return '1234px';
|
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
|
|
|
if (
|
|
|
|
|
/color|bg$|fg$|border$|ring$|separator|-bg-|fill|stroke|outline$|glass|scrim$|track$/.test(key)
|
|
|
|
|
)
|
|
|
|
|
return 'rgb(1, 2, 3)';
|
|
|
|
|
if (/shadow/.test(key)) return '0 0 0 7px rgb(1, 2, 3)';
|
|
|
|
|
return '1234px';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
const [component, urlArg] = process.argv.slice(2);
|
|
|
|
|
if (!component) throw new Error('usage: <component> [url]');
|
|
|
|
|
const url = urlArg ?? `http://localhost:5173/uix/components/${component}`;
|
uix(picker-shell): temable — 0 % → 76 %, y el componente que el instrumento no veía
31 claves en una entrada NUEVA de `recipes/base.ts`. Censo 0 % → 76 %. Global
48 %, sin contrato 43 → 42.
La cola avisaba «es CAPA de los pickers: mirar antes qué posee y qué presta».
Mirarlo cambió tres cosas del encargo:
1. **NO es una capa compartida: es un COMPONENTE.** A diferencia de
list-surface o calendar-surface no hay hook de capa ni vocabulario prestado
— color-picker, date-picker y chronos importan sus `.svelte` directamente.
No toca `LAYER_VOCABULARY` y el censo ya lo puntuaba bien.
2. **Su eje de talla no es `data-size`, es `data-picker-size` sobre
`[data-popover-content]`**, un ancestro PORTALADO. El TSC no puede emitir esa
cascada (`scope: 'size:xs'` generaría `[data-picker-shell][data-size='xs']`,
que no casa nunca), así que las coordenadas por talla quedan como públicos
PLANOS y la receta las consume desde sus propios selectores. Es el único
componente del eje donde el patrón de nombres resueltos no aplica; la regla
base es el default `sm` y `md` sólo re-declara lo que cambia. La fila de
tiempo lleva un TERCER eje, `data-size` sobre su propia clase.
3. **El instrumental del eje estaba CIEGO aquí, y la sonda genérica devolvió 0
nodos** — el antipatrón de comparar cero valores y pasar. Dos causas a la
vez: `picker-shell` no tiene demo propia (404, se mide dentro de un picker
anfitrión y detrás de su popover), y sus partes se llaman
`data-picker-header` / `-body` / `-footer` en vez de
`data-picker-shell-{parte}` — genéricas A PROPÓSITO, dice la receta, «so
every picker gets the same visual contract for free» —, así que todo filtro
por prefijo del componente ve sólo la raíz.
CÓMO SE VERIFICÓ, ya que el instrumento estándar no servía:
- Sonda DIRIGIDA (abre el popover del date-picker, captura los seis nodos
reales en las cuatro picker-sizes): determinismo comprobado (576 valores, 0
diffs entre dos corridas del mismo código) y **gate antes/después = 0 diffs**.
- Los knobs que la demo no monta —el header entero y la fila de tiempo— se
midieron FORZANDO el nodo: los quince mueven su propiedad, y el font-size del
header se comprobó talla a talla (12/14/16/16px → 102px, cada uno en su
picker-size).
- El guard R-5.4 pasó de **0/31 a 6/31** al ganar un `COMPONENT_OVERRIDES`
(prefijo de atributo + selector de apertura) acotado a este componente.
Verificado que los otros CATORCE con ledger no se mueven ni una cifra.
Adjudicar 31 excepciones sin ese arreglo habría sido escribir una mentira en
el ledger; los 25 que siguen mudos llevan cada uno su razón medida.
Header y footer comparten la regla que los separa del cuerpo → UN par de knobs
(`border-width` + `border`), no dos.
Sin tab `Tokens`: este componente no tiene página donde ponerlo. Registrado en
next-features §13 junto con la pregunta de fondo — si el precio de los nombres
genéricos (un contrato visual común para todos los pickers) compensa la ceguera
del instrumental — y con el eje portalado como caso de prueba si el TSC llega a
ganar un scope configurable.
Gates: censo --only 76 % · suite eidos sin rojos nuevos (el conocido
skin-media-player) · rtl:check 0 · docs:check 0 · check por fichero limpio ·
`component:audit` no lo cubre (eidos-only, sin morfo) y `eidos-lint` tampoco.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
|
|
|
const override = COMPONENT_OVERRIDES[component] ?? {};
|
|
|
|
|
const attrPrefix = override.attrPrefix ?? `data-${component}`;
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Openings that the blur + pointer-park would UNDO. A hover panel dismisses
|
|
|
|
|
// when the cursor leaves; a focus-only surface disappears when the focus does.
|
|
|
|
|
const openingIsFragile = override.openBy === 'hover' || override.openBy === 'focus';
|
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
|
|
|
const contract = readFileSync(resolve('src/uix/eidos/lib/recipes/base.ts'), 'utf8').replace(
|
|
|
|
|
/\r\n/g,
|
|
|
|
|
'\n'
|
|
|
|
|
);
|
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos
Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte
receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela
**83/88** con 5 adjudicadas. El default NO se mueve.
## Lo primero: el instrumento no veía el contrato
La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un
helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el
`return {`, un tabulador más adentro. Consecuencia medida:
- el censo la leía como «sin entrada en base.ts» — 84 claves invisibles;
- el centinela **moría** con `no recipe block for avatar`: el componente no se
podía medir, y su gate no significaba nada.
Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo
lo demás de este commit habría sido humo.
## Lo que entra al contrato (6 claves)
`group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`.
Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis
bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde
escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el
mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por
`--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 ·
−16,8 · −22,4 · −33,6 px → 37 px, uno a uno).
## Lo que se retira (2 declaraciones muertas, diff 0 las dos)
- `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta
declaraba su default `99`. **No lo leía nadie** — el tope se aplica con
`data-has-max` + `:nth-child(n + M)` porque una variable no entra en
`:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos
sitios; el `+3` del grupo sigue exactamente donde estaba.
- el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara
`--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo
podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue
computando `rgb(255, 255, 255)`.
## Seis literales firmados (salen del ratio)
Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie
del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo
centrado por la caja flex.
## Lo que se queda privado (el techo real es 90 %)
`--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian
de fuente con la variante (solid · soft · outline) y su valor sale del forward de
paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`.
Un público encima dejaría que un tema los fijara y matara el `color=` de cada
avatar.
## Artefactos
- Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576
valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072).
- Capturas 2× antes/después de las dos demos: **idénticas al byte**.
- Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y
`font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza —
el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo
el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas).
- El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de
`data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en
su propia ruta (21 muertos → 5).
- `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido
`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check`
COMPLETED sin un solo error en `avatar`.
- README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador
con `networkidle` y clic real).
## Lo que NO se arregla aquí (anotado)
- **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática
exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que
renombrarlas cambia el contrato público de tres canales: se lista, el audit
sigue en NEEDS-WORK por esto y sólo por esto.
- **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token
en `:root` y también sobre cada nodo, y para una propiedad personalizada que
la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema
pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo.
- **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE
las seis excepciones de `metrics`, así que se hace con la re-verificación del
ledger entero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// `avatar` is the ONLY entry whose value is an IIFE (a local matrix helper
|
|
|
|
|
// generates its 24 composite scopes), so its map lives in the `return {` one
|
|
|
|
|
// tab deeper: this probe found no block at all and the guard threw «no recipe
|
|
|
|
|
// block for avatar» — the component could not be measured. Read the returned
|
|
|
|
|
// map and dedent it once; everything downstream works unchanged.
|
|
|
|
|
const iife = contract.indexOf(`\n\t${component}: ((): RecipeTokenMap => {`);
|
|
|
|
|
const start =
|
|
|
|
|
iife >= 0
|
|
|
|
|
? contract.indexOf('\n\t\treturn {', iife)
|
|
|
|
|
: Math.max(
|
|
|
|
|
contract.indexOf(`\n\t'${component}': {`),
|
|
|
|
|
contract.indexOf(`\n\t${component}: {`)
|
|
|
|
|
);
|
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
|
|
|
if (start < 0) throw new Error(`no recipe block for ${component}`);
|
uix(avatar): temable — 75 % → 90 %, y su contrato era INVISIBLE para los dos instrumentos
Protocolo PLAN-theming §7 sobre `avatar` (incluye `AvatarGroup`, que comparte
receta y fichero). Censo 75 % → **90 %**, contrato 84 → 88 claves, centinela
**83/88** con 5 adjudicadas. El default NO se mueve.
## Lo primero: el instrumento no veía el contrato
La entrada `avatar` de `recipes/base.ts` es la ÚNICA construida por una IIFE (un
helper local genera sus 24 ámbitos compuestos), así que su mapa vive en el
`return {`, un tabulador más adentro. Consecuencia medida:
- el censo la leía como «sin entrada en base.ts» — 84 claves invisibles;
- el centinela **moría** con `no recipe block for avatar`: el componente no se
podía medir, y su gate no significaba nada.
Los dos lectores leen ya la IIFE (dedentan el `return`). Sin este arreglo, todo
lo demás de este commit habría sido humo.
## Lo que entra al contrato (6 claves)
`group-overlap-{xs,sm,md,lg,xl,xxl}` — la escala de solape de `AvatarGroup`.
Había UNA clave, `--avatar-group-overlap`, y la receta la RE-DECLARABA en seis
bloques `[data-size]`: sentada en el elemento, ganaba siempre al `:root` donde
escribe un tema. Medido: `37px` desde el asiento del tema no movía el margen; el
mismo valor escrito sobre el nodo, sí. Ahora el paso viaja por
`--_avatar-group-overlap` y los seis alcanzan desde `:root` (−8,4 · −11,2 · −14 ·
−16,8 · −22,4 · −33,6 px → 37 px, uno a uno).
## Lo que se retira (2 declaraciones muertas, diff 0 las dos)
- `group-max`: el envoltorio escribía `--avatar-group-max` INLINE y la receta
declaraba su default `99`. **No lo leía nadie** — el tope se aplica con
`data-has-max` + `:nth-child(n + M)` porque una variable no entra en
`:nth-child()`, y lo dice el propio comentario del CSS. Retirado de los dos
sitios; el `+3` del grupo sigue exactamente donde estaba.
- el respaldo `, white` de `--_avatar-badge-fg`: el contrato ya declara
`--avatar-badge-color-custom-contrast: white`, así que era inalcanzable y sólo
podía envejecer contra su token. Comprobado en la rama custom: la tinta sigue
computando `rgb(255, 255, 255)`.
## Seis literales firmados (salen del ratio)
Los cuatro `100 %` de Image y Fallback son IDENTIDAD — la parte ES la superficie
del avatar, no una talla propia — y los dos `line-height: 1` mantienen el glifo
centrado por la caja flex.
## Lo que se queda privado (el techo real es 90 %)
`--_avatar-bg`, `--_avatar-fg` y `--_avatar-badge-bg` son un CONMUTADOR: cambian
de fuente con la variante (solid · soft · outline) y su valor sale del forward de
paleta THM-2 que la capa de color alimenta por instancia desde `[data-color]`.
Un público encima dejaría que un tema los fijara y matara el `color=` de cada
avatar.
## Artefactos
- Sonda: **0 diffs** en TRES bases — demo de avatar con insignia y anillo (576
valores), la misma en modo fallback (384) y demo de AvatarGroup (3.072).
- Capturas 2× antes/después de las dos demos: **idénticas al byte**.
- Centinela R-5.4: 83/88, 5 adjudicadas con su medida en el ledger (`size-xxl` y
`font-size-xxl` viven en el paso `xxl`, que el barrido del guard no alcanza —
el límite que `metrics` ya registró; `radius-none` y `ring-width-sm` son «sólo
el paso en vigor pinta»; `group-overlap-xxl` junta las dos cosas).
- El guard aprende `avatar`: la insignia y el anillo son OPT-IN, el barrido de
`data-variant` destapa las 16 claves `*-outline-*`, y `AvatarGroup` se mide en
su propia ruta (21 muertos → 5).
- `eidos-lint` 0 invalid · `vitest src/uix/eidos` sin rojos nuevos (el conocido
`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 · `npm run check`
COMPLETED sin un solo error en `avatar`.
- README «Talla y tema» + pestaña `Tokens` (88 filas, verificada en el navegador
con `networkidle` y clic real).
## Lo que NO se arregla aquí (anotado)
- **R-5.3 preexistente**: cuatro claves `*-color-custom*` donde la gramática
exige `fg`. Son escotillas que el envoltorio escribe INLINE, así que
renombrarlas cambia el contrato público de tres canales: se lista, el audit
sigue en NEEDS-WORK por esto y sólo por esto.
- **Falso POSITIVO nuevo del centinela** (next-features §13): escribe el token
en `:root` y también sobre cada nodo, y para una propiedad personalizada que
la receta re-declara en el ELEMENTO ese inline gana justo lo que un tema
pierde. Es lo que hacía que `--avatar-group-overlap` leyera vivo.
- **El barrido de tallas del guard para en `xl`**: añadir `xxl` dejaría STALE
las seis excepciones de `metrics`, así que se hace con la re-verificación del
ledger entero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const end = contract.indexOf(iife >= 0 ? '\n\t\t};' : '\n\t},', start);
|
|
|
|
|
const block =
|
|
|
|
|
iife >= 0
|
|
|
|
|
? contract.slice(start, end).replace(/^\t/gm, '')
|
|
|
|
|
: contract.slice(start, end < 0 ? undefined : end);
|
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
|
|
|
const keys = [...block.matchAll(/^\t\t'?([a-z0-9-]+)'?\s*:/gm)]
|
|
|
|
|
.map((m) => m[1])
|
|
|
|
|
.filter((k) => !k.startsWith('_'));
|
|
|
|
|
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A component whose surface is spread over SEVERAL demo routes cannot be
|
|
|
|
|
// judged on one page: what that page does not mount reads dead. A token is
|
|
|
|
|
// LIVE when ANY of the component's surfaces follows it; only what nothing
|
|
|
|
|
// moves anywhere is dead. Each URL only re-tests what is still pending, so
|
|
|
|
|
// the common case (one route) costs exactly what it did before.
|
|
|
|
|
const origin = new URL(url).origin;
|
|
|
|
|
const urls = override.urls ? override.urls.map((path) => origin + path) : [url];
|
|
|
|
|
|
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
|
|
|
const browser = await chromium.launch();
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const live: string[] = [];
|
|
|
|
|
let pending = keys;
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
for (const target of urls) {
|
|
|
|
|
if (!pending.length) break;
|
|
|
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1200 } });
|
uix(image): temable — 60 % → 96 %, y dos instrumentos que morían en esta página
Una clave nueva y 12 literales anotados. La fila que queda fuera es el puente de
paleta del placeholder de color, que no es deuda.
Su «deuda» era casi toda IDENTIDAD: once `100 %` —la imagen, el fallback, el
error y sus dos `<img>` llenan su caja— y un `opacity: 1`, porque una imagen
CARGADA es opaca. Ninguno es una perilla, y ahora los doce llevan su razón.
Lo acuñado es la barra del esqueleto (`skeleton-bg`): leía `--gradient-shimmer` a
pelo, y es lo único de ese placeholder que un tema querría retocar sin tocar
todas las recetas que brillan.
LA PÁGINA NO ALCANZA `networkidle` NUNCA: monta un `src` roto a propósito para su
estado de error y el navegador reintenta, así que sonda y guard morían con un
`TimeoutError` antes de medir un solo nodo. Las dos caen ahora al evento `load`
si la espera de red se agota — la ociosidad era una comodidad, no una garantía.
UN TOKEN BAJO `@media (prefers-reduced-motion: reduce)` LEE MUERTO:
`placeholder-base` sólo existe ahí, donde el brillo se sustituye por un plano.
Emulando la preferencia alcanza, y vale para cualquier token detrás de una media
query de preferencia.
Y una lectura fantasma cazada por repetición: el primer diff acusó
`opacity: 0.925793 → 1` en la imagen cargada. Era el fade EN VUELO —el mismo
falso positivo que el rim animado de `onion-menu` esta mañana—. Repetida la
medida: 0 diffs. Un comentario CSS no puede mover un computado.
Gates: sonda 0 diffs en 544 valores × 7 estados (medida repetida) · comprobación
aparte del esqueleto y del base de movimiento reducido · centinela 20/23 con las
tres adjudicadas · censo 96 % · `component:audit` PASS · suite eidos con el rojo
conocido ajeno · `rtl:check` 0 · `docs:check` 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A demo whose network never goes idle (image keeps retrying the broken src
|
|
|
|
|
// of its error state) would time out the WHOLE run. The idle wait is a
|
|
|
|
|
// convenience, not a gate: fall back to the load event (2026-08-23).
|
|
|
|
|
try {
|
|
|
|
|
await page.goto(target, { waitUntil: 'networkidle', timeout: 15000 });
|
|
|
|
|
} catch {
|
|
|
|
|
await page.goto(target, { waitUntil: 'load', timeout: 15000 });
|
|
|
|
|
await page.waitForTimeout(800);
|
|
|
|
|
}
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
await page.waitForTimeout(600);
|
|
|
|
|
// A [data-busy] demo is still mutating — measuring it is measuring an instant.
|
|
|
|
|
await page
|
|
|
|
|
.waitForFunction(() => !document.querySelector('[data-busy]'), null, { timeout: 15000 })
|
|
|
|
|
.catch(() => {});
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Freeze transitions: a transitioned property reads its STARTING value right
|
|
|
|
|
// after the write, so a live token looked dead (measured on combobox's
|
|
|
|
|
// `box-shadow`, which transitions on `--duration-fast`).
|
|
|
|
|
await page.addStyleTag({
|
|
|
|
|
content: '*, *::before, *::after { transition: none !important; }'
|
|
|
|
|
});
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Open whatever can be opened, so portaled parts are in the document — then
|
|
|
|
|
// BLUR: a mouse click on a text input matches `:focus-visible`, and the
|
|
|
|
|
// focus rule repaints the rest-state chrome (the input-border false
|
|
|
|
|
// negative). Popovers dismiss on outside pointerdown, not on blur, so the
|
|
|
|
|
// open state survives.
|
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA
7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES
filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en
una regla inválida.
Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el
propio comentario del CSS dice que el hover usa «el mismo color que el anillo de
selección, más claro» y lo que los separa es la opacidad, que ya tenía token—,
la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos
piezas del glifo `+`→`×`. Dos limpiezas de paso: un
`var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí
mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)`
con un respaldo que no puede ocurrir.
Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN
un atenuado (sin atenuar es 1 por definición) y la caja del icono.
⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de
SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta
la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el
`70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el
centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito
encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL.
Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa
—la lección estaba escrita y se repitió—. → §13.
Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el
motor de color, como los rellenos de cada sector. No es superficie de tema.
TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas:
1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir»
lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya
casa, no se pulsa nada (8 → 14 de golpe).
2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden
con `extraNodes` — 37 nodos en reposo contra los 5 con atributo.
3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO:
`rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la
animación congelada computa 0,45 y sigue al token. → §13.
Los cuatro tokens del rim son del look `luminous`, que la demo del componente no
renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo
que forzar): medidos en `/active/docs/agnt`, que sí lo usa.
Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19
con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos
con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin
errores propios · capturas de reposo, sector en hover y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A surface that boots OPEN must not be clicked SHUT: onion-menu renders its
|
|
|
|
|
// sectors from the start, and the opening click toggled the whole recipe out
|
|
|
|
|
// of the document (12 of its 20 tokens read dead, 2026-08-23).
|
uix(background): temable — 57 % → 95 %, y montar MÁS capas hacía medir MENOS
Seis claves nuevas (31 → 37), tres literales firmados, un privado adjudicado y
19 tokens preexistentes medidos uno a uno: no había pasado por R-5.4.
LAS SEIS CLAVES SON LA MISMA COSTURA, DOS VECES
El patrón mesh leía var(--gradient-aurora) y los cinco pasos de escarcha leían
var(--blur-*), los dos A PELO: el VALOR es del sistema, el KNOB es del
componente. Con scrim-blur-* hay además una razón dura — `blur` es familia
MÉTRICA del eje `scaling` (theming §23), así que un literal ahí sería ciego al
zoom global.
TRES LITERALES FIRMADOS, Y EL GRANO NO SE ACUÑA
El 100% de img/video es identidad (una capa de medio ES la caja de su capa). El
grano del ruido es un feTurbulence en un data-URI: la textura ES la técnica —el
único patrón que el UA no elimina bajo forced-colors— y re-escaparlo para
meterlo en el contrato cambiaría el valor serializado sin cambiar la imagen. Su
mando sigue siendo pattern-noise-opacity.
EL 5 % QUE QUEDA ES CANAL DE VALOR
--_background-gradient-image lo escribe background-gradient.svelte EN LÍNEA
desde la prop `colors`. Un público encima no lo alcanzaría —el inline gana— y
mentiría: el caso de --gp-current-gradient y del preview-z que F2-B retiró.
UNA SOSPECHA MÍA, REFUTADA MIDIENDO
La línea del mesh hace lo que un apunte da por roto: un gradiente MESH por el
longhand background-image (su color base final no es capa de imagen válida →
computa `none`). Medido aquí: --gradient-aurora PINTA, porque su serialización
empieza por un radial-gradient. No hay defecto. La nota vale para un mesh
serializado con color base final, no para este token.
EL GUARD: DE 3/37 A 18/37, Y UNA LECCIÓN AL REVÉS
Las partes de este componente son CAPAS OPT-IN independientes (velo, foco,
pausa…), cada una tras su control, y sólo un patrón se renderiza a la vez: sobre
el escenario por defecto casi todo el contrato no tiene nodo que pintar. Nace
`prepareWith` — enciende TODOS los controles que se le den, al contrario que
`openWith`, que para en el primero — y con el barrido de data-pattern sube a 18.
Y la lección: MONTAR MÁS PUEDE MEDIR MENOS. Encender además el foco puntual, la
velocidad de paralaje y la profundidad bajó la corrida de 17 a 9: los tres
repintan el background-image y el translate de la MISMA capa donde se miden los
patrones. prepareWith se dejó en lo que no tapa.
LAS 19 ADJUDICACIONES, FORZANDO SOBRE LA CAPA REAL
Nunca sobre un nodo fabricado. Las dos del mask (fade-*, que el guard no puede
ver porque no fotografía mask-image — hueco de instrumento, §13), las dos del
contexto de tinta (on=dark/light), los ocho pasos de las dos escalas (blur 4→33,
8→33, 12→33, 24→33 px; strength alpha 0.08 · 0.13 · 0.4 · 0.7 → 0.9), el
gradient-drift-duration (24s → 42s, en una capa gradient que EXCLUYE a la de
patrón), los tres del foco puntual (40% → 42px…, y spotlight-fg sólo cuando la
capa NO lleva data-color: con él manda --palette-solid, por diseño), el
parallax-travel (sólo entra por las keyframes ligadas al scroll) y los dos de la
pausa (la demo no monta ese control: cero nodos).
AVISO: LOS CHIPS DE ESTA DEMO SE APLICAN CON RETRASO
Una comparación por chips capturó cada estado con el patrón ANTERIOR —el estado
«mesh» guardaba el glow— y dio 0 diffs por estar desfasada IGUAL en las dos
corridas: un gate verde sobre dos medidas equivocadas. La prueba buena fue la de
EQUIVALENCIA, determinista: cada token nuevo resuelve al mismo valor que el
primitivo que sustituyó (pattern-mesh-image = --gradient-aurora; scrim-blur-{k}
= --blur-{k} = 4/8/12/16/24 px) y lo pintado al forzar el atributo es idéntico
(el mesh pinta el aurora, el ruido pinta su data-URI intacto).
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (384 valores · 7 estados) · equivalencia
token↔primitivo exacta en las seis · R-5.4 18/37 con las 19 restantes
adjudicadas y medidas, cero STALE · component:audit PASS · censo 95 % ·
eidos-lint 3 morfo-backed / 63 eidos-only / 0 invalid / 0 class-hooks ·
rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player,
el conocido). Tras el cambio del guard, command, table, tooltip,
navigation-menu y waveform re-verificados sin regresión ni STALE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
for (const sel of override.prepareWith ?? []) {
|
|
|
|
|
const el = page.locator(sel).first();
|
|
|
|
|
if (!(await el.count())) continue;
|
|
|
|
|
try {
|
|
|
|
|
await el.click({ timeout: 1500 });
|
|
|
|
|
await page.waitForTimeout(350);
|
|
|
|
|
} catch {
|
|
|
|
|
/* one layer this route cannot show */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
uix(onion-menu): temable — 29 % → 84 %, y una regla que el navegador DESCARTA
7 claves nuevas (12 → 19) y 5 literales anotados. Lo que queda fuera son TRES
filas y ninguna es deuda de tokenización: una es canal de valor y dos viven en
una regla inválida.
Acuñado el color del ANILLO DEL SECTOR —uno para los dos estados, porque el
propio comentario del CSS dice que el hover usa «el mismo color que el anillo de
selección, más claro» y lo que los separa es la opacidad, que ya tenía token—,
la tipografía de la etiqueta, el radio y la tinta del disparador, y las dos
piezas del glifo `+`→`×`. Dos limpiezas de paso: un
`var(--style-label-font-family, var(--style-label-font-family))` —respaldo de sí
mismo, el defecto que ya se cazó en context-menu— y dos `var(--radius-full, …)`
con un respaldo que no puede ocurrir.
Cinco literales pasan a excepción POR IDENTIDAD: dos `opacity: 1` que DESHACEN
un atenuado (sin atenuar es 1 por definición) y la caja del icono.
⚠ `.onion-menu-icon :global(svg)` NO EXISTE. `:global()` es un envoltorio de
SVELTE y esto es un CSS plano: el selector es inválido y el navegador descarta
la regla entera. Medido: el glifo computa 14px —el tamaño propio del Icon— y el
`70 %` que el fichero cree pintar no pinta. Acuñé un `icon-glyph-size`, el
centinela lo delató y lo RETIRÉ; los literales se quedan con el defecto escrito
encima, porque arreglar el selector empieza a pintar el 70 % y MUEVE PÍXEL.
Mismo bug en `timeline.css:331`; `image-picker.css` ya documentaba la trampa
—la lección estaba escrita y se repitió—. → §13.
Canal de valor: el fondo del disparador lo escribe el componente INLINE desde el
motor de color, como los rellenos de cada sector. No es superficie de tema.
TRES CEGUERAS DEL INSTRUMENTO, dos nuevas y genéricas:
1. EL GUARD CERRABA LA SUPERFICIE. Este menú nace ABIERTO y el clic de «abrir»
lo cerraba: 12 de 20 tokens leían muertos. Ahora, si hay `openMarker` y ya
casa, no se pulsa nada (8 → 14 de golpe).
2. Sus tripas cuelgan de CLASES, no de `data-onion-*`: sonda y guard las miden
con `extraNodes` — 37 nodos en reposo contra los 5 con atributo.
3. Congelar las transiciones NO BASTA cuando el nodo está ANIMADO:
`rim-glow-opacity` leía 0,482956 —el valor animado— y parecía muerto; con la
animación congelada computa 0,45 y sigue al token. → §13.
Los cuatro tokens del rim son del look `luminous`, que la demo del componente no
renderiza (es una prop de Svelte que emite OTRO marcado, así que no hay atributo
que forzar): medidos en `/active/docs/agnt`, que sí lo usa.
Gates: sonda 0 diffs en 7.328 valores × 8 estados (37 nodos) · centinela 14/19
con las cinco adjudicadas · censo 84 % · `component:audit` PASS · suite eidos
con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 · `check` sin
errores propios · capturas de reposo, sector en hover y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const alreadyOpen = override.openMarker
|
|
|
|
|
? (await page.locator(override.openMarker).count()) > 0
|
|
|
|
|
: false;
|
|
|
|
|
for (const sel of alreadyOpen
|
|
|
|
|
? []
|
|
|
|
|
: [
|
|
|
|
|
...(override.openWith ?? []),
|
|
|
|
|
`[data-${component}-trigger]`,
|
|
|
|
|
`[data-${component}-input]`,
|
|
|
|
|
`[data-${component}-stop]`
|
|
|
|
|
]) {
|
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
|
|
|
const el = page.locator(sel).first();
|
|
|
|
|
if (await el.count()) {
|
|
|
|
|
try {
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (override.openBy === 'hover') await el.hover({ timeout: 1500 });
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
else if (override.openBy === 'focus') await el.focus({ timeout: 1500 });
|
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
|
|
|
else if (override.openBy === 'contextmenu')
|
|
|
|
|
await el.click({ button: 'right', timeout: 1500 });
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
else await el.click({ timeout: 1500 });
|
|
|
|
|
await page.waitForTimeout(400);
|
|
|
|
|
break;
|
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
|
|
|
} catch {
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
/* not clickable */
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (!openingIsFragile)
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur?.());
|
|
|
|
|
// ...and PARK THE POINTER (never for a hover-opened panel: moving the cursor
|
|
|
|
|
// away is exactly what dismisses it). blur() drops the focus but Playwright leaves the
|
|
|
|
|
// cursor where it clicked, so `:hover` keeps matching — and a hover rule
|
|
|
|
|
// usually outweighs the rest / focus / invalid ones it shares a node with
|
|
|
|
|
// (measured on textarea 2026-08-21: hover (0,4,0) beats focus (0,3,0) beats
|
|
|
|
|
// invalid (0,2,0) beats rest (0,1,0), so THREE rest-state tokens read dead).
|
|
|
|
|
// Same class as the click-focus false negative above, and the half that fix
|
|
|
|
|
// left behind. The per-token hover pass re-hovers on purpose further down.
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (!openingIsFragile) await page.mouse.move(0, 0);
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Runs before EVERY token. A component with no `content` part (textarea,
|
|
|
|
|
// any flat control) falls through to the click branch on every single key,
|
|
|
|
|
// so the pointer parking below is not belt-and-braces — without it the
|
|
|
|
|
// cursor sits on the input for the whole run.
|
|
|
|
|
const reopen = async () => {
|
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 open = await page
|
|
|
|
|
.locator(
|
|
|
|
|
override.openMarker ??
|
|
|
|
|
(override.attrPrefix ? `[data-${component}]` : `[data-${component}-content]`)
|
|
|
|
|
)
|
|
|
|
|
.count();
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (open) return;
|
|
|
|
|
for (const sel of [
|
|
|
|
|
...(override.openWith ?? []),
|
|
|
|
|
`[data-${component}-trigger]`,
|
|
|
|
|
`[data-${component}-input]`
|
|
|
|
|
]) {
|
|
|
|
|
const el = page.locator(sel).first();
|
|
|
|
|
if (await el.count()) {
|
|
|
|
|
try {
|
|
|
|
|
if (override.openBy === 'hover') await el.hover({ timeout: 1000 });
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
else if (override.openBy === 'focus') await el.focus({ timeout: 1000 });
|
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
|
|
|
else if (override.openBy === 'contextmenu')
|
|
|
|
|
await el.click({ button: 'right', timeout: 1000 });
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
else await el.click({ timeout: 1000 });
|
|
|
|
|
await page.waitForTimeout(250);
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (!openingIsFragile) {
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur?.());
|
|
|
|
|
await page.mouse.move(0, 0);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} catch {
|
|
|
|
|
/* keep trying */
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const staticPass = (key: string, value: string) =>
|
|
|
|
|
page.evaluate(
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
([kebab, token, val, props, prefix, extra, sweeps]) => {
|
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
|
|
|
const all = () =>
|
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
|
|
|
[...document.querySelectorAll<HTMLElement>('*')].filter(
|
|
|
|
|
(n) =>
|
|
|
|
|
[...n.attributes].some((a) => a.name.startsWith(prefix)) ||
|
|
|
|
|
(extra ? n.matches(extra as string) : false)
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Write on :root AND on every component node: resolved names are
|
|
|
|
|
// declared per part (root/host), and a portaled panel never sees the
|
|
|
|
|
// component root. Writing the var everywhere cannot fake a win at the
|
|
|
|
|
// PROPERTY level — a declaration another rule (or an inline style)
|
|
|
|
|
// beats stays beaten.
|
|
|
|
|
const hosts = () => [document.documentElement, ...all()];
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A tone token (`risk-solid`, `affirm-track`…) only paints on an instance
|
|
|
|
|
// wearing that tone: the palette forward routes the slots through
|
|
|
|
|
// `--{c}-palette-*` per [data-color]. Stamp the tone the key names.
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The attributes this component sweeps while measuring (variant, look,
|
|
|
|
|
// state…), or a single null pass when it has none. Several axes sweep
|
|
|
|
|
// as their CARTESIAN PRODUCT; with one axis the sequence is the same
|
|
|
|
|
// list it has always been.
|
|
|
|
|
const axes = sweeps as { attr: string; values: (string | null)[] }[];
|
|
|
|
|
const swept = axes.length ? all() : [];
|
|
|
|
|
const sweptWas = swept.map((n) => axes.map((ax) => n.getAttribute(ax.attr)));
|
|
|
|
|
const combos: ((string | null)[] | null)[] = axes.length
|
|
|
|
|
? axes.reduce<(string | null)[][]>(
|
|
|
|
|
(acc, ax) => acc.flatMap((c) => ax.values.map((v) => [...c, v])),
|
|
|
|
|
[[]]
|
|
|
|
|
)
|
|
|
|
|
: [null];
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const TONES = ['primary','secondary','neutral','affirm','fulfill','risk','threat','loss'];
|
|
|
|
|
const tone = TONES.find((t) => (token as string).startsWith(t + '-'));
|
|
|
|
|
const toned = tone ? all() : [];
|
|
|
|
|
const toneWas = toned.map((n) => n.getAttribute('data-color'));
|
uix(banner): temable — 30 % → 100 %, y el guard pasa de 4/42 a 53/53
11 claves nuevas (42 → 53). Su contrato ya era grande —40 claves de intención—
pero su CROMO estaba a pelo: la densidad por talla, la interlínea y el trazo del
anillo de `outline`, que era la última fila que impedía al privado del borde
derivar de un público.
Dos ausencias firmadas: los `transparent` de `outline` y `ghost` y los tres
`border-width: 0`. La variante ES la ausencia de cromo — mismo criterio que
button, y por la misma razón: diez claves de `transparent` no son un contrato,
son ruido.
EL INSTRUMENTO NO VEÍA 38 DE SUS 42 CLAVES, Y NINGUNA ESTABA MUERTA. Dos
cegueras a la vez:
1. Su paleta resuelve por `data-intent`, NO por `data-color`. El barrido de tono
del guard —añadido esta mañana para button— estampaba sólo `data-color`. Ahora
estampa los DOS nombres: un componente selecciona por uno y el otro queda
inerte.
2. Y luego por VARIANTE: las ranuras `solid`/`contrast` sólo pintan en `solid`,
la de borde sólo en `outline`, y la demo monta una de cada. Con `sweepAttr`
sobre `data-variant`, el guard las recorre.
Con las dos, 4/42 → 53/53. Es la diferencia entre «38 tokens que mienten» y «38
tokens que nadie había mirado con el atributo puesto»: exactamente lo que R-5.4
existe para distinguir.
Gates: sonda 0 diffs en 192 valores × 7 estados (la demo monta UNA franja) ·
centinela 53/53 sin una sola adjudicación · censo 100 % · `component:audit` PASS
· suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 ·
captura de las cuatro variantes con cuatro intenciones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Two attribute names for the same vocabulary: most recipes read
|
|
|
|
|
// `data-color`, banner reads `data-intent`. Stamp both — a component
|
|
|
|
|
// only selects on one, so the other is inert.
|
|
|
|
|
const toneIntentWas = toned.map((n) => n.getAttribute('data-intent'));
|
|
|
|
|
if (tone)
|
|
|
|
|
for (const n of toned) {
|
|
|
|
|
n.setAttribute('data-color', tone);
|
|
|
|
|
n.setAttribute('data-intent', tone);
|
|
|
|
|
}
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const sized = all().filter((n) => n.hasAttribute('data-size'));
|
|
|
|
|
const original = sized.map((n) => n.getAttribute('data-size'));
|
|
|
|
|
const snap = () =>
|
|
|
|
|
all()
|
|
|
|
|
.map((n) => {
|
|
|
|
|
const cs = getComputedStyle(n);
|
|
|
|
|
const own = (props as string[]).map((p) => cs[p as never]).join('|');
|
|
|
|
|
const b = getComputedStyle(n, '::before');
|
|
|
|
|
const a = getComputedStyle(n, '::after');
|
|
|
|
|
// ::placeholder IS readable through getComputedStyle - measured
|
|
|
|
|
// 2026-08-21 on textarea (the sentinel colour came straight back).
|
|
|
|
|
// The next-features §13 note saying it is not was wrong, and it had
|
|
|
|
|
// already cost command.input-placeholder-fg a hand-checked entry.
|
|
|
|
|
const ph = getComputedStyle(n, '::placeholder');
|
|
|
|
|
const pseudo = (props as string[])
|
|
|
|
|
.map((p) => `${b[p as never]}~${a[p as never]}~${ph[p as never]}`)
|
|
|
|
|
.join('|');
|
|
|
|
|
return own + '#' + pseudo;
|
|
|
|
|
})
|
|
|
|
|
.join('@');
|
|
|
|
|
let moved = false;
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
for (const combo of combos) {
|
|
|
|
|
if (combo !== null)
|
|
|
|
|
for (const n of swept)
|
|
|
|
|
axes.forEach((ax, i2) =>
|
|
|
|
|
combo[i2] === null
|
|
|
|
|
? n.removeAttribute(ax.attr)
|
|
|
|
|
: n.setAttribute(ax.attr, combo[i2]!)
|
|
|
|
|
);
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
for (const size of ['md', 'xs', 'sm', 'lg', 'xl']) {
|
|
|
|
|
for (const n of sized) n.setAttribute('data-size', size);
|
|
|
|
|
const before = snap();
|
|
|
|
|
for (const h of hosts()) h.style.setProperty(`--${kebab}-${token}`, val as string);
|
|
|
|
|
const after = snap();
|
|
|
|
|
for (const h of hosts()) h.style.removeProperty(`--${kebab}-${token}`);
|
|
|
|
|
if (before !== after) {
|
|
|
|
|
moved = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
}
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (moved) break;
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
}
|
|
|
|
|
sized.forEach((n, i) => (original[i] ? n.setAttribute('data-size', original[i]!) : null));
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
swept.forEach((n, i2) =>
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
axes.forEach((ax, j) =>
|
|
|
|
|
sweptWas[i2][j] ? n.setAttribute(ax.attr, sweptWas[i2][j]!) : n.removeAttribute(ax.attr)
|
|
|
|
|
)
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
uix(banner): temable — 30 % → 100 %, y el guard pasa de 4/42 a 53/53
11 claves nuevas (42 → 53). Su contrato ya era grande —40 claves de intención—
pero su CROMO estaba a pelo: la densidad por talla, la interlínea y el trazo del
anillo de `outline`, que era la última fila que impedía al privado del borde
derivar de un público.
Dos ausencias firmadas: los `transparent` de `outline` y `ghost` y los tres
`border-width: 0`. La variante ES la ausencia de cromo — mismo criterio que
button, y por la misma razón: diez claves de `transparent` no son un contrato,
son ruido.
EL INSTRUMENTO NO VEÍA 38 DE SUS 42 CLAVES, Y NINGUNA ESTABA MUERTA. Dos
cegueras a la vez:
1. Su paleta resuelve por `data-intent`, NO por `data-color`. El barrido de tono
del guard —añadido esta mañana para button— estampaba sólo `data-color`. Ahora
estampa los DOS nombres: un componente selecciona por uno y el otro queda
inerte.
2. Y luego por VARIANTE: las ranuras `solid`/`contrast` sólo pintan en `solid`,
la de borde sólo en `outline`, y la demo monta una de cada. Con `sweepAttr`
sobre `data-variant`, el guard las recorre.
Con las dos, 4/42 → 53/53. Es la diferencia entre «38 tokens que mienten» y «38
tokens que nadie había mirado con el atributo puesto»: exactamente lo que R-5.4
existe para distinguir.
Gates: sonda 0 diffs en 192 valores × 7 estados (la demo monta UNA franja) ·
centinela 53/53 sin una sola adjudicación · censo 100 % · `component:audit` PASS
· suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0 ·
captura de las cuatro variantes con cuatro intenciones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
toned.forEach((n, i2) => {
|
|
|
|
|
toneWas[i2] ? n.setAttribute('data-color', toneWas[i2]!) : n.removeAttribute('data-color');
|
|
|
|
|
toneIntentWas[i2]
|
|
|
|
|
? n.setAttribute('data-intent', toneIntentWas[i2]!)
|
|
|
|
|
: n.removeAttribute('data-intent');
|
|
|
|
|
});
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
return moved;
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
[
|
|
|
|
|
component,
|
|
|
|
|
key,
|
|
|
|
|
value,
|
|
|
|
|
PROPS,
|
|
|
|
|
attrPrefix,
|
|
|
|
|
override.extraNodes ?? null,
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
override.sweepAttr
|
|
|
|
|
? Array.isArray(override.sweepAttr)
|
|
|
|
|
? override.sweepAttr
|
|
|
|
|
: [override.sweepAttr]
|
|
|
|
|
: []
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
] as const
|
uix(theming): revisión adversarial de F2-A — 8 hallazgos de 34, y nace R-5.4
Pase escéptico del bloque (§7.7, molde Sidebar) con medición propia: worktree
con SOLO el CSS revertido a la base del bloque (DOM de demo constante — prueba
más dura que la del autor), sonda x2 por componente para fijar el suelo de
ruido. 34 hipótesis · 26 refutadas · 8 reales.
Lo que NO se pudo romper: el «default idéntico» aguanta en los ocho (0 diffs;
command 8.091 · table 13.050 · media-player 3.277 · gradient-picker 609 ·
listbox 2.813 · carousel 2.842; los 3 de tree-grid y los 5 de feed salen
idénticos corriendo DOS veces el mismo código → instrumento); 0 tokens
huérfanos de 336; el bundle ES 1:1 con los primitivos; el «verificado a mano»
de gradient-picker era verdad (15/16 alcanzan al montar); sin doble animación;
eidos-lint 0 invalid.
Los 4 de código, arreglados con diff de computed VACÍO (re-medido: command
8.091 y carousel 2.842 valores, 0 diffs):
1. command: el re-point de `--command-radius` dentro del Dialog era una
declaración muerta con comentario falso — la misma regla pone el radio a 0
y el input lee `--command-input-radius`, nunca este token (medido:
`--dialog-content-radius: 9999px` no movía NADA; `--command-radius: 77px`
movía la paleta inline y no la del dialog). Retirado; comentarios y README
reescritos con lo medido.
2. carousel: la regla vertical del indicador activo llevaba el `2.25` a pelo
mientras la horizontal leía `active-indicator-scale` — el token alcanzaba
media superficie (scale:6 → horizontal 18→48px, vertical 18→18px). Ahora
lo leen las dos.
3. carousel: el trío `item-gap*` no podía ganar NUNCA — soma estampa `gap`
INLINE desde la prop (lo necesita para el flex-basis; reglas casadas por
CDP: la de la receta y encima INLINE gap:0rem). Retirados los 3 tokens y
las 2 declaraciones muertas (la base y el gap:0 de ghost): 0 diffs, que es
la prueba. Canal de valor de soma, como --gp-current-gradient. Censo
carousel 35 knobs · 23 públicos · 77 % · 27 claves; global 43 % intacto
(5.190 · 2.102).
4. table: `striped-row-bg` no alcanza NADA en la demo publicada —
`:nth-of-type(even)` cuenta los <tr> de RowDetail y las filas de datos
quedan impares (medido tr a tr: diez inmunes). Arreglarlo mueve píxel →
PENDIENTE DE FIRMA (candidato `:nth-child(even of [data-table-row])`);
adjudicado en el ledger mientras tanto.
El instrumento cargaba 22 falsos negativos de 26 «no effect», con tres causas
medidas, y la cuarta era de la sonda:
- el paso de abrir hacía CLIC en el input, y un clic de ratón sobre un input
de texto SÍ casa `:focus-visible` — la regla de foco pintaba el borde y el
token de reposo leía muerto (era el «falso negativo sin causa» de §13);
- el override se escribía solo en `[data-{c}]`, y las partes que cuelgan de
`{c}-root` no lo recibían;
- `::before`/`::after` eran invisibles (el aro de buffering, el spinner);
- la no-determinación de feed no era una animación sin localizar: era la demo
aún CARGANDO — el auto load-more mantiene [data-busy] ~3 s y la firma
`commit-settle` anima box-shadow sobre el nodo medido. Con la espera de
asentado: 0 diffs y 0 nodos ausentes sobre 6.264 valores, dos corridas.
Con eso NACE EL GUARD R-5.4 (la deuda que §13 pedía desde gradient-picker, y
que carousel convirtió en patrón — un estilo inline de otra capa es invisible
para todo análisis estático): `npm run theming:sentinel -- <c> <url>`
(scripts/theming-sentinel.ts, promovido de `__`; blur tras abrir, override en
todos los nodos del componente, pseudos, pase de hover, settle) + ledger
scripts/theming-sentinel-exceptions.ts con UNA razón medida por token — muerto
sin adjudicar = exit 1, excepción STALE = aviso. Los ocho componentes en
verde: command 50/57 · table 41/45 · media-player 46/61 · tree-grid 45/47 ·
gradient-picker 9/25 · listbox 20/21 · carousel 26/27 · feed 43/50, con 46
excepciones adjudicadas. El guard ya me corrigió a mí: cinco entradas mías
salieron STALE (los control-height-* de media-player SÍ alcanzan) y las borró;
y dos muertos nuevos los produjeron los propios arreglos (el blur apaga
focus-input-border; el settle desmonta el spinner de feed) — adjudicados con
su porqué. Doctrina en recipe-contract §4.
También: tres cifras de especificidad de next-features §12 recontadas (la
banda de tree-grid es (0,8,0); la tinta seleccionada del arquetipo es (0,4,0);
y el `item` de table vive en la fila Y EN LA CELDA — elementsFromPoint pone el
velo en la celda), el comentario de listbox que mandaba pisar el privado
`--_listbox-max-height` apunta ahora al público, y quedan registrados en §13
los dos `header-z` con literal '2' y el spinner de media-player a 720ms
mientras el gemelo de feed lleva tokens.
Guards: censo global 43 % intacto · audit --only command+carousel PASS ·
eidos-lint 0 invalid (command 16/12, carousel 29/9, listbox 12/7) · vitest
eidos 434/435 (el rojo es el conocido skin-media-player) · rtl 0/180 · docs
0/813 · check 0 errores en tocados (72 globales de otras sesiones) · prettier
limpio en lo nuevo (los desvíos de command.css/carousel.css ya estaban en
HEAD y no se tocan).
Pendiente de firma (CONTINUE §«PENDIENTE DE FIRMA»): el striped inerte, el
arquetipo en fila+celda, y que soma lea el gap de un token.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
|
|
|
|
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// Hover pass — a `hover-*` token can only move a computed value while some
|
|
|
|
|
// node is really hovered (tree-grid's hover-row-bg, gradient-picker's
|
|
|
|
|
// hover-preset-border were false negatives without it).
|
|
|
|
|
const hoverPass = async (key: string, value: string) => {
|
|
|
|
|
const count = await 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
|
|
|
([prefix, extra]) =>
|
|
|
|
|
[...document.querySelectorAll('*')].filter(
|
|
|
|
|
(n) =>
|
|
|
|
|
[...n.attributes].some((a) => a.name.startsWith(prefix)) ||
|
|
|
|
|
(extra ? n.matches(extra) : false)
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
).length,
|
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
|
|
|
[attrPrefix, override.extraNodes ?? null] as const
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
|
|
|
|
for (let i = 0; i < Math.min(count, 30); i++) {
|
|
|
|
|
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
|
|
|
([prefix, idx, extra]) =>
|
|
|
|
|
[...document.querySelectorAll<HTMLElement>('*')].filter(
|
|
|
|
|
(n) =>
|
|
|
|
|
[...n.attributes].some((a) => a.name.startsWith(prefix)) ||
|
|
|
|
|
(extra ? n.matches(extra as string) : false)
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
)[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
|
|
|
[attrPrefix, i, override.extraNodes ?? null] as const
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
|
|
|
|
const el = handle.asElement();
|
|
|
|
|
if (!el) continue;
|
|
|
|
|
try {
|
|
|
|
|
await el.hover({ timeout: 800 });
|
|
|
|
|
} catch {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const moved = await page.evaluate(
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
([prefix, token, val, props, idx, kebab, extra, sweeps]) => {
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const all = () =>
|
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
|
|
|
[...document.querySelectorAll<HTMLElement>('*')].filter(
|
|
|
|
|
(n) =>
|
|
|
|
|
[...n.attributes].some((a) => a.name.startsWith(prefix)) ||
|
|
|
|
|
(extra ? n.matches(extra as string) : false)
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
|
|
|
|
const node = all()[idx as number];
|
|
|
|
|
if (!node) return false;
|
|
|
|
|
const snap = () => {
|
|
|
|
|
const cs = getComputedStyle(node);
|
|
|
|
|
return (props as string[]).map((p) => cs[p as never]).join('|');
|
|
|
|
|
};
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// The hover pass swept NOTHING, so a `{variant}-hover-*` key could
|
|
|
|
|
// only ever move on the variant the demo happens to mount — ten of
|
|
|
|
|
// toggle's twelve hover keys read dead for that reason alone
|
|
|
|
|
// (measured 2026-08-23). Same axes as the static pass; the pointer
|
|
|
|
|
// does not move, so re-stamping attributes is safe.
|
|
|
|
|
const axes = sweeps as { attr: string; values: (string | null)[] }[];
|
|
|
|
|
const swept = axes.length ? all() : [];
|
|
|
|
|
const sweptWas = swept.map((n) => axes.map((ax) => n.getAttribute(ax.attr)));
|
|
|
|
|
const combos: ((string | null)[] | null)[] = axes.length
|
|
|
|
|
? axes.reduce<(string | null)[][]>(
|
|
|
|
|
(acc, ax) => acc.flatMap((c) => ax.values.map((v) => [...c, v])),
|
|
|
|
|
[[]]
|
|
|
|
|
)
|
|
|
|
|
: [null];
|
|
|
|
|
let moved = false;
|
|
|
|
|
for (const combo of combos) {
|
|
|
|
|
if (combo !== null)
|
|
|
|
|
for (const n of swept)
|
|
|
|
|
axes.forEach((ax, j) =>
|
|
|
|
|
combo[j] === null
|
|
|
|
|
? n.removeAttribute(ax.attr)
|
|
|
|
|
: n.setAttribute(ax.attr, combo[j]!)
|
|
|
|
|
);
|
|
|
|
|
const before = snap();
|
|
|
|
|
for (const h of [document.documentElement, ...all()])
|
|
|
|
|
h.style.setProperty(`--${kebab}-${token}`, val as string);
|
|
|
|
|
const after = snap();
|
|
|
|
|
for (const h of [document.documentElement, ...all()])
|
|
|
|
|
h.style.removeProperty(`--${kebab}-${token}`);
|
|
|
|
|
if (before !== after) {
|
|
|
|
|
moved = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
swept.forEach((n, i2) =>
|
|
|
|
|
axes.forEach((ax, j) =>
|
|
|
|
|
sweptWas[i2][j] ? n.setAttribute(ax.attr, sweptWas[i2][j]!) : n.removeAttribute(ax.attr)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
return moved;
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
},
|
uix(toggle): el ledger y el guard del commit anterior, que un índice COMPARTIDO se llevó
Reparación, no trabajo nuevo. `edc019515` salió con la mitad del componente:
la receta, el CSS, el README, la demo y la ficha entraron; el ledger R-5.4, el
guard y la entrada del §8 se quedaron fuera, y en su lugar viajaron fragmentos
de OTRA sesión (rating-group). Causa medida: en esta rama el ÍNDICE de git es
un recurso compartido — entre el `git add` verificado y el `git commit` otra
sesión hizo su propio `reset` + `add`, y el commit fotografió su índice, no el
mío. Verificar el árbol indexado no basta si alguien puede reescribirlo
DESPUÉS de mirarlo; la verificación y el commit tienen que ir en la misma
invocación. Sin esto `npm run theming:sentinel -- toggle` falla: el contrato
tiene 126 claves y el ledger no tenía ninguna.
Lo que entra:
- **Ledger** (`theming-sentinel-exceptions.ts`): las 8 adjudicaciones medidas
de toggle (los cinco pasos de `rounded` que la demo no expone,
`disabled-opacity`, y el par de transición que ES lo que el guard congela)
y la excepción POR PATRÓN de sus 48 claves de tono — la cascada de paleta,
medida aquí: con `data-color=primary` `--toggle-primary-solid` no mueve nada
y `--palette-solid` sobre el mismo nodo repinta; quita `data-color` y
`--toggle-neutral-solid` pinta otra vez.
- **Guard** (`theming-sentinel.ts`): el barrido de VARIOS ejes como producto
cartesiano (un solo eje da la misma secuencia de siempre), el valor `null`
que QUITA el atributo, el mismo barrido en el pase de HOVER, y el override
de toggle. 36/126 → 70/126 sin adjudicar nada. Los 12 componentes con
`sweepAttr` re-verificados uno a uno: cero regresiones, cero STALE.
- **`PLAN-theming.md` §8**: la entrada de toggle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
[
|
|
|
|
|
attrPrefix,
|
|
|
|
|
key,
|
|
|
|
|
value,
|
|
|
|
|
PROPS,
|
|
|
|
|
i,
|
|
|
|
|
component,
|
|
|
|
|
override.extraNodes ?? null,
|
|
|
|
|
override.sweepAttr
|
|
|
|
|
? Array.isArray(override.sweepAttr)
|
|
|
|
|
? override.sweepAttr
|
|
|
|
|
: [override.sweepAttr]
|
|
|
|
|
: []
|
|
|
|
|
] as const
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
);
|
|
|
|
|
if (moved) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const stillDead: string[] = [];
|
|
|
|
|
for (const key of pending) {
|
|
|
|
|
await reopen();
|
|
|
|
|
const value = sentinelFor(key);
|
|
|
|
|
let moved = await staticPass(key, value);
|
uix(theming): revisión adversarial del bloque — la pasada de HOVER envenenaba a todos los tokens de reposo
Cinco hipótesis, un hallazgo real, cuatro refutadas. Bloque de CATORCE
componentes; global del eje 56 % → **62 %**.
HALLAZGO REAL — la anomalía de `float-panel` tenía causa, y era del guard.
`resize-grip-fg` movía aislado y leía muerto en una corrida completa. Instrumenté
el propio guard para probar UNA clave y salió `moved=true`; luego, en la corrida
entera, muerto. La causa: **la pasada de HOVER deja el puntero sobre el último
nodo que tocó**, y una regla `:hover` gana a la de reposo con la que comparte
nodo — el asa re-apunta su color al acento al pasar el ratón. Es decir:
**cualquier token de reposo probado DESPUÉS de un token `hover-*` podía leer
muerto**. Es exactamente la clase que F2-A arregló con el clic-foco, una pasada
más tarde: allí se aparcó el puntero tras ABRIR, aquí faltaba aparcarlo tras
HOVERAR.
Arreglado. Y entonces el propio guard delató DOS excepciones STALE —
`float-panel.resize-grip-fg` y `media-player.track` —, retiradas: dos tokens que
el ledger daba por muertos están vivos. Se confirma la regla de F2-A: **un falso
negativo siempre tiene causa**; registrarlo como «sin causa» era el paso previo,
no el final.
REFUTADA «hay tokens huérfanos»: comprobación ESTRICTA —comentarios despojados,
que es el hueco por el que `popover.bg` sobrevivió, sobre todo el CSS del árbol—
de las **553 claves** de los catorce bloques: ninguna declarada sin que una regla
la lea.
REFUTADA «el cambio de instrumento rompió algo»: los **27 componentes con ledger
re-verificados** tras aparcar el puntero — cero regresiones. Los tres rojos que
quedan (`drawer` 24, `metrics` 16, `color-picker` 23) son ANTERIORES al bloque y
están anotados con lo que ya se descartó de cada uno.
REFUTADA «quedan fantasmas del barrido en lo tocado»: pasa de 9 a **8** — el
`--color-content-tertiary` de float-panel murió con su costura. Los ocho
restantes son de componentes que este bloque no tocó.
CONFIRMADO, y ya estaba escrito: hay gates FINOS. `popover` mide 1 nodo en reposo
(7 en abierto), `drawer` 1, `context-menu` 1 + 14 en abierto, `color-picker`
5 + 12. Cada veredicto lo dice, y lo que sostiene esas cifras es el forzado a
mano de sus estados, no la sonda.
Guards: `--names` sin desviadas · docs:check 0 · censo 62 % (2.877 públicos de
5.137 knobs; `global` 1.145 → 912).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (!moved && /hover/.test(key)) {
|
|
|
|
|
moved = await hoverPass(key, value);
|
|
|
|
|
// PARK THE POINTER AFTERWARDS. The hover pass leaves the cursor on the
|
|
|
|
|
// LAST node it hovered, and a hover rule outweighs the rest-state one it
|
|
|
|
|
// shares a node with: every rest token tested after a hover token could
|
|
|
|
|
// read dead. Measured 2026-08-22 on float-panel — `resize-grip-fg` moved
|
|
|
|
|
// in isolation and read dead in a full run, because the grip's own
|
|
|
|
|
// `:hover` rule re-points its colour to the accent. Same class as the
|
|
|
|
|
// click-focus poisoning fixed in F2-A, one pass later.
|
uix(skip-link): temable — 71 % → 100 %, y el guard aprende a abrir por FOCO
Cero claves nuevas: su 29 % de deuda eran DOS literales que son la técnica, y
ahora van firmados. Diff de computed 0 sobre 192 valores en 7 estados más los 52
del estado enfocado —el único que se ve— medidos aparte; capturas 2× idénticas
byte a byte; R-5.4 de 0/8 a 8/8 SIN una sola adjudicación.
LA §4 PEDÍA `width` Y `height`, Y LAS DOS SON LA TÉCNICA
El `1px × 1px` no es un tamaño: es la mitad del sr-only canónico (la otra mitad
es el par clip / clip-path). Un tema que lo mueva no cambia una apariencia, rompe
el mecanismo — y a 0×0 algunos motores sacan el enlace del árbol de
accesibilidad, que es exactamente lo único que este componente no puede
permitirse; su propia cabecera ya razona eso mismo para display:none, visibility
y el tabindex negativo. Firmados con /* literal: */, la válvula de
recipe-contract §3: salen del ratio como ausencia ESCRITA, no como deuda. Misma
clase que text-blur y text-scramble.
LOS OTROS 5 KNOBS YA ESTABAN BIEN, Y LAS DOS AUSENCIAS SON DOCTRINA
La tipografía se HEREDA (no hay font-* declarado; acuñarla fijaría el default,
D-TH.5) y el anillo de foco es del SISTEMA (--focus-ring-*, recipe-contract §2).
El `background:` en shorthand no mata ninguna capa de estado: su parte lleva
archetype 'provider', que no recibe velo.
EL INSTRUMENTO LEÍA 0 DE 8 — LA OCTAVA CLASE DE PUNTO CIEGO
Toda su superficie existe SÓLO bajo :focus: sr-only mientras no lo tiene, píldora
cuando sí. El guard no lo enfocaba nunca, y el blur que hace tras abrir lo habría
deshecho igual. Clicarlo tampoco vale: su handler manda el foco a la región de
destino, así que la píldora se va por el camino de entrada.
Nace `openBy: 'focus'`, y con él `openingIsFragile` — el conjunto de aperturas
que el blur y el aparcado del puntero NO deben deshacer, hasta hoy sólo 'hover'.
Es aditivo: las cuatro condiciones que decían `openBy !== 'hover'` pasan a
consultarlo y ninguna configuración existente cambia de rama. Re-verificado sobre
tres del ledger: dialog 37/44 (la cifra exacta de su commit), tooltip 15/23,
context-menu 27/28 — cero regresiones.
ARTEFACTOS
Sonda antes/después → 0 diffs, 0 nodos perdidos (192 valores, 7 estados) · estado
ENFOCADO medido aparte con un script propio (la sonda estándar mide reposo,
tallas, abierto y hover: nunca foco) → 52 valores, 0 diffs · capturas 2×
idénticas byte a byte · R-5.4 8/8, exit 0 · censo --only 71 % → 100 % (knobs 8 →
6: los dos literales pasan a la clase excepción, fuera del ratio) ·
component:audit PASS · eidos-lint 2 morfo-backed / 0 invalid · rtl:check 0 ·
docs:check 0 · suite eidos 439/440 (el rojo conocido, skin-media-player) ·
npm run check COMPLETED sin errores en ficheros tocados · pestaña Tokens añadida
a su demo y VERIFICADA en el navegador: 8 filas, 0 errores de consola.
LO QUE NO ENTRA Y POR QUÉ
docs/audit/theming/README.md es un agregado global y otra sesión tiene `form` en
vuelo en el mismo árbol; se regenerará con su commit. Por lo mismo, de
theming-sentinel.ts sólo entran mis hunks (verificado con git diff --cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (!openingIsFragile) await page.mouse.move(0, 0);
|
uix(theming): revisión adversarial del bloque — la pasada de HOVER envenenaba a todos los tokens de reposo
Cinco hipótesis, un hallazgo real, cuatro refutadas. Bloque de CATORCE
componentes; global del eje 56 % → **62 %**.
HALLAZGO REAL — la anomalía de `float-panel` tenía causa, y era del guard.
`resize-grip-fg` movía aislado y leía muerto en una corrida completa. Instrumenté
el propio guard para probar UNA clave y salió `moved=true`; luego, en la corrida
entera, muerto. La causa: **la pasada de HOVER deja el puntero sobre el último
nodo que tocó**, y una regla `:hover` gana a la de reposo con la que comparte
nodo — el asa re-apunta su color al acento al pasar el ratón. Es decir:
**cualquier token de reposo probado DESPUÉS de un token `hover-*` podía leer
muerto**. Es exactamente la clase que F2-A arregló con el clic-foco, una pasada
más tarde: allí se aparcó el puntero tras ABRIR, aquí faltaba aparcarlo tras
HOVERAR.
Arreglado. Y entonces el propio guard delató DOS excepciones STALE —
`float-panel.resize-grip-fg` y `media-player.track` —, retiradas: dos tokens que
el ledger daba por muertos están vivos. Se confirma la regla de F2-A: **un falso
negativo siempre tiene causa**; registrarlo como «sin causa» era el paso previo,
no el final.
REFUTADA «hay tokens huérfanos»: comprobación ESTRICTA —comentarios despojados,
que es el hueco por el que `popover.bg` sobrevivió, sobre todo el CSS del árbol—
de las **553 claves** de los catorce bloques: ninguna declarada sin que una regla
la lea.
REFUTADA «el cambio de instrumento rompió algo»: los **27 componentes con ledger
re-verificados** tras aparcar el puntero — cero regresiones. Los tres rojos que
quedan (`drawer` 24, `metrics` 16, `color-picker` 23) son ANTERIORES al bloque y
están anotados con lo que ya se descartó de cada uno.
REFUTADA «quedan fantasmas del barrido en lo tocado»: pasa de 9 a **8** — el
`--color-content-tertiary` de float-panel murió con su costura. Los ocho
restantes son de componentes que este bloque no tocó.
CONFIRMADO, y ya estaba escrito: hay gates FINOS. `popover` mide 1 nodo en reposo
(7 en abierto), `drawer` 1, `context-menu` 1 + 14 en abierto, `color-picker`
5 + 12. Cada veredicto lo dice, y lo que sostiene esas cifras es el forzado a
mano de sus estados, no la sonda.
Guards: `--names` sin desviadas · docs:check 0 · censo 62 % (2.877 públicos de
5.137 knobs; `global` 1.145 → 912).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
}
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
if (moved) live.push(key);
|
|
|
|
|
else stillDead.push(key);
|
|
|
|
|
}
|
|
|
|
|
pending = stillDead;
|
|
|
|
|
await page.close();
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await browser.close();
|
uix(chart): temable — 9 % → 49 %, y la ficha pedía 71 claves de las que 20 llevaban el mismo valor
22 claves nuevas fusionadas en el bloque existente (17 → 39). Censo chart
9 % → **49 %**; global **56 % → 57 %** (2.638 → 2.675 públicos; <20 % 19 → 18).
LO QUE DECIDIÓ LA MEDICIÓN, no la ficha:
Las 84 filas fuera de alcance colapsaban a **26 valores distintos** — 9×
`content-secondary`, 9× `content-primary`, 9× `100%`, 6× `font-size-sm`, 6×
`font-size-xs`, 6× `font-weight-medium`, 5× `surface-default`, 4× `radius-sm`.
Nombrar por parte habría dado cinco claves para el pelo que separa marcas y
nueve para la tinta de anotación: la inflación de `spinner` con otra cara.
**El texto no recibe tokens de chart.** Toma su ROL DE TAMAÑO de los estilos con
nombre del canon —`caption` (xs) para las anotaciones, `label` (sm) para el
texto de cromo— y su tinta y su peso de las ranuras del sistema. Acuñar
`--chart-label-font-size` sería tipografía por componente, prohibido el mismo
día para text-effects. Precedente de consumir la capa: `code-block`, `kbd`,
`announce`. Doce declaraciones cambian de `var(--font-size-xs|sm)` a
`var(--style-caption|label-font-size)`, mismo valor.
**La receta sólo posee la tinta de lo que NO es texto**, cinco claves y cada una
un concepto que el CSS repite: `axis-fg` (eje + rejilla smith + conector del
funnel), `grid-fg` (rejilla + radar), `separator-fg` (el pelo de superficie
entre marcas contiguas: porción, punto activo, punto smith, pétalo, segmento),
`smith-boundary-fg` y `track-bg` (raíl del bar-list y del gauge). Más 17 de
geometría de cromo (gap, los pares row/column-gap, swatch, track, bar-segment).
**Incumplimiento heredado, corregido**: el CSS leía la coordenada CRUDA
`var(--control-height-sm, var(--space-7))` —lo que `recipe-css-contract`
prohíbe— con un respaldo muerto. El contrato guarda el bundle
`var(--size-sm-control-height)`, 1:1 con él.
**Identidad fuera de contrato**: los nueve `100%` («ocupa a tu padre») y el
`min-inline-size: max-content` del tooltip, misma clase ya adjudicada en
`date-picker`. Por eso el techo es 49 %, y está escrito en el veredicto §5.
EL INSTRUMENTO ESTABA CIEGO DE TRES MANERAS, arregladas ANTES de medir:
1. **La superficie de chart vive en DIECISÉIS rutas de demo**, una por tipo:
`/chart` monta 58 nodos de los 302 de la página y el resto vive en rutas
hermanas. El guard gana `urls`: un token está VIVO si ALGUNA superficie del
componente lo sigue, y cada URL sólo re-prueba lo pendiente (el caso de una
sola ruta cuesta exactamente lo que costaba). De paso `picker-shell` recibe
la suya: su ruta propia es un 404 y se mide dentro del date-picker — pasa de
0/31 con seis sin adjudicar a 6/31 sin tener que saberlo.
2. **El guard no leía NINGUNA propiedad SVG.** Sin `fill` / `stroke` /
`strokeWidth` / `fillOpacity` / `r` / `rx`, doce de las 39 claves de un
componente que dibuja en SVG leían muertas estando vivas. Misma clase que el
hueco de `filter` / `backdrop-filter` de ayer.
3. **El valor centinela se elegía por SUBCADENA**: `slice-stroke-width` recibía
`rgb(1, 2, 3)` por contener «stroke» —inválido para una longitud— y leía
muerta. La dimensión se decide ahora por el FINAL del nombre.
Los 28 componentes con ledger re-verificados tras los tres cambios: cero
regresiones, cero STALE. (Ojo: doce a la vez contra el mismo dev server dan
falsos exit=1 por timeout; repetir a solas antes de creerse una regresión.)
Gates: sonda **0 diffs en 161.850 valores computados** (16 rutas × 7 estados,
892 nodos en reposo) · centinela **37/39**, con `transition-duration` y `-ease`
adjudicadas midiéndolas sin la congelación (0,12s → 11,5s · cubic-bezier →
steps(4)) · censo --only 49 % · --names 39/39 conformes · component-audit PASS ·
eidos-lint 0 invalid · suite eidos con el único rojo conocido
(`skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con 72 errores
globales y 0 en los ficheros tocados.
Abre (→ next-features §13): el censo **penaliza consumir la capa tipográfica**
si no eres un primitivo —las doce declaraciones correctas siguen contando
`global`—, que es la nota de `listbox` con una segunda ocurrencia; y el número
del gauge no tiene estilo con nombre que lo represente (un readout de dato no es
un `h2`), así que sigue leyendo `--font-size-xl`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const dead = pending;
|
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
|
|
|
|
|
|
|
|
const ledger = SENTINEL_EXCEPTIONS[component] ?? {};
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
const patterns = SENTINEL_PATTERN_EXCEPTIONS.filter((p) => p.component === component);
|
|
|
|
|
const reasonFor = (k: string) =>
|
|
|
|
|
ledger[k] ?? patterns.find((p) => p.pattern.test(k))?.reason ?? null;
|
|
|
|
|
const adjudicated = dead.filter((k) => reasonFor(k) !== null);
|
|
|
|
|
const unadjudicated = dead.filter((k) => reasonFor(k) === null);
|
fix(theming): el guard aprende a decir OSCILANTE, y deja de dar falsas alarmas
Un token puede leerse vivo unas corridas y muerto otras sobre el MISMO HEAD,
sin mentir: la culpa es de la apertura del guard, no del contrato. Hasta hoy
eso rompía el ledger por los dos lados.
- Si el token está adjudicado y la corrida lo lee VIVO, el guard gritaba STALE
y la sesión siguiente borraba una adjudicación correcta.
- Si NO está adjudicado y la corrida lo lee muerto, el guard fallaba R-5.4 con
un token que alcanza perfectamente.
Una razón que empiece por OSCILLATES: describe justo eso. El guard la respeta
en los dos sentidos: adjudica cuando lee muerto y, cuando lee vivo, imprime
«oscillating — read ALIVE this run; the entry stays» en vez de STALE.
Marcadas las dos carreras ya medidas:
- emoji-picker.open-trigger-fg (17/21 vs 16/21 en el mismo código; vivo cuatro
veces, una de ellas replicando el staticPass paso a paso). Tres corridas:
vivo, vivo, muerto — exit 0 las tres, ninguna falsa alarma.
- editable.submit-fg (vivo 3 de 4, 42/56 vs 41/56). Salir del modo edición es
lo que hace un blur y el guard abre por foco + prepareWith, así que la
superficie que mide no es determinista. Cuatro corridas: exit 0 las cuatro.
Sale del plan de la revisión del 24 (P6).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
// A reason that opens with OSCILLATES: describes a token measured ALIVE that
|
|
|
|
|
// the guard reads dead only some runs (a race in the demo, not a lie). Such
|
|
|
|
|
// an entry must survive the run where the coin lands on the live side —
|
|
|
|
|
// without this, every sweep that catches it alive reports a false STALE and
|
|
|
|
|
// the next session deletes a correct adjudication (measured 2026-08-24 on
|
|
|
|
|
// emoji-picker.open-trigger-fg, alive 4 of 5 runs on the same code).
|
|
|
|
|
const oscillates = (k: string) => (reasonFor(k) ?? '').startsWith('OSCILLATES:');
|
|
|
|
|
const stale = live.filter((k) => k in ledger && !oscillates(k));
|
|
|
|
|
const oscillating = live.filter((k) => k in ledger && oscillates(k));
|
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
|
|
|
|
|
|
|
|
console.log(`sentinel ${component}: ${live.length}/${keys.length} tokens move a computed value`);
|
uix(button): temable — 55 % → 91 %, y la cascada de paleta ANULA 419 claves
Una clave nueva y 17 declaraciones anotadas. Al medirlo salió lo más gordo del
eje hasta hoy: 56 de las 106 claves públicas de este componente no pintan nada,
y no por deuda suya.
Acuñado lo único que faltaba: EL PASO `full` DE LA ESCALA DE RADIO —
`[data-rounded='full']` leía el primitivo global a pelo, así que la forma que
más se retoca (la píldora) era el único escalón fuera del contrato.
Diecisiete declaraciones pasan a excepción firmada, de dos clases: IDENTIDAD
(`fit-content` × 2 —un botón ES su contenido—, el `100 %` de `[data-block]` y
los cuatro `1px` de las cajas sr-only) y AUSENCIA (los `transparent` de `soft`,
`outline`, `ghost` y `plain`: la variante ES la ausencia de cromo, y ponerle
fondo a un `ghost` es pedir un `soft`). Diez claves de `transparent` habrían
sido justo el antipatrón que la doctrina avisa: agrupa por VALOR.
Para que la ausencia cuente, EL CENSO APRENDE LA VÁLVULA UN PISO MÁS ABAJO: un
PRIVADO cuyo valor es un literal CON su razón escrita es una ausencia firmada,
no deuda. Sin eso, cuatro privados marcaban «no deriva» y seis knobs leían
inalcanzables mientras todas las demás ramas del mismo privado leen un público.
⚠⚠ EL HALLAZGO: LA CASCADA DE PALETA ANULA LOS TONOS DEL COMPONENTE.
El último bloque del forward, `[data-button][data-color]`, resuelve
`--button-palette-*` desde el `--palette-*` GLOBAL —que `[data-color='{tono}']`
llena desde `--color-{tono}-*`— y gana a las reglas por tono por ORDEN, con la
misma especificidad (0,2,0). Medido sobre `data-color='risk'`:
`--button-risk-solid` no mueve nada, ni en `:root` ni en el nodo, mientras
`--palette-solid` y `--color-risk-solid` en el nodo repintan. Las siete
`primary-*` son el RESPALDO de esa misma regla y sólo actúan SIN `data-color`.
No es de este componente: 49 recetas emiten el mismo forward genérico y el
contrato tiene 419 CLAVES DE TONO. Medido igual en `badge` y `callout`. El
arreglo cabe en el orden de emisión —el genérico ANTES de los de tono— y no
movería un píxel, porque el valor por defecto de `--button-risk-solid` ES
`var(--color-risk-solid)`; pero cambia el contrato de la jaula del color (un
ancestro que inyecte `--palette-*` dejaría de pisar un tono semántico). Es
firma. → §13.
El techo restante son dos filas de canal de valor: `--_button-fill-finish`, el
acabado de degradado que el generador deriva por instancia.
Un error de método, anotado porque casi cuela: anclé la clave nueva en la
primera línea `'radius-xl'` del fichero y aterrizó en `meter`. Lo delató el
navegador (`--button-radius-full` sin definir ⇒ radio 0). Una inserción se
ancla en el BLOQUE del componente, nunca en una línea de clave.
Instrumento: el guard aprende `sweepAttr` (barrer una variante mientras mide) y
a estampar el TONO que la clave nombra; y el fichero de excepciones gana
PATRONES, para que una razón arquitectónica se escriba una vez y no 56.
Gates: sonda 0 diffs (416 valores; la demo monta UN botón) · equivalencia del
paso `full` comprobada aparte · centinela 34/106 con todo lo demás adjudicado ·
censo 91 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno ·
`rtl:check` 0 · `docs:check` 0 · `check` sin errores propios · captura de las
seis variantes + píldora + tono `risk`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
for (const k of adjudicated) console.log(` adjudicated ${k} — ${reasonFor(k)}`);
|
fix(theming): el guard aprende a decir OSCILANTE, y deja de dar falsas alarmas
Un token puede leerse vivo unas corridas y muerto otras sobre el MISMO HEAD,
sin mentir: la culpa es de la apertura del guard, no del contrato. Hasta hoy
eso rompía el ledger por los dos lados.
- Si el token está adjudicado y la corrida lo lee VIVO, el guard gritaba STALE
y la sesión siguiente borraba una adjudicación correcta.
- Si NO está adjudicado y la corrida lo lee muerto, el guard fallaba R-5.4 con
un token que alcanza perfectamente.
Una razón que empiece por OSCILLATES: describe justo eso. El guard la respeta
en los dos sentidos: adjudica cuando lee muerto y, cuando lee vivo, imprime
«oscillating — read ALIVE this run; the entry stays» en vez de STALE.
Marcadas las dos carreras ya medidas:
- emoji-picker.open-trigger-fg (17/21 vs 16/21 en el mismo código; vivo cuatro
veces, una de ellas replicando el staticPass paso a paso). Tres corridas:
vivo, vivo, muerto — exit 0 las tres, ninguna falsa alarma.
- editable.submit-fg (vivo 3 de 4, 42/56 vs 41/56). Salir del modo edición es
lo que hace un blur y el guard abre por foco + prepareWith, así que la
superficie que mide no es determinista. Cuatro corridas: exit 0 las cuatro.
Sale del plan de la revisión del 24 (P6).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
|
|
|
for (const k of oscillating)
|
|
|
|
|
console.log(` oscillating ${k} — read ALIVE this run; the entry stays (${reasonFor(k)})`);
|
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
|
|
|
if (stale.length)
|
|
|
|
|
console.log(
|
|
|
|
|
` STALE exception(s) — the token moves now, the ledger entry should go: ${stale.join(', ')}`
|
|
|
|
|
);
|
|
|
|
|
if (unadjudicated.length)
|
|
|
|
|
console.log(
|
|
|
|
|
` NO EFFECT, UNADJUDICATED (${unadjudicated.length}): ${unadjudicated.join(', ')}\n` +
|
|
|
|
|
` R-5.4: a public token that moves nothing and carries no written adjudication is a token that lies.`
|
|
|
|
|
);
|
|
|
|
|
process.exit(unadjudicated.length === 0 ? 0 : 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main();
|