diff --git a/docs/audit/theming/scroll-area.md b/docs/audit/theming/scroll-area.md index 93c2edf0a..c56ae7a1c 100644 --- a/docs/audit/theming/scroll-area.md +++ b/docs/audit/theming/scroll-area.md @@ -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: */` 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 `⚠`. -_(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. diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index 670b0ef8c..4555fe6b6 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -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> = { + '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, diff --git a/src/uix/eidos/components/scroll-area/README.md b/src/uix/eidos/components/scroll-area/README.md index df40a7b54..e54800875 100644 --- a/src/uix/eidos/components/scroll-area/README.md +++ b/src/uix/eidos/components/scroll-area/README.md @@ -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`), diff --git a/src/uix/eidos/components/scroll-area/scroll-area.css b/src/uix/eidos/components/scroll-area/scroll-area.css index 171aeba8d..714ed2284 100644 --- a/src/uix/eidos/components/scroll-area/scroll-area.css +++ b/src/uix/eidos/components/scroll-area/scroll-area.css @@ -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],