fix(theming): float-panel tenía DOCE rojos sin medir — el guard R-5.4 vuelve a verde

El barrido del centinela sobre TODO el ledger (59 componentes) destapó que
`float-panel` fallaba: 17/40 con doce tokens públicos sin adjudicar. No es
regresión —la bisección de las NUEVE versiones del guard desde `cd6a9f4e3` da
siempre 17/40 con los mismos doce— sino un rojo que su propio commit del 22
dejó escrito («lo rojo que queda son 12 tokens anteriores») y que nadie midió.
Y la frase del handoff «desde el 23 no queda ningún rojo del bloque viejo» era
FALSA: se adjudicaron drawer, metrics y color-picker; éste no. Corregida, con
la lección: un componente cuyo commit dice «lo que queda» NO está cerrado, y la
única forma de saberlo es correr el guard sobre el ledger ENTERO.

LOS DOCE, MEDIDOS UNO A UNO

SEIS son el plano de profundidad, y la predicción del 22 era exacta: el
contenido lleva data-depth=overlay y [data-depth=overlay] declara background,
border, box-shadow, font-family y line-height a la MISMA especificidad (0,1,0)
y más tarde en la cascada. Verificado moviendo los tokens del propio plano
sobre el nodo (superficie, borde, fuente y leading lo repintan) y enumerando
las reglas que casan. border-width cae con border (el plano escribe el
shorthand). Misma adjudicación que tooltip, misma decisión abierta (§12.9).

⚠ Y una corrección a mi propio diagnóstico: primero atribuí la sombra a
[data-topmost] porque el panel de la demo arranca frontal. Falso — quitando el
atributo sigue muerta: es el plano también. Lo que sí depende de topmost es su
compañero shadow-topmost, que SÍ alcanza (→ rgb(4,5,6)) porque su regla es
(0,2,0) y le gana al plano: la prueba de que la causa es la cascada y no un
token roto.

TRES son estados o partes que la demo no monta: active-shadow (forzado
data-dragging → alcanza), resize-size (lo consumen las asas de ESQUINA
se/ne/sw/nw y la demo monta sólo las de borde, e y s) y button-size (lo
consumen [data-float-panel-action] y [data-float-panel-stage-trigger], ninguno
montado — el único control es el cierre, un <Button> compuesto que se
dimensiona solo: el mismo grupo que las tres action-* ya adjudicadas).

UNO es el PUENTE DE PALETA: el contenido lleva data-color, así que
--_float-panel-accent viene del forward THM-2 (--_float-panel-palette-border →
rgb(7,8,9)); sin data-color la clave alcanza (→ rgb(1,2,3)).

DOS son la ley del congelado: transition-duration / -ease no pueden moverse
bajo un guard que congela `transition`. Pasada aparte: 0.12s → 11.5s y
cubic-bezier(0.4,0,0.2,1) → steps(7).

DE PASO, DOS ARREGLOS DE LA MISMA REVISIÓN

- `background.parallax-travel` estaba MAL CLASIFICADO como estructural («el
  escenario no tiene scroll timeline»): lo tiene (animation-timeline: view()).
  Forzando data-parallax + speed 1, el translate sigue al token (14,6 → 60,5 px
  al mismo scrollY). La causa real es que el guard NO fotografía translate ni
  transform — registrado en §13 junto al hueco de mask-image, para entrar en
  PROPS en un pase con re-verificación del ledger entero.
- `cropper`: el hueco de composición que el README daba por «anotado» y no lo
  estaba, ahora es fila de §Gaps con su disposición. Y las tablas de tokens de
  `cropper` y `waveform` pasan a la sección «Talla y tema», el molde del resto.

La nueva constante PLANE_SUPERSEDED se iza sobre el objeto (un `const` usado
antes de su declaración reventaba el guard, y el fallo se veía sólo mirando el
exit REAL: tras la tubería, `$?` es el del grep).

Verificación: R-5.4 float-panel 17/40 con 23 adjudicaciones, cero STALE, exit 0
· docs:check 0 · el ledger vuelve a sus 63 hunks de prettier preexistentes (mis
entradas se reformatearon a mano, sin tocar las ajenas).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 0e01649f79
commit 233ef989b3

