From 3282ef9786237cf442f1a72480b3e7714b8df9a5 Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 24 Aug 2026 04:09:17 +0200 Subject: [PATCH] =?UTF-8?q?uix(scroll-area):=20temable=20=E2=80=94=2094=20?= =?UTF-8?q?%=20=E2=86=92=20100=20%,=20y=20la=20clave=20que=20el=20guard=20?= =?UTF-8?q?le=C3=ADa=20muerta=20la=20escriben=20CINCO=20componentes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/audit/theming/scroll-area.md | 59 ++++++++++++++++--- scripts/theming-sentinel-exceptions.ts | 11 ++++ .../eidos/components/scroll-area/README.md | 37 ++++++++++++ .../components/scroll-area/scroll-area.css | 2 +- 4 files changed, 99 insertions(+), 10 deletions(-) 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],