uix(scroll-area): temable — 94 % → 100 %, y la clave que el guard leía muerta la escriben CINCO componentes

CERO claves acuñadas. El componente llegaba con `global` a cero, `private` a
cero y trece claves públicas que ya cubrían su cromo entero; lo único fuera de
alcance era UN literal. Este commit lo firma, adjudica sus tres silenciosas y
escribe los artefactos que le faltaban.

QUÉ ENTRA

- **El literal se firma como identidad**: `opacity: 1` en
  `[data-scroll-area-scrollbar][data-state='visible']` deshace el `0` del
  estado oculto de la regla de encima — es el otro extremo de un par, no un
  knob. Un `--scroll-area-visible-opacity` sería una clave cuyo único valor
  sensato es `1`, y además asimétrica: el `0` hermano el censo ni lo cuenta.
  Precedente literal en `navigation-menu.css:360` y `select.css:363`.
- **Tres adjudicaciones en el ledger**, todas MEDIDAS sobre los nodos reales:
  - `auto-max-block` — su regla pide `[data-autosize]` y el escenario monta el
    modo fijo. Forzado sobre la raíz real: `max-block-size` none -> 1234px, y
    al quitar el token vuelve a `none`. NO es decorativa: la escriben
    `combobox`, `command`, `gradient-picker`, `select` y `palabras` sobre su
    propio ScrollArea. Retirarla habría roto cinco componentes. El atributo lo
    estampa el envoltorio de eidos desde una prop, no lo declara el morfo, así
    que la excepción de demo de PLAN §7.8 tampoco lo abre.
  - `transition-duration` / `transition-ease` — SON la transición que el guard
    congela para poder medir lo demás. Medidos sin congelar: 0.12s -> 11.5s y
    cubic-bezier(0.4, 0, 0.2, 1) -> steps(4), en la barra y en el thumb.
- README con su sección «Talla y tema» (13 claves por grupo) y veredicto §5 de
  la ficha con las cifras.

QUÉ NO SE TOCA

- `thumb-bg-hover` se queda con el modificador DETRÁS: es un hover NEUTRO, y
  los hovers neutros están EXCLUIDOS del codemod de D-TH.6 porque migran a la
  capa de estado (firma 3). Su migración sigue bloqueada por morfo — el thumb
  lleva `archetype: 'thumb'`, que no recibe velo.
- `base.ts` y `generated/base.css`: sin cambio de contrato no hay nada que
  regenerar. 13 -> 13 claves.

ARTEFACTOS

- Censo: 94 % (17 knobs · 15 públicos · 1 literal · 1 sistema) -> **100 %**
  (16 · 15 · 0 literal · 1 excepción · 1 sistema). Global 68 %, no baja.
- Sonda antes/después: **0 diffs sobre 896 valores computados**, 7 estados,
  4 nodos (raíz · viewport · barra vertical · thumb).
- Captura 2× antes y después: idénticas.
- Centinela R-5.4: **10/13**, las 3 restantes adjudicadas -> verde.
- component-audit PASS · eidos-lint 17 morfo-backed / 1 eidos-only / **0
  invalid** · `vitest run src/uix/eidos` 35/36 ficheros (el rojo conocido es
  `skin-media-player`) · rtl:check 0 · docs:check 0 · `check` con **cero**
  errores en ficheros de scroll-area.
- Prettier: los hunks de deriva del fichero son los MISMOS que en HEAD (3 en el
  README, 3 en el CSS, ambos preexistentes); mi bloque no añade ninguno.

NOTA DE CONCURRENCIA: la pestaña `Tokens` de su demo (paso 11 del protocolo) la
escribí yo, pero entró en HEAD por `9fc38b19a` + `c88b2f861` de otra sesión, que
la arrastró con un `git add` derivado y lo declaró. No se re-commitea aquí.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent 300153f9f6
commit 3282ef9786

