docs(theming): la revisión del 24 entra al repo con su plan EJECUTADO — y las ocho entradas §8 que faltaban

REVIEW-theming-2026-08-24.md: 2 errores (los dos de la concurrencia, ya
corregidos), 4 desviaciones, 1 incoherencia real de gramática (las cuatro
claves de avatar, preexistentes y ahora VISIBLES), 9 refutaciones con su
medición, y el barrido del centinela sobre el LEDGER ENTERO: 59 + 23
componentes, cero sin adjudicar, cero STALE. P7 vacío.

Las OCHO entradas §8 que faltaban (emoji-picker, spin-field, pin-input,
dialog, form, skip-link, image-picker, search-field) entran redactadas desde
sus commits y VERIFICADAS por un pase escéptico independiente contra el censo,
el contrato en HEAD y en {sha}^, y los artefactos. El escéptico corrigió tres
antes de entrar — form (el patrón casa 23 claves, no 24; §13 corregido de ~63
a ~62), search-field (nueve tokens del indicador, no ocho) e image-picker (una
cifra no reproducible, reescrita) — que es exactamente para lo que existe el
pase.

Queda UNA cosa del plan, y es firma: P2, la demo como instrumento (el
precedente de chat-log). Presentada aparte con recomendación.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent df839849a4
commit 9130070bd5

@ -1350,12 +1350,12 @@ Lo que `chart` destapó (2026-08-22):
`data-button` / `data-popover-trigger`, los dos selectores casan a la MISMA
especificidad (0,1,0) y decide el ORDEN DE CARGA — que gana el componente
canónico. Censo de lo medido: `gradient-picker` 36 claves de 45 (F2-A, se
retiraron), `emoji-picker` 3 (retiradas hoy) y **`form` 24** (adjudicadas, no
retiraron), `emoji-picker` 3 (retiradas hoy) y **`form` 23** (adjudicadas, no
retiradas: son demasiadas para decidirlo sin firma). La pregunta de fondo es
la misma de §12.9 con el plano `overlay`: **qué manda, el componente compuesto
o la receta que lo compone**. Si la respuesta es «la receta», el arreglo es
subir la especificidad de esas reglas y no retirar nada; si es «el compuesto»,
hay ~63 claves públicas que retirar en tres componentes.
hay ~62 claves públicas que retirar en tres componentes.
- **R-5.3 no guarda el vocabulario DIMENSIONAL** (medido 2026-08-23 en `badge`).
Cubre la ranura de tinta (`fg` vs `color`) y la posición del modificador, pero
no que una clave POR TALLA se llame `…-height-{k}` y no