@ -130,4 +130,48 @@ gana sobre el panel (§12.9, pendiente de firma).
Sonda antes/después: **0 diffs en 1.890 valores** (9 nodos × 7 estados).
---
**ADJUDICADO 2026-08-23 — los DOCE, uno a uno.** Quedaron escritos como «rojo
que queda» el 22 y nadie los midió: el guard R-5.4 **fallaba** en este
componente y el handoff daba por cerrado el bloque viejo. Ninguna regresión —la
bisección de las nueve versiones del guard desde `cd6a9f4e3` da siempre 17/40
con estos mismos doce—, sólo un rojo sin medir. Ahora el guard pasa: **17/40 con
23 adjudicaciones, cero STALE**.
**SEIS son el plano, y la predicción del 22 era exacta.** El contenido lleva
`data-depth='overlay'`, y `[data-depth='overlay']` declara `background`,
`border`, `box-shadow`, `font-family` y `line-height` a la MISMA especificidad
(0,1,0) y más tarde en la cascada. Verificado moviendo los tokens del propio
plano sobre este nodo (superficie, borde, fuente y leading lo repintan) y
enumerando las reglas que casan: la del plano está ahí, después de la de la
receta. `border-width` cae con `border` porque el plano escribe el shorthand.
Misma adjudicación que `tooltip`, misma decisión abierta.
⚠ **Y una corrección a mi propio diagnóstico**: primero atribuí la sombra a
`[data-topmost]` (el panel de la demo arranca frontal). Falso: quitando el
atributo, `--float-panel-shadow` sigue muerto. Es el plano también. Lo que sí
depende de `topmost` es su compañero **`shadow-topmost`, que SÍ alcanza**
(→ `rgb(4,5,6)`) porque su regla es (0,2,0) y le gana al plano — la prueba de
que la cascada es la explicación y no un token roto.
**Tres son estados o partes que la demo no monta**: `active-shadow` (forzado
`data-dragging` → alcanza), `resize-size` (lo consumen las asas de ESQUINA
`se/ne/sw/nw` y la demo monta sólo las de borde, `e` y `s`) y `button-size`
(lo consumen `[data-float-panel-action]` y `[data-float-panel-stage-trigger]`,
ninguno montado — el único control es el cierre, que es un `<Button>` compuesto
y se dimensiona solo: el mismo grupo que las tres `action-*` ya adjudicadas).
**Uno es el PUENTE DE PALETA** (§3.pre, no es deuda): el contenido lleva
`data-color`, así que `--_float-panel-accent` viene del forward THM-2
(`--_float-panel-palette-border` → `rgb(7,8,9)`); quitando `data-color`, la
clave alcanza (→ `rgb(1,2,3)`). Es la base sin color, no una clave muerta.
**Dos son la ley del congelado**: `transition-duration` / `-ease` no pueden
moverse bajo un guard que congela `transition` para hacer medible todo lo demás.
Pasada aparte sin congelar: `0.12s → 11.5s` y
`cubic-bezier(0.4,0,0.2,1) → steps(7)`.
Sin cambios de código: cero diffs por construcción.
<!-- veredicto:end -->

@ -1282,6 +1282,15 @@ Lo que `chart` destapó (2026-08-22):
waveform, chronos, los pickers) puede pasarle primitivos crudos sin que el
censo ni `recipe-css-contract` lo vean. Medir cuántas hay antes de decidir si
D-TH.2 se ensancha.
- **El guard no fotografía `translate` ni `transform`** (medido 2026-08-23 en
`background`, y ya latente en `drawer.handle-active-scale`). El paralaje entra
por `translate` sobre la capa; con `data-parallax` forzado y speed 1 el token
SIGUE al scroll (14,6 → 60,5 px al mismo `scrollY`), pero el guard no puede
verlo. Tres tokens vivos leen muertos por esta causa y una adjudicación estuvo
MAL CLASIFICADA un día («estructural, el escenario no tiene scroll timeline» —
falso: lo tiene, `animation-timeline: view()`). Va con el hueco de
`mask-image`: los tres a `PROPS` en el mismo pase, y **re-verificando los ~59
con ledger** (el barrido de `REVIEW-theming-2026-08-23.md` §5 es la base).
- **El guard no fotografía `mask-image`** (medido 2026-08-23 en `background`).
Sus dos tokens de desvanecido (`fade-size`, `fade-at`) alimentan un
`mask-image` radial y leen muertos aunque están vivos — medidos a mano, el

@ -23,11 +23,20 @@ y `result` 14 % → **90 %**, los dos con `global` a CERO tras la COSTURA. El
censo cuenta 54 knobs menos: los literales ANOTADOS salen del ratio (clase
`exception`). Y `prose` 1 % → **83 %**.
**Desde el 2026-08-23 NO queda ningún rojo del bloque viejo**: `drawer` (24),
`metrics` (18) y `color-picker` (25) están medidos uno a uno y adjudicados por
escrito; el guard R-5.4 pasa en todos. Los tres hallazgos reales que salieron
—el velo del cajón que no pinta, las tallas del cajón que soma escribe inline y
el eje `size` del picker que muere en el portal— están en §13 y esperan firma.
**Desde el 2026-08-23 (tarde) NO queda ningún rojo del bloque viejo** —
`drawer` (24), `metrics` (18), `color-picker` (25) y **`float-panel` (12)**
medidos uno a uno y adjudicados por escrito; el guard R-5.4 pasa en todos. Los
tres hallazgos reales que salieron —el velo del cajón que no pinta, las tallas
del cajón que soma escribe inline y el eje `size` del picker que muere en el
portal— están en §13 y esperan firma.
⚠ **Esta frase estuvo MAL un día**: se escribió el 23 por la mañana dando por
cerrado el bloque viejo cuando `float-panel` seguía con sus doce rojos sin
medir, escritos como «lo que queda» en su propio commit del 22. Lo destapó el
barrido del centinela sobre TODO el ledger (`REVIEW-theming-2026-08-23.md`).
**Un componente cuyo commit dice «lo rojo que queda» NO está cerrado**, y la
única forma de saberlo es correr el guard sobre el ledger entero, no sobre los
que tocas.
## LO QUE DEJÓ LA SESIÓN DEL 2026-08-23