@ -5,8 +5,8 @@
> 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**: **94%** — 15 de 16 knobs por token público
- **Knobs de apariencia**: 17 — público 15 · privado 0 · global 0 · literal 1 · sistema 1 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Medido**: 2026-08-24 · **Alcance**: **100%** — 15 de 15 knobs por token público
- **Knobs de apariencia**: 16 — público 15 · privado 0 · global 0 · literal 0 · sistema 1 · excepción 1 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 13 pública(s) — `auto-max-block`, `bg`, `radius`, `scrollbar-padding`, `scrollbar-size`, `track-bg`, `hover-track-bg`, `thumb-bg`, `thumb-bg-hover`, `thumb-radius`, `thumb-min-size`, `transition-duration`, `transition-ease`
- **Eje `size`**: no · **ficheros**: `scroll-area.css`
@ -20,20 +20,20 @@ _Ninguno._
_Ninguno._
### 1.3 Literales (1)
### 1.3 Literales (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `scroll-area.css:104` | `[data-scroll-area-scrollbar][data-state='visible']` | `opacity` | `1` |
_Ninguno._
### 1.4 Excepciones firmadas (0) — fuera del ratio
### 1.4 Excepciones firmadas (1) — 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._
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `scroll-area.css:104` | `[data-scroll-area-scrollbar][data-state='visible']` | `opacity` | `1` |
## 2. Sistema transversal (1) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
@ -75,6 +75,47 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido y CERRADO al 100 % el 2026-08-24** (94 % → 100 %, contrato **13 → 13**
claves —ninguna acuñada—, centinela **10/13** con 3 adjudicadas, diff de
computed **0 sobre 896 valores** en 7 estados, 4 nodos).
**No entra una sola clave, y ése ES el resultado.** El componente llegaba con
`global` a cero, `private` a cero y trece claves públicas que ya cubrían todo su
cromo. Lo único fuera de alcance era **un literal**: el `opacity: 1` de
`[data-scroll-area-scrollbar][data-state='visible']`. Es identidad y va firmado
con su anotación: **deshace el `0` del estado oculto de la regla de encima**, así
que es el otro extremo de un par, no un knob. Acuñar
`--scroll-area-visible-opacity` habría creado una clave cuyo único valor sensato
es `1` — y además una asimetría, porque el `0` del hermano el censo ni lo cuenta.
Precedente literal: `navigation-menu.css:360` y `select.css:363`, la misma frase
sobre el mismo par.
**Las tres silenciosas del centinela están VIVAS, y una de ellas es la más
consumida del contrato.** Medidas una a una sobre los nodos reales:
- **`auto-max-block`** — su regla es
`[data-scroll-area][data-autosize] [data-scroll-area-viewport]` y el escenario
monta el modo fijo por defecto. Forzando `data-autosize` sobre la raíz real:
`max-block-size` pasa de `none` a `1234px`, y al quitar el token vuelve a
`none`. **No es decorativa**: la escriben `combobox`, `command`,
`gradient-picker`, `select` y `palabras` sobre su propio ScrollArea para cerrar
la altura del panel. Retirarla habría roto cinco componentes.
⚠ El atributo lo estampa el ENVOLTORIO de eidos desde una prop, no lo declara
el morfo, así que la excepción de demo de PLAN §7.8 tampoco lo abre.
- **`transition-duration` / `transition-ease`** — la clase ya conocida: el guard
congela `transition` para poder medir todo lo demás, y estos DOS tokens SON la
transición. Medidos sin congelar: `0.12s → 11.5s` y
`cubic-bezier(0.4, 0, 0.2, 1) → steps(4)`, en la barra y en el thumb.
**`thumb-bg-hover` se queda con el modificador DETRÁS, a propósito.** La
gramática de D-TH.6 pediría `hover-thumb-bg`, pero los hovers neutros están
EXCLUIDOS del codemod: migran a la capa de estado por la firma 3, y renombrar lo
condenado es churn. Su migración además está bloqueada por morfo — el thumb lleva
`archetype: 'thumb'`, que no recibe velo — y sigue siendo el único knob de la
firma 3 sin resolver.
**Lo que este componente enseña**: un contrato ya completo se cierra ADJUDICANDO,
no acuñando; y la clave que el instrumento leía muerta era justamente la que
cinco componentes escriben desde fuera.
<!-- veredicto:end -->

