From 1f785b6f2d541c89febaf09dc3b732072773e820 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 23 Aug 2026 17:17:36 +0200 Subject: [PATCH] =?UTF-8?q?uix(s-text-virtual-list):=20temable=20=E2=80=94?= =?UTF-8?q?=2058=20%=20=E2=86=92=2070=20%,=20y=20su=20techo=20es=20=C3=A9s?= =?UTF-8?q?e=20porque=20el=20triple=20es=20un=20CANAL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CERO claves nuevas: dos literales firmados y tres declaraciones adjudicadas por doctrina. Diff de computed 0 sobre 2.240 valores en 7 estados; R-5.4 9/9 sin una sola adjudicación — su contrato ya era sano. LOS TRES QUE FALTAN SON CANAL DE VALOR, NO DEUDA font-family, font-size y font-weight leen var(--_s-tvl-*, ), y el privado lo escribe el componente INLINE en cada render: eidos.resolve(size,'md') y sus dos hermanos SIEMPRE producen un valor, así que el estilo inline siempre está y el fallback del CSS sólo cubre el render previo a que aterrice. Un --s-text-virtual-list-font-* quedaría pisado por ese inline y MENTIRÍA — la clase de --gp-current-gradient, del preview-z de drag-drop y del canal de rect del indicador de navigation-menu. El mando aquí ES la prop (size / family / weight), y el tema llega por donde viven esos tokens: la capa tipográfica. Y hay una razón dura para que el triple viaje por una variable y no por un token de receta: el canvas que mide el texto lee getComputedStyle del contenedor, así que la fuente pintada y la medida tienen que ser la MISMA — una indirección que pudiera divergir rompería el cálculo de anchuras de la virtualización. LOS DOS LITERALES SON IDENTIDAD La lista ES el ancho de su contenedor; el espaciador ES el del scroller. Firmados con su anotación, fuera del ratio. Su techo real es el 70 % y está medido: mismo caso que listbox (68 % a propósito) o waveform (86 %). Lo que queda fuera no es deriva, es una decisión con dueño escrita. VERIFICACIÓN Sonda antes/después 0 diffs (2.240 valores · 7 estados · 10 nodos) · R-5.4 9/9 · component:audit PASS · censo 70 % con las tres filas restantes explicadas · eidos-lint 4 morfo-backed / 0 invalid / 0 class-hooks · rtl:check 0 · docs:check 0 · suite eidos 439/440 (el rojo es skin-media-player, el conocido). Co-Authored-By: Claude Opus 5 --- docs/audit/theming/README.md | 6 +- docs/audit/theming/s-text-virtual-list.md | 58 ++++++++++++++----- .../components/s-text-virtual-list/README.md | 20 +++++++ .../s-text-virtual-list.css | 12 +++- .../s-text-virtual-list/+page.svelte | 10 +++- 5 files changed, 87 insertions(+), 19 deletions(-) diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md index bec7257cf..5124e15ae 100644 --- a/docs/audit/theming/README.md +++ b/docs/audit/theming/README.md @@ -10,8 +10,8 @@ - **Medido**: 2026-08-23 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/` - **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**? -- **Alcance global**: **66%** — 3025 de 4598 knobs pasan por un token público del componente -- **Reparto**: público 3025 · privado 318 · global 838 · literal 417 · sistema transversal 509 · excepción firmada 114 _(los dos últimos, fuera del ratio)_ +- **Alcance global**: **66%** — 3025 de 4596 knobs pasan por un token público del componente +- **Reparto**: público 3025 · privado 318 · global 838 · literal 415 · sistema transversal 509 · excepción firmada 116 _(los dos últimos, fuera del ratio)_ - **Sin token público propio**: 24 · **alcance < 20 %**: 12 · **alcance 100 %**: 26 · **con eje `size`**: 57 ## Cómo se lee @@ -134,7 +134,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [toggle-group](./toggle-group.md) | 50% | 2 | 1 | 0 | 1 | 0 | 0 | 1 | – | | [sidebar](./sidebar.md) | 55% | 45 | 23 | 0 | 14 | 5 | 3 | 50 | – | | [audio-player](./audio-player.md) | 56% | 18 | 10 | 0 | 8 | 0 | 0 | 8 | – | -| [s-text-virtual-list](./s-text-virtual-list.md) | 58% | 12 | 7 | 0 | 3 | 2 | 0 | 9 | – | | [badge](./badge.md) | 59% | 22 | 13 | 6 | 1 | 2 | 0 | 65 | y | | [drawer](./drawer.md) | 60% | 74 | 43 | 8 | 16 | 5 | 2 | 54 | y | | [cropper](./cropper.md) | 60% | 27 | 15 | 0 | 3 | 7 | 2 | 15 | – | @@ -156,6 +155,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [listbox](./listbox.md) | 68% | 37 | 23 | 9 | 1 | 1 | 3 | 21 | – | | [progress](./progress.md) | 69% | 35 | 24 | 2 | 3 | 6 | 0 | 41 | y | | [color-swatch](./color-swatch.md) | 70% | 10 | 7 | 0 | 3 | 0 | 0 | 12 | y | +| [s-text-virtual-list](./s-text-virtual-list.md) | 70% | 10 | 7 | 0 | 3 | 0 | 0 | 9 | – | | [form](./form.md) | 71% | 52 | 36 | 8 | 5 | 2 | 1 | 54 | y | | [image-picker](./image-picker.md) | 71% | 20 | 12 | 0 | 1 | 4 | 3 | 13 | – | | [search-field](./search-field.md) | 71% | 15 | 10 | 0 | 0 | 4 | 1 | 17 | y | diff --git a/docs/audit/theming/s-text-virtual-list.md b/docs/audit/theming/s-text-virtual-list.md index 31e48d10a..03ea4a8cf 100644 --- a/docs/audit/theming/s-text-virtual-list.md +++ b/docs/audit/theming/s-text-virtual-list.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-23 · **Alcance**: **58%** — 7 de 12 knobs por token público -- **Knobs de apariencia**: 12 — público 7 · privado 0 · global 3 · literal 2 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ +- **Medido**: 2026-08-23 · **Alcance**: **70%** — 7 de 10 knobs por token público +- **Knobs de apariencia**: 10 — público 7 · privado 0 · global 3 · literal 0 · sistema 0 · excepción 2 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 9 pública(s) — `line-height`, `fg`, `bg`, `border-width`, `border-fg`, `radius`, `row-padding-inline`, `row-border-width`, `row-border-fg` - **Eje `size`**: no · **ficheros**: `s-text-virtual-list.css` @@ -16,29 +16,29 @@ | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `s-text-virtual-list.css:15` | `[data-s-text-virtual-list]` | `font-family` | `var(--_s-tvl-font-family, var(--font-family-primary))` | -| 2 | `s-text-virtual-list.css:16` | `[data-s-text-virtual-list]` | `font-size` | `var(--_s-tvl-font-size, var(--font-size-md))` | -| 3 | `s-text-virtual-list.css:17` | `[data-s-text-virtual-list]` | `font-weight` | `var(--_s-tvl-font-weight, var(--font-weight-regular))` | +| 1 | `s-text-virtual-list.css:23` | `[data-s-text-virtual-list]` | `font-family` | `var(--_s-tvl-font-family, var(--font-family-primary))` | +| 2 | `s-text-virtual-list.css:24` | `[data-s-text-virtual-list]` | `font-size` | `var(--_s-tvl-font-size, var(--font-size-md))` | +| 3 | `s-text-virtual-list.css:25` | `[data-s-text-virtual-list]` | `font-weight` | `var(--_s-tvl-font-weight, var(--font-weight-regular))` | ### 1.2 A través de un privado (0) _Ninguno._ -### 1.3 Literales (2) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `s-text-virtual-list.css:14` | `[data-s-text-virtual-list]` | `inline-size` | `100%` | -| 2 | `s-text-virtual-list.css:33` | `[data-s-text-virtual-list-spacer]` | `inline-size` | `100%` | +_Ninguno._ -### 1.4 Excepciones firmadas (0) — fuera del ratio +### 1.4 Excepciones firmadas (2) — 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 | `s-text-virtual-list.css:22` | `[data-s-text-virtual-list]` | `inline-size` | `100%` | +| 2 | `s-text-virtual-list.css:41` | `[data-s-text-virtual-list-spacer]` | `inline-size` | `100%` | ## 2. Sistema transversal (0) — informativo, fuera del ratio Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). @@ -81,6 +81,38 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 — 58 % → 70 %.** CERO claves nuevas: dos literales +firmados y tres declaraciones adjudicadas por doctrina. Diff de computed **0 +sobre 2.240 valores en 7 estados**; guard R-5.4 **9/9**, sin una sola +adjudicación — su contrato ya era sano. + +**Los tres que faltan son un CANAL DE VALOR, no deuda.** `font-family`, +`font-size` y `font-weight` leen `var(--_s-tvl-*, )`, y el privado lo +escribe el componente INLINE en cada render: +`eidos.resolve(size, 'md')` / `(family, 'primary')` / `(weight, 'regular')` +siempre producen un valor, así que el estilo inline siempre está y el fallback +del CSS sólo cubre el render previo a que aterrice. Un +`--s-text-virtual-list-font-*` quedaría pisado por ese inline y **mentiría** — +la clase de `--gp-current-gradient`, del `preview-z` de drag-drop y del canal de +rect del indicador de `navigation-menu`. + +El mando aquí ES la prop (`size` / `family` / `weight`), y el tema llega por +donde viven esos tokens: la capa tipográfica. Hay además una razón dura para que +el triple viaje por una variable y no por un token de receta: **el canvas que +mide el texto lee `getComputedStyle` del contenedor**, así que la fuente pintada +y la medida tienen que ser la MISMA — cualquier indirección que pudiera +divergir rompería el cálculo de anchuras de la virtualización. + +**Los dos literales son identidad**: la lista ES el ancho de su contenedor y el +espaciador ES el del scroller. Firmados con su anotación, fuera del ratio. + +**Su techo real es el 70 %**, y está medido. Mismo caso que `listbox` (68 % a +propósito) o `waveform` (86 %): lo que queda fuera no es deriva, es una decisión +con dueño escrita. + +**Verificación**: sonda antes/después **0 diffs** (2.240 valores · 7 estados · 10 +nodos) · R-5.4 **9/9** · `component:audit` PASS · censo 70 % con las tres filas +restantes explicadas · `eidos-lint` 4 morfo-backed / **0 invalid, 0 +class-hooks**. diff --git a/src/uix/eidos/components/s-text-virtual-list/README.md b/src/uix/eidos/components/s-text-virtual-list/README.md index 798a42214..75a9f307e 100644 --- a/src/uix/eidos/components/s-text-virtual-list/README.md +++ b/src/uix/eidos/components/s-text-virtual-list/README.md @@ -45,6 +45,26 @@ layout; aquí la altura sale del canvas antes de montar). texto pintado usen exactamente la misma fuente, más `overscan` y el vocabulario visual de SText. +## Talla y tema + +Nueve claves públicas en `lib/recipes/base.ts`: `line-height`, `fg`, `bg`, +`border-width`, `border-fg`, `radius` y las tres de la fila +(`row-padding-inline`, `row-border-width`, `row-border-fg`). El guard R-5.4 las +alcanza **todas** (9/9, 2026-08-23). + +**El triple tipográfico NO se acuña, y no es deuda.** `font-family`, `font-size` +y `font-weight` los escribe el componente INLINE en cada render, desde las props +`family` / `size` / `weight` resueltas por `eidos.resolve` (que siempre produce +un valor). Un `--s-text-virtual-list-font-*` quedaría pisado por ese inline y +mentiría — es la clase de `--gp-current-gradient` y del canal de rect del +indicador de `navigation-menu`. El mando aquí ES la prop, y el tema llega por +donde viven esos tokens: la capa tipográfica. Además el triple tiene que ser +EXACTO: el canvas que mide el texto lee `getComputedStyle` del contenedor, así +que la fuente pintada y la medida han de ser la misma. + +Los dos `100%` de la receta son identidad (la lista ES el ancho de su +contenedor; el espaciador ES el del scroller), firmados con su anotación. + ## Comparativa | Capacidad | UIX | TanStack Virtual (dynamic) | react-window (VariableSize) | diff --git a/src/uix/eidos/components/s-text-virtual-list/s-text-virtual-list.css b/src/uix/eidos/components/s-text-virtual-list/s-text-virtual-list.css index 9f5b3a836..aa35534cf 100644 --- a/src/uix/eidos/components/s-text-virtual-list/s-text-virtual-list.css +++ b/src/uix/eidos/components/s-text-virtual-list/s-text-virtual-list.css @@ -8,10 +8,18 @@ * `--_s-tvl-font-weight` inline from resolved tokens; the recipe * applies them so the canvas (which reads `getComputedStyle`) and the * rendered text use the exact same font triple. + * + * That triple is a VALUE CHANNEL, not a theming surface: `s-text-virtual-list + * .svelte` writes it inline on EVERY render (`eidos.resolve(size, 'md')` and + * friends always produce a value), so a `--s-text-virtual-list-font-*` public + * would be overwritten and would lie. The knob is the PROP — `size`, `family`, + * `weight` — and the theme reaches it where those tokens live, in the + * typographic layer. The fallbacks below only cover the render before the + * inline style lands. */ [data-s-text-virtual-list] { - inline-size: 100%; + inline-size: 100%; /* literal: identity — the list IS its container's width */ font-family: var(--_s-tvl-font-family, var(--font-family-primary)); font-size: var(--_s-tvl-font-size, var(--font-size-md)); font-weight: var(--_s-tvl-font-weight, var(--font-weight-regular)); @@ -30,7 +38,7 @@ [data-s-text-virtual-list-spacer] { position: relative; - inline-size: 100%; + inline-size: 100%; /* literal: identity — the spacer IS the scroller's width */ } [data-s-text-virtual-list-row] { diff --git a/web/routes/uix/components/s-text-virtual-list/+page.svelte b/web/routes/uix/components/s-text-virtual-list/+page.svelte index 56024fd43..8c4b2cf3f 100644 --- a/web/routes/uix/components/s-text-virtual-list/+page.svelte +++ b/web/routes/uix/components/s-text-virtual-list/+page.svelte @@ -2,8 +2,9 @@ import { STextVirtualList } from '$uix/eidos/components/s-text-virtual-list'; import { compileMorfo } from '$uix/morfo'; import { sTextVirtualListMorfo } from '@/uix/morfo/components/s-text-virtual-list'; + import TokensPanel from '../../lib/TokensPanel.svelte'; - type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; + type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y'; let tab = $state('live'); // ── Live state ──────────────────────────────────────────────────────── @@ -111,6 +112,9 @@ + @@ -246,6 +250,10 @@ {/if} + {#if tab === 'tokens'} + + {/if} + {#if tab === 'recipe'}

Eidos recipe