@ -17,6 +17,9 @@
* (2026-08-21) unless dated otherwise.
*/
const PLANE_SUPERSEDED =
'SUPERSEDED by the depth plane: the part carries data-depth=overlay, and [data-depth=overlay] declares this property at the same specificity (0,1,0) and LATER in the cascade, so the recipe key is bypassed (see next-features 12.9, measured on tooltip, popover and float-panel)';
export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
background: {
// 2026-08-23. Its parts are independent OPT-IN layers and only ONE pattern
@ -27,7 +30,8 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
//
// The mask: the guard does not read `mask-image`, so both read dead while
// being perfectly alive (instrument gap, next-features §13).
'fade-size': 'the guard does not snapshot mask-image; measured by hand → reaches (the radial mask changes)',
'fade-size':
'the guard does not snapshot mask-image; measured by hand → reaches (the radial mask changes)',
'fade-at':
'the guard does not snapshot mask-image; measured by hand → reaches (at 50% 35% → at 11% 50%)',
// The ink context: only under `on='dark'` / `on='light'`.
@ -52,16 +56,17 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'the gradient layer is exclusive with the pattern one the sweep needs; forced data-kind=gradient → reaches (24s → 42s)',
// The spotlight is off on the default stage and, once on, it repaints the
// pattern layer it shares a node with — measured on its own pass.
'spotlight-size': 'spotlight is off in the demo; forced data-spotlight → reaches (40% 40% → 42px)',
'spotlight-size':
'spotlight is off in the demo; forced data-spotlight → reaches (40% 40% → 42px)',
'spotlight-strength':
'spotlight is off in the demo; forced data-spotlight → reaches (the mix stop moves)',
'spotlight-fg':
"spotlight is off AND the demo layer carries data-color, where --palette-solid wins by design; forced data-spotlight with no data-color → reaches",
'spotlight is off AND the demo layer carries data-color, where --palette-solid wins by design; forced data-spotlight with no data-color → reaches',
// Structural, not conditional: the travel only enters through the
// scroll-linked `background-parallax` keyframes, which need a scroll
// timeline the measured stage does not have.
'parallax-travel':
'only enters via the scroll-linked keyframes (--_background-parallax-offset); the measured stage has no scroll timeline',
'the demo boots at speed 0, so no layer carries data-parallax; forced data-parallax + speed 1 on the real layer, the scroll-linked translate follows it (14.6 -> 60.5 px at the same scrollY). The guard would not see it anyway: translate / transform are not in its property snapshot (next-features 13)',
// No node at all: the demo never mounts the pause control.
'pause-offset': 'the pause control is not mounted in the demo (0 nodes on the page)',
'pause-z': 'the pause control is not mounted in the demo (0 nodes on the page)'
@ -87,8 +92,7 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'readonly is off in the demo; forced data-readonly → reaches (oklch(0.5032 0 0) → rgb(1,2,3))',
'segment-readonly-decoration':
'readonly is off in the demo; forced data-readonly → reaches (underline dotted → overline wavy)',
'disabled-opacity':
'disabled is off in the demo; forced data-disabled → reaches (0.4 → 0.123)',
'disabled-opacity': 'disabled is off in the demo; forced data-disabled → reaches (0.4 → 0.123)',
// The swatch scale: only the step in force paints. Forced each size on the
// real root and measured the swatch — every step reaches.
'swatch-size-xs': 'only the size in force paints; forced data-size=xs → reaches (14px → 77px)',
@ -450,7 +454,42 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'action-font-size': 'same button; mounted -> reaches (16px -> 1234px)',
'hover-action-bg': 'same button, and only under :hover; same mounting pass',
'hover-action-fg': 'same button, and only under :hover; same mounting pass',
'resize-grip-radius': 'only under :focus-visible; focused the grip -> reaches (4px -> 1234px)'
'resize-grip-radius': 'only under :focus-visible; focused the grip -> reaches (4px -> 1234px)',
// 2026-08-23. The twelve the 2026-08-22 commit left written as "rojo que
// queda" and nobody measured. Measured now, one by one, on the real panel.
//
// SIX are the depth plane, exactly as that commit predicted: the content
// carries `data-depth='overlay'`, and `[data-depth='overlay']` declares
// background / border / box-shadow / font-family / line-height at the SAME
// specificity (0,1,0) and LATER in the cascade. Verified by moving the
// plane's own tokens on this node: surface, border, font and leading
// repaint it, and the plane's `box-shadow` rule is in its matched list.
// Same adjudication as `tooltip`, same open decision (PLAN 12.9).
bg: PLANE_SUPERSEDED,
border: PLANE_SUPERSEDED,
'border-width': PLANE_SUPERSEDED + ' (the plane writes the `border` shorthand)',
shadow:
PLANE_SUPERSEDED +
'; `shadow-topmost` DOES reach — its [data-topmost] rule is (0,2,0) and beats the plane (-> rgb(4,5,6))',
'font-family': PLANE_SUPERSEDED,
'line-height': PLANE_SUPERSEDED,
// Three states / parts the demo does not mount.
'active-shadow':
'only under [data-dragging] / [data-resizing]; forced -> reaches (rgba(15,23,42,0.22) 0 28px 72px -> rgb(1,2,3))',
'resize-size':
'the CORNER handles (se/ne/sw/nw) carry it and the demo mounts only the edge ones (e, s)',
'button-size':
'consumed by [data-float-panel-action] / [data-float-panel-stage-trigger], neither mounted in the demo — the only control is the close Button, which is a composed <Button> and sizes itself (same group as the action-* entries above)',
// The palette bridge (THM-2), not debt: with a colour stamped the forward
// redefines the private and this key is only the no-colour base.
accent:
'the content carries data-color, so --_float-panel-accent comes from the THM-2 forward (--_float-panel-palette-border -> rgb(7,8,9)); with data-color removed it reaches (-> rgb(1,2,3))',
// The known law: the guard freezes transitions to make everything else
// measurable. Unfrozen pass on the panel itself.
'transition-duration':
'frozen by the guard by design; unfrozen pass -> reaches (0.12s -> 11.5s)',
'transition-ease':
'frozen by the guard by design; unfrozen pass -> reaches (cubic-bezier(0.4,0,0.2,1) -> steps(7))'
},
drawer: {
// The guard OPENS the drawer before testing, and the overlay then covers the

@ -80,7 +80,7 @@ anatomy and adds the canvas Blob + the circular mask + the perceptual layer.
zag machine guards the same way). Background shows `grab` when zoomed; the
selection keeps `move`.
## Recipe tokens
## Talla y tema
Public, themeable — `--cropper-*`: `viewport-bg/radius`, `mask-bg`, `grid-line`,
`selection-border(-width)`, `handle-size/bg/border(-width)` and the zoom
@ -101,13 +101,22 @@ hook eidos-only —no es parte del morfo—, así que la capa de estado del sist
(`--state-hover` sobre un `data-archetype`) no le llega. Es cola de la **firma
3** (migración de los hover neutros a la capa de estado), y darle arquetipo toca
morfo. De paso: ese botón debería COMPONER `IconButton` en vez de ser un
`<button>` con cromo propio — hueco de composición, anotado.
`<button>` con cromo propio — hueco de composición, en §Gaps.
`eidos-lint cropper` → invalid 0 (13 morfo-backed selectors). CSS imported from the
wrapper (code-split) — intentionally NOT in `eidos/index.css`.
## Gaps
- **El botón de zoom no compone `IconButton`** (2026-08-23). Es un `<button>`
con cromo propio —radio, borde, fondo, tinta, tamaño y foco escritos a mano en
la receta— donde el ecosistema ya tiene la primitiva. Consecuencias medidas: su
`:hover` tiene que pintarse a mano (`[data-cropper-zoom-btn]` es hook
eidos-only, sin `data-archetype`, así que la capa de estado del sistema no le
llega) y su cromo son seis claves que el `IconButton` daría gratis.
**Disposición: implementar** — mueve píxel, así que va en su propio cambio,
fuera del eje de theming.
| Gap | Disposition | Notes |
| --- | --- | --- |
| Keyboard move/resize/zoom of the selection | **diferir** | v1 is pointer-driven; arrows-to-move (FloatPanel grab-mode style) is a follow-up. |

@ -51,7 +51,7 @@ del plan (F-1.2).
publicos (`--waveform-fg`, `--waveform-played-fg`) re-tintables por
tema/scope; un eje `color` llegara si un consumidor real lo pide.
## Tokens
## Talla y tema
`waveform: { height-xs…xl, bar-width, fg, buffered-fg, played-fg,
playhead-width }` en `lib/recipes/base.ts` (nombres post-codemod D-TH.6,

Loading…
Cancel
Save

Powered by TurnKey Linux.