@ -772,6 +772,301 @@ commits de corrección.
en checkbox / radio-group / toggle, así que es escala del sistema y mueve
píxel en cuatro sitios.
- 2026-08-23 — **`search-field` 71 % → 100 %** (`f24abdb60`), contrato **17
claves sin tocar**, centinela **7/17 → 17/17** sin una sola adjudicación.
**CERO claves acuñadas: su 29 % de deuda eran CUATRO literales que son
IDENTIDAD**, ahora firmados con `/* literal: … */` (la válvula de
recipe-contract §3): 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%`, que la §4 pedía
acuñar como `loading-indicator-radius`: **sería un knob que SÓLO puede
romper el componente**, porque el arco es un BORDE que gira y con cualquier
otro valor la ruleta deja de leerse como una — la clase 4 de F2-B, «el 50 %
de un círculo», al pie de la letra. Censo 15 knobs → 11 (público 10, literal
4 → 0, excepción firmada 0 → 4; el `--opacity-muted` del indicador bajo
`prefers-reduced-motion` es capa de sistema, fuera del ratio). **Lo que
enseñó**: (1) **diez de sus diecisiete tokens no se habían medido NUNCA** —
la demo no monta dos estados: el indicador de carga sólo existe con
`loading` y son **NUEVE** claves suyas (el commit y el comentario del guard
dicen «ocho»; `base.ts` le da nueve, y 7 del icono + 9 + 1 = 17), y la X
sólo se desvanece con el campo VACÍO, y el escenario arranca con valor
(`clear-hidden-opacity`); con los dos interruptores en `prepareWith` **EL
ORDEN IMPORTA**: el clic en la X va 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; (2) **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: **58 de las 162 recetas** (cifra
del commit; el censo confirma las 162, pero ningún guard reproduce el 58)
resuelven así su eje `size` —incluidas `empty-state` y `banner`, cerradas
al 100 % este mes—, y pasarlas 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 (anotado en el veredicto §5 y en el
README). Diff de computed vacío (1.216 valores · 8 estados · 5 nodos),
capturas 2× antes/después idénticas byte a byte. Guards: audit PASS ·
eidos-lint 0 invalid (9 morfo-backed, 4 eidos-only) · vitest
`src/uix/eidos` 35/36 (el rojo `skin-media-player`, preexistente) · rtl 0 ·
docs 0. README con su sección «Talla y tema» y pestaña Tokens en la demo
(17 claves).
- 2026-08-23 — **`image-picker` 71 % → 100 %** (`1a41312b6`), contrato 13 →
**14** claves, centinela **14/14** con 0 adjudicadas — de 13/13 a 14/14 SIN
una sola adjudicación. **Una sola clave: `hover-button-bg`**, el único knob
que iba a un primitivo global a pelo (`--color-surface-default`), y **no es
el velo del sistema, por eso se acuña**: los dos botones flotan SOBRE la
foto, su relleno de reposo es translúcido (`color-mix(… 88%, transparent)`)
y el hover lo vuelve OPACO — un cambio de RELLENO, no una capa encima.
Medido desde el píxel hacia arriba con las transiciones congeladas: el
cambio cae en el `<button>` (el nodo con forma, radio 6 px, `srgb .988/.88`
→ `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
`highlighted` de `listbox`. **El nombre lo decidió el catálogo**: la §4 lo
llamaba `hover-rotate-bg` por el PRIMER selector de la regla y miente dos
veces (pinta rotate **y** remove, y el contrato ya llama `button-*` a ese
par desde que nació); `hover-{parte}-{slot}` es la forma de las 77 claves
`hover-*` de `base.ts` (52 nombres distintos). Los **cuatro literales se
firman, no se acuñan** (válvula de recipe-contract §3): 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 SO, que el UA sustituiría.
Censo 20 knobs → 16 (público 12 → 13, global 1 → 0, literal 4 → 0,
excepción firmada 0 → 4; sistema 3, fuera del ratio). **Lo que enseñó**: el
instrumento medía **UN nodo** — el picker arranca VACÍO y 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** (`DEMO_VARIANTS` en la sonda, `prepareWith` en el centinela), y
é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. **Anotado
y FUERA por D-TH.5**: ese hover neutro es una invención por componente (§38
/ R-4.3 mandan la capa `--state-*`), pero migrarlo cambia el default y no
basta con tocar la receta — el arquetipo del par es `action`, que HOY no
recibe velo, así que o `action` gana velo en `archetypes.css` (mueve píxel
en todo el catálogo) o el par cambia de arquetipo, que es morfo. Diff de
computed vacío (1.344 valores en 7 estados × 6 nodos), capturas 2×
antes/después idénticas.
- 2026-08-23 — **`form` 71 % → 86 %** (`9c3f17fa6`), contrato 54 → **64**
claves, centinela **22/64** con 42 adjudicadas (19 nominales + 23 por un
patrón). Diez claves que 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); más un literal
de identidad firmado. **R-5.3 cazó el nombre en el acto**: lo acuñé
`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`, el mismo día). **Lo
que enseñó**: ⚠⚠ **23 claves del contrato NO PINTAN, y no es deuda de valor:
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, y aquí son 23 claves
PÚBLICAS: decisión del autor, no del que ejecuta. Quedan adjudicadas POR
PATRÓN (`^(action-|transition-|disabled-opacity$)`, una razón arquitectónica
escrita una vez) y anotadas en §13 con la pregunta de fondo, que es la 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 ~62 claves públicas que retirar
en tres componentes. ⚠ el commit, su título y la nota de §13 dicen **24** y
«las 20 + el patrón»: el guard adjudica 19 nominales + 23 por patrón = 42,
que es lo que exige el 22/64 — off-by-one del autor, a corregir en
`next-features.md`. Las demás adjudicaciones son ramas que la demo no monta:
el layout `grid` (forzado alcanza — y 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
(`plain` contra `panel`) y los cuatro `--_form-action-*` leen el forward de
paleta THM-2. Diff de computed vacío (832 valores · 7 estados), capturas 2×
antes/después. Guards: censo `--only` 86 % · audit PASS · `--names` 0
desviadas · eidos-lint 36 morfo-backed / 0 invalid / 0 class-hooks · rtl 0 ·
docs 0 · suite eidos 439/440 (el rojo conocido).
- 2026-08-23 — **`skip-link` 71 % → 100 %** (`ebe3fe6ed`), contrato **8 claves
sin tocar**, centinela **0/8 → 8/8** con **cero adjudicaciones**. **Ninguna
clave nueva**: su 29 % de deuda eran DOS literales que son la TÉCNICA:
`inline-size: 1px` / `block-size: 1px`, la mitad del sr-only canónico —la
otra mitad es el par `clip` / `clip-path`—, ahora firmados con
`/* literal: */`, la válvula de recipe-contract §3: salen del ratio como
ausencia ESCRITA, no como deuda, y el censo baja de 8 knobs a 6. No se
acuñan porque un tema que los mueva no cambia una apariencia, ROMPE el
mecanismo — y a `0 × 0` algunos motores sacan el enlace del árbol de
accesibilidad, que es lo único que este componente no puede permitirse;
misma clase que `text-blur` y `text-scramble`. Los otros 5 knobs ya pasaban
por token público, y las dos ausencias que quedan son doctrina aplicada: 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. **Lo que
enseñó**: la OCTAVA clase de punto ciego del instrumento — toda su
superficie existe SÓLO bajo `:focus` (sr-only mientras no lo tiene, píldora
cuando sí), así que el guard leía 0 de 8: no lo enfocaba nunca, el `blur`
que hace tras abrir lo habría deshecho igual, y clicarlo tampoco vale porque
su handler manda el foco a la región de destino y 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-verificados tres del ledger: `dialog` 37/44 (la
cifra exacta de su commit), `tooltip` 15/23, `context-menu` 27/28 — cero
regresiones. Diff de computed vacío (192 valores en 7 estados, más los 52
del estado ENFOCADO —el único que se ve— medidos aparte con un script
propio: la sonda estándar mide reposo, tallas, abierto y hover, nunca foco),
capturas 2× antes/después idénticas byte a byte.
- 2026-08-23 — **`dialog` 67 % → 89 %** (`bb89174a8`), contrato 37 → **44**
claves, centinela **37/44** con 7 adjudicadas. Las siete son las dos tintas
SEMÁNTICAS que le faltaban. Cuatro para los tonos EVALUATIVOS
(`risk-border`, `risk-title-fg`, `threat-border`, `threat-title-fg`): dialog
no tiene forward de paleta THM-2 y su trato de tono es deliberadamente
estrecho —el borde y la tinta del TÍTULO, nunca la superficie del cuerpo,
para que el texto largo no compita con la señal—, pero iban a
`--color-risk-*` a pelo. Y tres para el tinte de SALIDA (`saved-border`,
`failed-border`, `dismissed-border`): el morfo escribe `data-last-action`
antes de que `data-state` pase a `closed`, así que el borde superior dice
CÓMO se cerró mientras se va; el tercero cubre `cancelled` / `dismissed` /
`dismissed-outside`, que comparten valor — todo cierre sin resultado.
`{tono}-border` es el nombre del catálogo (48 claves) y `failed-border` el
de `proof-of-human`. **El 11 % que queda son los cuatro
`--_dialog-content-*-override`, y no es deuda**: escotillas POR INSTANCIA
que el wrapper escribe INLINE desde las props `width` / `minWidth` /
`maxWidth` / `height` / `minHeight` — un público encima lo pisaría el inline
y mentiría, la clase de `--_background-gradient-image` y del triple de
`s-text-virtual-list`. Más el `width: 100%` de la hoja inferior, firmado
como identidad. ⚠ **Nada de tipografía de superficie, por §12.9**: el
contenido lleva `data-depth='modal'` y un plano declara `font-family` /
`line-height` a la misma especificidad y más tarde en la cascada; las claves
de título y descripción que ya existían son de PARTES internas, no de la
superficie del plano. **Lo que enseñó**: ⚠⚠ **`overlay-opacity` es un FALSO
POSITIVO del guard** — `dialog-overlay.svelte` escribe
`--dialog-overlay-opacity` en el `style` INLINE del velo y el guard escribe
su centinela en el `style` de cada nodo, así que se pisa a sí mismo y ve un
cambio. Medido como lo haría un TEMA (sólo desde `:root`, animaciones
congeladas) el fondo del velo **no se mueve**
(`color(srgb 0.1098 0.098 0.0902 / 0.2518)` antes y después): la clave está
en el contrato y ningún tema la alcanza. Cara opuesta de
`drag-drop.preview-z`, misma familia que el velo de `drawer` que no pinta; a
§13 con el arreglo del guard, y retirar o rescatar la clave es decisión
aparte, porque cambia quién controla la opacidad del velo. Y dos de las
siete adjudicaciones costaron una segunda pasada **por el VALOR del
atributo**: la rejilla de posición usa `top-left` / `top-right` (físicos),
no `top-start`, y el botón de cierre NO lleva `data-position` en esta demo
aunque todas las reglas que leen `close-inset` lo exigen. Diff de computed
vacío (480 valores · 8 estados · 8 nodos con el diálogo abierto), capturas
2× antes/después. Guards: censo `--only` 89 % · audit PASS · eidos-lint 15
morfo-backed / 0 invalid / 0 class-hooks · rtl 0 · docs 0 · suite eidos
439/440 (el rojo conocido).
- 2026-08-23 — **`pin-input` 65 % → 100 %** (`4b9354bba`), contrato 20 → **22**
claves, centinela **16/22** con seis adjudicadas. Su 35 % de deuda eran TRES
defectos, ninguno de diseño: un prefijo ABREVIADO —los privados se llamaban
`--_pin-cell-*` dentro de un componente llamado `pin-input`, así que el
censo no los reconocía como suyos y los cuatro knobs que los leen contaban
como deuda aunque DERIVAN de públicos; cuarto caso de la clase tras
`color-picker`, `metrics` y `time-picker`—, un respaldo A SÍ MISMO
(`font-family` leía `var(--style-label-font-family,
var(--style-label-font-family))`, un no-op con forma de defecto) y la tinta
del dígito a `--color-content-primary` a pelo. Las dos claves nuevas son
esas dos costuras (§2-A): `font-family` y `cell-fg`. **Lo que enseñó**: los
**veintidós respaldos escritos a mano** duplicaban lo que el contrato ya
declara en `:root` —código muerto, y retirarlos dio 0 diffs, que es la
prueba— pero UNO MENTÍA: `disabled-opacity` respaldaba `0.55` cuando
`--opacity-disabled` vale **0.4**. Es la ley de los `, 1.4` contra un
`--font-line-height-sm` de 1.45 que este mes ya salió en dos componentes,
aquí con más deriva: **un respaldo no es una red de seguridad, es una
segunda fuente que nadie actualiza**. Y las seis adjudicaciones enseñaron
DÓNDE vive cada estado: `filled` y `active` son atributos de la CELDA, pero
`invalid` y `readonly` se estampan en el CONTENEDOR y cascadean (soma los
OR-fusiona con el Field que las envuelve), así que forzarlos en la celda no
hace nada. Diff de computed vacío (1.856 valores · 8 estados · 8 nodos),
capturas 2× antes/después.
- 2026-08-23 — **`spin-field` ADJUDICADO: 67 % es su techo** (`2ebeb9b16`),
64 % → 67 % por UN literal firmado, contrato 20 → **20** claves, centinela
**13/20** con siete adjudicadas. **Cero claves nuevas: el techo estaba
escrito en la cabecera de su propio CSS.** 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 —«never a
parallel `--spin-field-*` chrome that would alias the Field base», dice la
cabecera—, así que acuñar ahí sería el antipatrón que el fichero prohíbe por
su nombre ([S6] no-token-aliases). 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` el ancho del botón, `* 0.34` el
glifo) viven dentro de un `calc()` sobre ese mismo privado de Field, así que
un token propio no cambiaría quién manda; y el `line-height: 1` de la celda
de glifo es identidad, FIRMADO — lo único que mueve la cifra (16/25 → 16/24
knobs en ratio). **Lo que enseñó**: (1) 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, y
forzado el atributo sobre el nodo real los tres alcanzan; (2) es una CAPA,
no un componente: sin wrapper ni ruta propia, el guard lo mide dentro de
`number-field`, declarado ahora en `urls` para que nadie tenga que saberlo;
(3) 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, y un «missing node» tampoco se cree a la primera. Las otras
cuatro adjudicadas: `focus-control-bg` (pseudo forzado por CDP),
`control-disabled-opacity` (0.48 → 0.137) y el par de transición medido sin
congelar (0,12s → 11,5s). Diff de computed vacío (2.400 valores · 8 estados
· 5 nodos), `component:audit` sin hallazgos, `docs:check` 0, el ledger en
sus 63 hunks previos, captura del anfitrión `number-field`.
- 2026-08-23 — **`emoji-picker` 61 % → 100 %** (`863c6993c`), contrato 17 →
**21** claves, centinela **17/21** con 5 adjudicadas. Siete acuñadas, **tres
retiradas por muertas** y tres literales de identidad firmados. Los nombres
los dio el catálogo: `open-trigger-fg` (`accordion` lo nombra igual),
`grid-padding-inline` + `grid-padding-block-end` (el eje lógico completo,
como `card-group` / `combobox` / `command`) y las tres del marcador de tono,
la familia `timeline.marker-{size-*,radius,bg}` — se llama `tone-marker-*` y
no `marker-*` porque el pie lleva además un selector de TONO, y `marker` a
secas sería ambiguo dentro del propio componente. Las cinco adjudicaciones,
todas forzadas sobre nodos reales: las tres del marcador (la demo monta CERO
celdas `data-tone-capable`; con el atributo puesto el `::after` alcanza —
4px → 37px, 9999px → 37px, y el fondo), `preview-name-fg` (los hijos de la
vista previa son `<span>` desnudos, sin atributo del componente, fuera del
conjunto medido) y `open-trigger-fg`. **Lo que enseñó**: (1) **el cromo de
reposo del trigger NO es suyo** — el nodo lleva también
`data-popover-trigger`, y
`[data-popover-trigger]:not([data-archetype='field-trigger'])` es
**(0,2,0)** contra el (0,1,0) de esta receta: el radio, la tinta y el tamaño
de letra los pinta `popover.css`. Retiradas las tres declaraciones, **0
diffs**, la prueba de que estaban muertas (`trigger-fg` no llegó a existir:
se acuñó y se midió muerta en el mismo pase). Es el defecto que
`gradient-picker` documentó (36 de sus 45 tokens) y la misma respuesta;
sobreviven dos, `trigger-size` porque popover dimensiona con `height` y no
con el par lógico, y `open-trigger-fg` porque su selector añade
`[data-state='open']` y gana por orden. (2) ⚠ **`open-trigger-fg` OSCILA
entre corridas** — 17/21, 16/21, 16/21 con el mismo código, medido vivo
CUATRO veces (la última replicando paso a paso el `staticPass` del guard):
el panel se cierra en algún punto de la pasada y, durante la animación de
SALIDA, el content sigue en el DOM mientras el trigger ya perdió
`data-state='open'`, que es lo que la regla selecciona, así que `reopen()`
mira el CONTENT, cree que está abierto y no reabre. Un `openMarker` sobre
el trigger NO basta (el clic de reapertura cae en esa misma ventana) y se
retiró; el arreglo real es que `reopen()` espere a la CONDICIÓN en vez de
dormir un número fijo — §13. Su entrada de ledger saldrá **STALE** en las
corridas donde alcanza, y está escrito en la razón para que nadie la borre
por eso. (3) **el instrumento de captura estaba roto desde siempre**:
`__shot.ts` tenía el puerto 5173 CABLEADO, así que cualquier sesión en otro
puerto moría con `ERR_EMPTY_RESPONSE` — probablemente por lo que los pasos
6 y 13 del protocolo se saltaron en 33 componentes sin que nadie lo notara.
Ahora toma la URL como argumento, con un `--open` para los componentes cuya
superficie sólo existe abierta. Diff de computed vacío (6.080 valores · 8
estados · 176 nodos con el panel abierto), capturas 2× antes/después,
estrenando el instrumento arreglado.
- 2026-08-23 — **`progress` 69 % → 87 %** (`2e708adae`), 2 claves. `row-gap` +
`column-gap`: el `gap` en atajo tenía DOS valores distintos, así que es un knob
por eje (molde `chart.legend-row-gap`). **Seis literales de identidad

@ -0,0 +1,202 @@
# REVIEW — theming «theme-reach»: la tanda del 23/24 en paralelo (2026-08-24)
**Alcance**: los 35 commits desde la revisión anterior (`0c29c544b`) — 23 de
componente (`emoji-picker` `meter` `spin-field` `pin-input` `dialog`
`color-swatch` `progress` `skip-link` `form` `image-picker` `search-field`
`switch` `avatar` `file-upload` `editable` `chat-log` `select` `calendar`
`chat-composer` `radio-group` `toggle` ×2 `rating-group` `card`) + 12 de
docs/fix, ejecutados por seis sesiones en paralelo sobre un worktree
compartido. Pregunta: desviaciones del protocolo §7, errores, e incoherencias
con el framework.
**Método** (cada afirmación con su artefacto):
- Refutación estática commit a commit (`scripts/__rv-equiv.mjs`): líneas del
generado atribuidas, pares `prop → token` contra el contrato DE ESE commit.
- Centinela R-5.4 re-corrido para los 23 sobre HEAD limpio.
- Guards: `component-audit` **PASS 23/23** · `eidos-lint` **0 invalid** en los
22 con morfo (el 23.º abajo, R-8) · vitest `src/uix/eidos` con un solo rojo,
el conocido (`skin-media-player`) · `rtl:check` 0 · `docs:check` 0 ·
`npm run check` COMPLETED con **72 errores, CERO en ficheros de los 23**
(el que casa «slider» es de `1857c7854`, 2026-08-03).
- Gramática de nombres (`theming-census --names`): 4543 claves públicas,
**4 desviadas, las cuatro de `avatar` y preexistentes** (I-1).
- Coherencia del vocabulario `invalid-*` en las 162 recetas (I-2).
- Artefactos §7: ficha §5 **23/23** · README «Talla y tema» **23/23** ·
pestaña `Tokens` en los 21 con ruta de demo (los 2 sin ruta, R-8) ·
capturas citadas en 22/23 mensajes de commit (el 23.º es un commit de SOLO
comentarios: no toca píxel).
- Barrido del centinela sobre **el ledger entero** (la ley del handoff):
§5 abajo.
**Veredicto corto**: el trabajo de componentes es sólido — los 23 pasan la
refutación estática, el centinela y los guards, y las dos únicas sondas que no
cerraron a cero declararon el ruido y lo probaron reproduciéndolo. Los errores
graves de la tanda no están en el CSS: están en la **concurrencia** (el índice
compartido rompió el ledger en HEAD durante cuatro commits) y en la
**contabilidad** (ocho componentes sin su entrada §8). Nada de lo encontrado
mueve un píxel.
---
## 1. Errores reales — 2, los dos de la concurrencia, los dos ya corregidos
- **E1 — HEAD llevó CUATRO commits con el ledger del centinela SIN PARSEAR.**
`007c2af52` cosió dos bloques y dejó `},,` en el literal:
`theming-sentinel-exceptions.ts` no compilaba, así que el guard R-5.4 no
estaba en rojo — estaba MUERTO, y sólo se veía en checkout limpio. Arreglado
en `88a208644` (un carácter, reconstruido desde HEAD). Causa raíz en E2.
- **E2 — Un commit fotografió el índice de OTRA sesión.** `edc019515` (toggle)
salió con tres fragmentos de `rating-group` dentro y sin la mitad de los
suyos; `007c2af52` lo reparó (y al hacerlo produjo E1). La causa es
estructural y está medida y registrada (§13 + handoff + memoria): **hay UN
solo `.git/index`**, y verificar el árbol indexado no protege de la ventana
entre mirar y confirmar. Regla nueva ya escrita: verificación y `git commit`
en la MISMA invocación de shell.
## 2. Desviaciones del protocolo — 4
- **D1 — Ocho componentes sin su entrada en el registro §8** (§7.6 paso 21):
`emoji-picker`, `spin-field`, `pin-input`, `dialog`, `form`, `skip-link`,
`image-picker`, `search-field`. Sus menciones a partir de la línea ~2156 del
PLAN son filas de la tabla §9 (con cifras VIEJAS), no entradas de registro.
`meter`, `color-swatch` y `progress` sí la tienen — la desviación es de
commits concretos, no de una sesión. → P1.
- **D2 — La demo de `chat-log` se editó como instrumento** (monta el separador
de día, un evento de sistema y el divisor de no-leídos que antes sólo
existían en la prosa; once claves no tenían nodo que pintar). El agente lo
declaró y es lo mínimo para medir — pero la regla firmada dice que **las
demos quedan FUERA del plan de theming**, así que el precedente «demo como
instrumento» es una decisión del autor, no de una sesión. → firma (P2).
- **D3 — Fichas de la tanda con «Medido: 2026-08-23» para mediciones del 24.**
Los §13 de `select` y `calendar` dicen «medido 2026-08-24»; sus fichas dicen
23. Cosmética, pero la fecha es parte del artefacto. → P3.
- **D4 — La bala «qué queda en la cola» del handoff (c05349c3f) está
incompleta**: lista 11 pendientes y omite ~14 no tocados por debajo de 90 %
(`radio-cards` 61, `password-field` 62, `tag-group` 65, `image-adjustments`
67, `separator` 67, `popover` 67, `timeline` 77, `knob` 78, `stepper` 79,
`textarea` 80, `code-block` 81, `prose` 83, `chat-message` 84, `combobox`
85…). Una cola incompleta en el handoff es un recorte de alcance en
silencio, aunque sea por omisión. Error mío de la supervisión. → P4.
## 3. Incoherencias con el framework — 1 real (preexistente), 3 de instrumento
- **I1 — Las CUATRO únicas claves del catálogo que violan la gramática de
tinta son de `avatar`, y son CONTRATO público**: `ring-color-custom`,
`badge-color-custom`, `badge-color-custom-contrast`, `group-carve-color`
(la ranura de tinta es `fg`, R-5.3). Preexistentes — ahora VISIBLES porque
`f68bac4a6` enseñó al censo a leer la IIFE de avatar. El `--names` ya
propone los nombres del codemod (`ring-fg-custom`…), pero renombrar es
romper contrato público → espera firma. Es la única desviación de gramática
en 4543 claves.
- **I2 — El vocabulario `invalid-*` de la tanda es COHERENTE, y la gramática
quedó verificada** (para que la próxima tanda no la re-derive): la regla es
**modificador + nombre del slot EXISTENTE**. `checkbox` (la caja ES la raíz,
slot `border`) → `invalid-border` desnudo; `radio-group` (slot
`indicator-border`, dos partes con borde) → `invalid-indicator-border`;
`select` (precedente interno `disabled-border/bg/fg` desnudos + catálogo con
8 `invalid-border`) → desnudo; `switch` y `file-upload` (tres partes con
borde cada uno) → `invalid-track-border` / `invalid-dropzone-border`. Los
gemelos no divergen: nombran slots distintos porque sus partes lo son.
- **I3 — `eidos-lint` no conoce la exención `CSS_ONLY_LAYERS`** que el test
vitest sí tiene: sobre `spin-field` (capa CSS compartida tras
css-field/number-field, sin morfo POR DISEÑO) el script muere con «No morfo
file» en vez de eximirlo. Hueco menor de instrumento, preexistente. → P5.
- **I4 — La columna `size` del censo infra-informa el eje que vive en el
TSC**: `radio-group` y `rating-group` subieron sus escalas al contrato con
`scope: 'size:{k}'` y retiraron los bloques `[data-size]` del CSS — la
columna (que lee el CSS) los deja de marcar (57 → 55) aunque el eje talla
existe y es MÁS canónico que antes. Nota de instrumento. → P5.
- *(Ya registrados en §13 durante la tanda, no se repiten aquí: el guard no
fotografía `transform` (3 componentes) · el barrido de descendientes de
`file-upload` (única ocurrencia en 162 recetas) · la cascada de paleta se
lleva también el tono por defecto · el canal inline filtrado al contrato son
4 claves · la sonda/guard mueren bajo `--import tsx/esm`.)*
## 4. Refutado (se comprobó, no hay hallazgo)
- **R1 — «dialog cambió defaults al acuñar tonos»**: los «4 a ojo» de
`__rv-equiv` son un límite del script (dos reglas con la misma propiedad en
un hunk se emparejan mal). El CSS real ya leía `threat`/`fulfill`/`neutral`
correctos regla a regla; ningún default se movió.
- **R2 — «pin-input cambió valores»**: los 11 pares eran retirada de respaldos
MUERTOS (`var(--pin-input-x, fallback)` → `var(--pin-input-x)`); el bloque
del contrato preexistía al commit, así que el fallback nunca aplicó.
Idéntico por construcción. Ídem los 3 de `form` (shorthand con dos tokens).
- **R3 — «faltan §8 para los DIEZ de la mañana»**: mi primera regex era
estricta de más; `meter`, `color-swatch` y `progress` SÍ registraron. El
hallazgo real queda en OCHO (D1).
- **R4 — «el 0/98 de card y el rojo de toggle»**: árbol de trabajo rancio con
el guard duplicando un bloque; desde HEAD limpio 49/98 y verde (ya en §13).
- **R5 — «72 errores de check»**: todos preexistentes, cero en ficheros de los
23 (el de slider es del 2026-08-03).
- **R6 — «el primer barrido del ledger dio 59/59 rotos»**: mi propia lista
llevaba CRLF (Python en Windows escribe `\r\n` por stdout); cada nombre
llevaba un `\r` invisible y el guard buscaba `anchor-nav\r`. La firma
«TODOS fallan igual» delata al instrumento, no al código.
- **R7 — «spin-field y color-swatch sin pestaña Tokens»**: no tienen ruta de
demo propia (color-swatch es el quinto sin ruta; spin-field es capa
compartida) — el paso no aplica.
- **R8 — «spin-field sin morfo»**: exento POR DISEÑO (`CSS_ONLY_LAYERS` del
test); lo que falla es el script de lint (I3).
- **R9 — Los centinelas de los 23**: verdes sobre HEAD limpio. Dos carreras
conocidas y ADJUDICADAS con medición: `editable.submit-fg` (vivo 3 de 4) y
`emoji-picker.open-trigger-fg` (la entrada del ledger ya dice «oscillates
run to run; measured alive four times» — cuando cae del lado vivo, el guard
avisa STALE: falsa alarma esperada, el guard no tiene vocabulario para
«oscilante, se queda»). → P6 propone dárselo.
## 5. Barrido del centinela — el ledger ENTERO (ejecutado 2026-08-24)
**59 componentes** (todo el ledger menos los 23 ya re-corridos en esta
revisión) barridos uno a uno sobre HEAD limpio: **cero NO EFFECT sin
adjudicar, cero STALE, cero errores**. Con los 23 de la tanda, el ledger
entero queda verificado el mismo día — la ley del handoff («corre el guard
sobre el LEDGER ENTERO») se cumple por segunda vez, y esta vez sin rojos.
Seis componentes leen `0/N` con el guard en verde — la firma que la doctrina
manda mirar dos veces: `button-group` 0/1 · `toggle-group` 0/1 ·
`scroll-frames` 0/4 · `clipboard` 0/6 · `code` 0/6 · `drag-drop` 0/9. Los seis
están TOTALMENTE adjudicados, y las razones muestreadas son mediciones con
`forced -> reaches` (el gap de toggle-group colapsa a 0 en el grupo attached y
alcanza al quitar el atributo; el filete de button-group sólo dibuja entre
botones attached de variante rellena; el preview de drag-drop sólo existe en
vuelo) — no es ceguera del instrumento, es superficie que la demo no monta,
con su porqué escrito.
El primer intento del barrido murió 59/59 con «no recipe block» — la lista la
había escrito Python por stdout en Windows y cada nombre llevaba ` ` (R6).
La firma «TODOS fallan IGUAL» delata al instrumento antes que al código.
**P7 queda VACÍO: el barrido no destapó nada que medir.**
## 6. Plan de solución — EJECUTADO 2026-08-24 (menos P2, que es firma)
- **P1 EJECUTADO**: las OCHO entradas §8 escritas e insertadas junto a sus
compañeras de tanda. Cada una redactada leyendo su commit, su ficha y su
README, y **verificada por un pase escéptico independiente** contra el
censo, el contrato en HEAD y en `{sha}^`, y el texto de los artefactos.
El escéptico corrigió TRES antes de entrar: `form` (el patrón del ledger
casa 23 claves, no las 24 que el commit decía — corregido también en §13,
donde el total pasa de ~63 a ~62), `search-field` (el indicador de carga son
NUEVE tokens, no ocho) e `image-picker` (una cifra de catálogo no
reproducible y un precedente mal nombrado, reescritos). Y de propina el
mismo defecto en su forma pequeña: **trece entradas existentes decían
`(COMMIT)` donde va el sha** — resueltos los trece (`df839849a`).
- **P2 — LO ÚNICO QUE QUEDA, y es tuyo**: la demo como instrumento (el
precedente de `chat-log`). Análisis y recomendación presentados aparte;
nada se toca hasta tu firma.
- **P3 EJECUTADO** (`9bbc58502`): la fecha «Medido» de las seis fichas
commiteadas pasada la medianoche dice ya 2026-08-24.
- **P4 EJECUTADO** (`9bbc58502`): la cola del handoff corregida — eran
TREINTA Y CUATRO, no once, y separados los 16 nunca tocados de los 18 que
sólo deben el README «Talla y tema». Con la regla para regenerarla del censo
y no de memoria.
- **P5 EJECUTADO** (`9bbc58502`): los dos huecos de instrumento en §13
(`eidos-lint` sin las exenciones del test; la columna `size` del censo que
se apaga cuando el eje sube al TSC).
- **P6 EJECUTADO** (`2e0f4b650`): el guard entiende `OSCILLATES:` — adjudica
cuando lee muerto y no grita STALE cuando lee vivo. Marcadas las dos
carreras medidas (`emoji-picker.open-trigger-fg`, `editable.submit-fg`);
verificado con 3 y 4 corridas, exit 0 todas.
- **P7 VACÍO**: el barrido del ledger entero (§5) no destapó ningún rojo.
Loading…
Cancel
Save

Powered by TurnKey Linux.