|
|
# float-panel — alcance de tema: análisis y propuesta
|
|
|
|
|
|
> Generado por `node --import tsx/esm scripts/theming-census.ts --report`.
|
|
|
> Lo **medido** y la **propuesta** se regeneran; el **Veredicto** (§5) se conserva.
|
|
|
> Vista de conjunto: [README](./README.md) · método y protocolo:
|
|
|
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
|
|
|
|
|
|
- **Medido**: 2026-08-24 · **Alcance**: **84%** — 41 de 49 knobs por token público
|
|
|
- **Knobs de apariencia**: 53 — público 41 · privado 3 · global 0 · literal 5 · sistema 4 · excepción 0 _(los dos últimos, fuera del ratio)_
|
|
|
- **Contrato hoy** (`lib/recipes/base.ts`): 40 pública(s) — `outline-bg`, `outline-border`, `ghost-bg`, `ghost-border`, `ghost-shadow`, `action-radius`, `action-fg`, `action-font-size`, `hover-action-bg`, `hover-action-fg`, `resize-grip-fg`, `resize-grip-radius`, `bg`, `border`, `border-width`, `radius`, `shadow`, `shadow-topmost`, `active-shadow`, `fg`, `font-family`, `font-size`, `line-height`, `header-bg`, `header-padding-inline`, `header-padding-block`, `header-gap`, `controls-gap`, `title-font-size`, `title-font-weight`, `title-fg`, `body-padding-inline`, `body-padding-block`, `resize-size`, `resize-edge`, `button-size`, `grip-size`, `accent`, `transition-duration`, `transition-ease` · 1 privada(s) forward — `_palette-border`
|
|
|
- **Eje `size`**: no · **ficheros**: `float-panel.css`
|
|
|
|
|
|
## 1. Knobs fuera de alcance
|
|
|
|
|
|
### 1.1 Directo a primitivo global (0)
|
|
|
|
|
|
_Ninguno._
|
|
|
|
|
|
### 1.2 A través de un privado (3)
|
|
|
|
|
|
| # | fichero:línea | selector | propiedad | valor |
|
|
|
| ---: | --- | --- | --- | --- |
|
|
|
| 1 | `float-panel.css:107` | `[data-float-panel-header]` | `border-block-end` | `var(--border-width-medium) solid var(--_float-panel-accent)` |
|
|
|
| 2 | `float-panel.css:184` | `[data-float-panel-action]:focus-visible, [data-float-panel-stage-trigger]:focus-visible` | `box-shadow` | `0 0 0 2px var(--_float-panel-accent)` |
|
|
|
| 3 | `float-panel.css:255` | `[data-float-panel-resize-grip]:hover, [data-float-panel-resize-grip][data-grabbed]` | `color` | `var(--_float-panel-accent)` |
|
|
|
|
|
|
### 1.3 Literales (5)
|
|
|
|
|
|
| # | fichero:línea | selector | propiedad | valor |
|
|
|
| ---: | --- | --- | --- | --- |
|
|
|
| 1 | `float-panel.css:173` | `[data-float-panel-action], [data-float-panel-stage-trigger]` | `line-height` | `1` |
|
|
|
| 2 | `float-panel.css:231` | `[data-float-panel-resize-grip]` | `background-image` | `repeating-linear-gradient( -45deg, transparent 0 3px, currentColor 3px 4.5px )` |
|
|
|
| 3 | `float-panel.css:248` | `[data-float-panel-resize-grip]:dir(rtl)` | `background-image` | `repeating-linear-gradient(45deg, transparent 0 3px, currentColor 3px 4.5px)` |
|
|
|
| 4 | `float-panel.css:254` | `[data-float-panel-resize-grip]:hover, [data-float-panel-resize-grip][data-grabbed]` | `opacity` | `1` |
|
|
|
| 5 | `float-panel.css:259` | `[data-float-panel-resize-grip]:focus-visible` | `opacity` | `1` |
|
|
|
|
|
|
### 1.4 Excepciones firmadas (0) — fuera del ratio
|
|
|
|
|
|
Literales que llevan su anotación `/* literal: <razón> */` en la propia
|
|
|
declaración: la válvula de recipe-contract §3, la misma que honra
|
|
|
`component-audit`. **Una desviación firmada no es deuda** — se listan para que la
|
|
|
razón se lea, no para acuñarlas.
|
|
|
|
|
|
_Ninguno._
|
|
|
## 2. Sistema transversal (4) — informativo, fuera del ratio
|
|
|
|
|
|
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
|
|
|
|
|
|
| # | fichero:línea | selector | propiedad | valor |
|
|
|
| ---: | --- | --- | --- | --- |
|
|
|
| 1 | `float-panel.css:121` | `[data-float-panel-header]:focus-visible` | `box-shadow` | `inset 0 0 0 var(--ring-inset-width) var(--ring-inset-color, currentColor)` |
|
|
|
| 2 | `float-panel.css:125` | `[data-float-panel-header][data-grabbed]` | `box-shadow` | `inset 0 0 0 var(--ring-inset-width) var(--ring-inset-color, currentColor)` |
|
|
|
| 3 | `float-panel.css:239` | `[data-float-panel-resize-grip]` | `opacity` | `var(--opacity-muted)` |
|
|
|
| 4 | `float-panel.css:261` | `[data-float-panel-resize-grip]:focus-visible` | `box-shadow` | `inset 0 0 0 var(--ring-inset-width) var(--ring-inset-color, currentColor)` |
|
|
|
|
|
|
## 3. Privados de la receta — ¿de dónde sale su valor?
|
|
|
|
|
|
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
|
|
|
| --- | ---: | --- | --- | :-: |
|
|
|
| `--_float-panel-accent` | 2 | `var(--float-panel-accent)`, `var(--_float-panel-palette-border)` | private, public | no |
|
|
|
|
|
|
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_float-panel-palette-border`.
|
|
|
|
|
|
## 4. Propuesta de corrección
|
|
|
|
|
|
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (3)
|
|
|
|
|
|
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién
|
|
|
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
|
|
|
al final) y theming §6.7 (slots de color, modificador delante). Un token con
|
|
|
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
|
|
|
no distingue lo que debería — se marca `⚠`.
|
|
|
|
|
|
| token (`--float-panel-…`) | scope TSC | valor propuesto | usos |
|
|
|
| --- | --- | --- | ---: |
|
|
|
| `action-shadow` | `root` | ⚠ `var(--float-panel-accent)` / `var(--_float-panel-palette-border)` | 2 |
|
|
|
| `resize-grip-bg-image` | `root` | ⚠ `repeating-linear-gradient( -45deg, transparent 0 3px, currentColor 3px 4.5px )` / `repeating-linear-gradient(45deg, transparent 0 3px, currentColor 3px 4.5px)` | 2 |
|
|
|
| `hover-resize-grip-fg` | `root` | ⚠ `var(--float-panel-accent)` / `var(--_float-panel-palette-border)` | 2 |
|
|
|
|
|
|
### 4.2 Sin nombre mecánico (4)
|
|
|
|
|
|
- **⚠ decisión: `border-block-end` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border-block-end`.
|
|
|
- **⚠ decisión: `1` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 3: `line-height`, `opacity`.
|
|
|
|
|
|
### 4.3 Avisos sobre los tokens propuestos (1)
|
|
|
|
|
|
- **el privado `--_float-panel-accent` debe pasar a leer este público (o desaparecer)** — `--float-panel-action-shadow`, `--float-panel-hover-resize-grip-fg`
|
|
|
|
|
|
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
|
|
|
|
|
|
- [ ] **Privado que no deriva de un público** — §3 lo marca; el privado debe leer el público o desaparecer.
|
|
|
- [ ] **Velo o acento en el nodo equivocado** (`archetype: 'item'` en un envoltorio, un `background` en shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba.
|
|
|
- [ ] **Doble animación** al mover un sello a una superficie con animación propia — registro de `animationstart`/`animationend`.
|
|
|
- [ ] **Diff de computed = 0** en reposo · hover · abierto · disabled · foco, por talla, antes y después.
|
|
|
- [ ] **Centinela por token nuevo**: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
|
|
|
|
|
|
## 5. Veredicto
|
|
|
|
|
|
<!-- veredicto:start -->
|
|
|
|
|
|
**EJECUTADO 2026-08-22 — 59 % → 84 %**, 12 claves. `global` a CERO.
|
|
|
|
|
|
Lo cosido: las dos VARIANTES que ajustan desde el default elevado
|
|
|
(`outline-*` / `ghost-*`) y los botones de su barra (`action-*` +
|
|
|
`hover-action-*`), más el asa de redimensionado.
|
|
|
|
|
|
**Y de paso muere uno de los nueve fantasmas del barrido**: el asa leía
|
|
|
`var(--color-content-tertiary, var(--color-content-subtle))` — ese primitivo
|
|
|
NO EXISTE, así que pintaba el respaldo. El contrato guarda lo que de verdad
|
|
|
pintaba (`--color-content-subtle`), con lo que la referencia muerta
|
|
|
desaparece sin mover un píxel.
|
|
|
|
|
|
**Once adjudicaciones medidas**: la demo monta la variante por DEFECTO (no
|
|
|
outline ni ghost) y no tiene botón de acción genérico —sólo el control de
|
|
|
cierre—, así que se forzaron el atributo de variante y se montó el botón donde
|
|
|
el CSS lo selecciona; el radio del asa sólo pinta bajo `:focus-visible`. Las
|
|
|
once alcanzan.
|
|
|
|
|
|
**La ANOMALÍA que registré aquí quedó RESUELTA el mismo día**, en la revisión
|
|
|
adversarial del bloque: `resize-grip-fg` movía aislado y leía muerto en una
|
|
|
corrida completa porque **la pasada de hover deja el puntero sobre el último
|
|
|
nodo que tocó**, y el asa re-apunta su color al acento bajo `:hover`. Con el
|
|
|
puntero aparcado tras esa pasada, el token vive y el guard delató su propia
|
|
|
excepción como STALE.
|
|
|
|
|
|
**Lo rojo que queda es ANTERIOR**: 12 tokens del bloque viejo, y seis de ellos
|
|
|
—`bg`, `border`, `border-width`, `shadow`, `font-family`,
|
|
|
`line-height`— son el mismo conjunto que en `popover`: el PLANO de profundidad
|
|
|
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 -->
|