@ -21,6 +21,17 @@ 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>> = {
'scroll-area': {
// 2026-08-24. Three keys, three measured reasons, zero retirements: one
// MODE the demo has no control for, and the transition pair the guard
// freezes in order to make every other token measurable.
'auto-max-block':
'the rule is `[data-scroll-area][data-autosize] [data-scroll-area-viewport]` and the stage mounts the default fixed-fill mode. There is no control for it, and `data-autosize` is stamped by the EIDOS wrapper from a prop, not declared by the morfo, so the demo exception of PLAN 7.8 does not open it either. Measured 2026-08-24 on the real viewport: the token alone leaves `max-block-size: none`; forcing data-autosize on the real root makes the same write reach (none -> 1234px), and removing the token restores none',
'transition-duration':
'the guard freezes transitions and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s on both transitioned properties of the scrollbar and on the one of the thumb)',
'transition-ease':
'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(4), on scrollbar and thumb)'
},
fab: {
// 2026-08-24. ONE key, and it is the guard's blind spot for `translate` —
// the same limit already adjudicated for `transform` on background,

@ -29,6 +29,43 @@ consumer composes every part explicitly so a vertical-only ScrollArea
omits the horizontal scrollbar (and the Corner) without prop
gymnastics on the root.
## Talla y tema
**13 claves públicas** en `lib/recipes/base.ts` (bloque `scroll-area`). El
componente **no tiene eje `size`** (ni `size` ni `variant`: es una decisión, ver
abajo), así que ninguna clave lleva paso de talla — el grosor de la barra es UN
knob, no una escala. La pestaña **Tokens** de su demo las resuelve en vivo.
| Grupo | Claves |
| ------------- | ---------------------------------------------------------------------- |
| raíz | `bg` · `radius` · `auto-max-block` |
| barra (track) | `scrollbar-size` · `scrollbar-padding` · `track-bg` · `hover-track-bg` |
| thumb | `thumb-bg` · `thumb-bg-hover` · `thumb-radius` · `thumb-min-size` |
| transición | `transition-duration` · `transition-ease` |
`scrollbar-size` viste tres nodos a la vez (las dos barras y la esquina, que
además reserva el hueco de la contraria), y `track-bg` viste la barra Y la
esquina: por eso una barra doble se lee como una sola pieza.
**`auto-max-block` es la clave que otros COMPONEN**: `combobox`, `command`,
`gradient-picker`, `select` y `palabras` la escriben sobre su propio ScrollArea
para cerrar la altura del panel. Es contrato de consumidor, no adorno.
**Lo que el guard R-5.4 da por silencioso** (3 de 13, todas medidas y vivas,
ninguna retirada): `auto-max-block`, cuya regla pide `data-autosize` y el
escenario monta el modo fijo por defecto; y el par `transition-duration` /
`transition-ease`, que ES la transición que el guard congela para poder medir
todo lo demás — medido sin congelar, 0.12s → 11.5s en barra y thumb.
**`thumb-bg-hover` no se renombra a `hover-thumb-bg`**: es un hover NEUTRO, y
los hovers neutros están excluidos del codemod de D-TH.6 porque migran a la capa
de estado (firma 3). Su thumb lleva `archetype: 'thumb'`, que no recibe velo, así
que esa migración es una decisión de morfo y está pendiente de firma.
**El único literal es identidad y va firmado**: el `opacity: 1` de la barra
visible deshace el `0` del estado oculto — no es un knob, es el otro extremo del
par.
## Baseline
Origin: `air/components/layout/scroll-area` (branch `morfo-runtime`),

@ -101,7 +101,7 @@
}
[data-scroll-area-scrollbar][data-state='visible'] {
opacity: 1;
opacity: 1; /* literal: identity — the visible bar IS full opacity, it undoes the 0 above */
}
[data-scroll-area-scrollbar][data-hover],

Loading…
Cancel
Save

Powered by TurnKey Linux.