From 0c362cedd529381e0d56df5f4fa71a65b970ce4f Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 23 Aug 2026 04:48:21 +0200 Subject: [PATCH] =?UTF-8?q?uix(theming):=20cola=20peque=C3=B1a=20=E2=80=94?= =?UTF-8?q?=20seis=20componentes,=20y=20una=20receta=20que=20TAPABA=20sus?= =?UTF-8?q?=20claves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `section` (cuatro claves resucitadas), `sticky` 0 → 100 %, `button-group` 40 → 100 %, `qr-code` 43 → 100 %, `toggle-group` 33 → 50 % (su techo) y `split-button` adjudicado en 50 % (el suyo). Cero claves nuevas. Global 65 %, 25 componentes al 100 %, y los de alcance <20 % bajan de 14 a 12. EL HALLAZGO ESTÁ EN `section`: LA RECETA TAPABA SUS PROPIAS CLAVES PÚBLICAS. Declaraba los cuatro pasos de relleno en el host (`[data-box][data-section] { --section-padding-block-sm: … }`), y esa declaración gana a la que el contrato emite en `:root`: un tema que escribiera `--section-padding-block-lg` perdía contra la receta. Borrado el bloque —mismos valores, una sola fuente—, computado idéntico (48px antes y después), y las cuatro claves alcanzan. Lo demás es IDENTIDAD: once `100 %` entre las seis recetas y el centinela de 2px del sticky —el hilo invisible que el observador vigila—, todos con su razón escrita. Más un respaldo muerto retirado en `toggle-group`, sobre un token PRESTADO de `toggle`: el velo de un grupo deshabilitado ES el del toggle, y se tema en su ficha (§5.3-3). Y UNA CLASE DE ADJUDICACIÓN NUEVA, QUE SE REPETIRÁ: UN PASO POR INSTANCIA. `section` y `qr-code` mapean su prop de talla a `var(--{c}-{eje}-{k})` y la escriben INLINE, así que sólo el paso ACTIVO alcanza; los otros leen muertos hasta que la prop cambia. No es deuda ni ceguera del instrumento: es cómo funciona una escala resuelta por prop. Gates: los seis guards en verde · censo 65 % · `component:audit` PASS · suite eidos con el rojo conocido ajeno · `rtl:check` 0 · `docs:check` 0. Co-Authored-By: Claude Opus 5 --- docs/audit/theming/README.md | 16 ++++----- docs/audit/theming/button-group.md | 28 +++++++++------ docs/audit/theming/qr-code.md | 32 ++++++++++------- docs/audit/theming/section.md | 33 ++++++++++++----- docs/audit/theming/split-button.md | 5 ++- docs/audit/theming/sticky.md | 36 +++++++++---------- docs/audit/theming/toggle-group.md | 33 +++++++++++------ docs/process/PLAN-theming.md | 29 +++++++++++++++ scripts/theming-sentinel-exceptions.ts | 23 ++++++++++++ .../components/button-group/button-group.css | 6 ++-- src/uix/eidos/components/qr-code/qr-code.css | 8 ++--- src/uix/eidos/components/section/section.css | 11 +++--- src/uix/eidos/components/sticky/sticky.css | 4 +-- .../components/toggle-group/toggle-group.css | 6 ++-- 14 files changed, 181 insertions(+), 89 deletions(-) diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md index 44e5ee82b..1c980f7fa 100644 --- a/docs/audit/theming/README.md +++ b/docs/audit/theming/README.md @@ -10,9 +10,9 @@ - **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**: **65%** — 2992 de 4618 knobs pasan por un token público del componente -- **Reparto**: público 2992 · privado 320 · global 858 · literal 448 · sistema transversal 509 · excepción firmada 94 _(los dos últimos, fuera del ratio)_ -- **Sin token público propio**: 24 · **alcance < 20 %**: 14 · **alcance 100 %**: 22 · **con eje `size`**: 58 +- **Alcance global**: **65%** — 2993 de 4608 knobs pasan por un token público del componente +- **Reparto**: público 2993 · privado 319 · global 858 · literal 438 · sistema transversal 509 · excepción firmada 104 _(los dos últimos, fuera del ratio)_ +- **Sin token público propio**: 24 · **alcance < 20 %**: 12 · **alcance 100 %**: 25 · **con eje `size`**: 58 ## Cómo se lee @@ -113,11 +113,9 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [field-langs](./field-langs.md) | 0% | 39 | 0 | 0 | 31 | 6 | 2 | 0 | – | | [aspect-ratio](./aspect-ratio.md) | 0% | 3 | 0 | 0 | 1 | 2 | 0 | 0 | – | | [date-picker](./date-picker.md) | 0% | 2 | 0 | 0 | 0 | 2 | 0 | 0 | – | -| [sticky](./sticky.md) | 0% | 2 | 0 | 1 | 0 | 1 | 0 | 1 | – | | [text-blur](./text-blur.md) | 0% | 2 | 0 | 0 | 0 | 2 | 0 | 0 | – | | [cascade](./cascade.md) | 0% | 1 | 0 | 0 | 0 | 1 | 0 | 0 | – | | [motion](./motion.md) | 0% | 1 | 0 | 0 | 0 | 1 | 0 | 0 | – | -| [section](./section.md) | 0% | 1 | 0 | 0 | 0 | 1 | 0 | 4 | – | | [chronos](./chronos.md) | 1% | 244 | 3 | 21 | 202 | 11 | 7 | 9 | y | | [time-picker](./time-picker.md) | 18% | 59 | 10 | 12 | 24 | 11 | 2 | 16 | y | | [proof-of-human](./proof-of-human.md) | 19% | 79 | 14 | 17 | 29 | 15 | 4 | 20 | – | @@ -126,18 +124,16 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [palabras](./palabras.md) | 22% | 468 | 103 | 1 | 272 | 87 | 5 | 0 | – | | [surface](./surface.md) | 25% | 4 | 1 | 3 | 0 | 0 | 0 | 25 | – | | [scroll-frames](./scroll-frames.md) | 33% | 9 | 3 | 1 | 1 | 4 | 0 | 4 | – | -| [toggle-group](./toggle-group.md) | 33% | 3 | 1 | 0 | 1 | 1 | 0 | 1 | – | | [link](./link.md) | 36% | 14 | 4 | 4 | 1 | 2 | 3 | 5 | – | | [audio-player](./audio-player.md) | 37% | 19 | 7 | 0 | 8 | 4 | 0 | 5 | – | | [menu-dial](./menu-dial.md) | 38% | 8 | 3 | 0 | 1 | 4 | 0 | 3 | y | | [mockup](./mockup.md) | 38% | 29 | 11 | 0 | 13 | 5 | 0 | 0 | – | | [date-range-picker](./date-range-picker.md) | 40% | 53 | 20 | 9 | 19 | 2 | 3 | 22 | y | -| [button-group](./button-group.md) | 40% | 5 | 2 | 0 | 0 | 3 | 0 | 1 | – | -| [qr-code](./qr-code.md) | 43% | 7 | 3 | 0 | 0 | 4 | 0 | 10 | – | | [navigation-menu](./navigation-menu.md) | 44% | 38 | 15 | 1 | 13 | 5 | 4 | 26 | y | | [skeleton](./skeleton.md) | 45% | 11 | 5 | 5 | 0 | 1 | 0 | 9 | – | | [text-scramble](./text-scramble.md) | 50% | 4 | 2 | 0 | 0 | 2 | 0 | 0 | – | | [split-button](./split-button.md) | 50% | 2 | 1 | 0 | 1 | 0 | 0 | 1 | y | +| [toggle-group](./toggle-group.md) | 50% | 2 | 1 | 0 | 1 | 0 | 0 | 1 | – | | [waveform](./waveform.md) | 55% | 11 | 6 | 0 | 1 | 4 | 0 | 10 | y | | [sidebar](./sidebar.md) | 55% | 45 | 23 | 0 | 14 | 5 | 3 | 50 | – | | [color-field](./color-field.md) | 56% | 16 | 9 | 0 | 2 | 5 | 0 | 22 | y | @@ -255,8 +251,11 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [display](./display.md) | — | 6 | 0 | 0 | 0 | 0 | 6 | 0 | – | | [text-circular](./text-circular.md) | 100% | 6 | 6 | 0 | 0 | 0 | 0 | 6 | – | | [barcode](./barcode.md) | 100% | 5 | 5 | 0 | 0 | 0 | 0 | 7 | – | +| [qr-code](./qr-code.md) | 100% | 3 | 3 | 0 | 0 | 0 | 0 | 10 | – | +| [button-group](./button-group.md) | 100% | 2 | 2 | 0 | 0 | 0 | 0 | 1 | – | | [flex](./flex.md) | 100% | 2 | 2 | 0 | 0 | 0 | 0 | 7 | – | | [grid](./grid.md) | 100% | 2 | 2 | 0 | 0 | 0 | 0 | 13 | – | +| [sticky](./sticky.md) | 100% | 1 | 1 | 0 | 0 | 0 | 0 | 1 | – | | [alert-dialog](./alert-dialog.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – | | [auto-grid](./auto-grid.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – | | [container](./container.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 1 | – | @@ -267,6 +266,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [icon](./icon.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 2 | – | | [mask-field](./mask-field.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – | | [number-field](./number-field.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – | +| [section](./section.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 4 | – | | [stack](./stack.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – | | [wrap](./wrap.md) | — | 0 | 0 | 0 | 0 | 0 | 0 | 0 | – | diff --git a/docs/audit/theming/button-group.md b/docs/audit/theming/button-group.md index 53ebe96d4..30e43c310 100644 --- a/docs/audit/theming/button-group.md +++ b/docs/audit/theming/button-group.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**: **40%** — 2 de 5 knobs por token público -- **Knobs de apariencia**: 5 — público 2 · privado 0 · global 0 · literal 3 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ +- **Medido**: 2026-08-23 · **Alcance**: **100%** — 2 de 2 knobs por token público +- **Knobs de apariencia**: 2 — público 2 · privado 0 · global 0 · literal 0 · sistema 0 · excepción 3 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 1 pública(s) — `divider-strength` - **Eje `size`**: no · **ficheros**: `button-group.css` @@ -20,22 +20,22 @@ _Ninguno._ _Ninguno._ -### 1.3 Literales (3) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `button-group.css:31` | `[data-button-group][data-block]` | `inline-size` | `100%` | -| 2 | `button-group.css:47` | `[data-button-group][data-equal][data-orientation='vertical'] > [data-button]` | `inline-size` | `100%` | -| 3 | `button-group.css:51` | `[data-button-group][data-equal]:not([data-orientation='vertical'])` | `inline-size` | `100%` | +_Ninguno._ -### 1.4 Excepciones firmadas (0) — fuera del ratio +### 1.4 Excepciones firmadas (3) — 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 | `button-group.css:31` | `[data-button-group][data-block]` | `inline-size` | `100%` | +| 2 | `button-group.css:47` | `[data-button-group][data-equal][data-orientation='vertical'] > [data-button]` | `inline-size` | `100%` | +| 3 | `button-group.css:51` | `[data-button-group][data-equal]:not([data-orientation='vertical'])` | `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). @@ -77,6 +77,12 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 (cola pequeña) — 40 % → 100 %** sin acuñar nada: los tres +knobs fuera eran `inline-size: 100%` —la variante `block`, y las dos caras de +`equal`—, identidad pura, ahora con su razón escrita. + +`divider-strength` leía quieto porque **la costura sólo se dibuja entre botones +ATTACHED de una variante con relleno** (solid/soft/ghost/plain) y la demo monta +`surface`; forzado, alcanza (alfa 0,18 → 0,9). diff --git a/docs/audit/theming/qr-code.md b/docs/audit/theming/qr-code.md index 6f8257de6..c23bdfefd 100644 --- a/docs/audit/theming/qr-code.md +++ b/docs/audit/theming/qr-code.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**: **43%** — 3 de 7 knobs por token público -- **Knobs de apariencia**: 7 — público 3 · privado 0 · global 0 · literal 4 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ +- **Medido**: 2026-08-23 · **Alcance**: **100%** — 3 de 3 knobs por token público +- **Knobs de apariencia**: 3 — público 3 · privado 0 · global 0 · literal 0 · sistema 0 · excepción 4 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 10 pública(s) — `fg`, `bg`, `size-xs`, `size-sm`, `size-md`, `size-lg`, `size-xl`, `radius`, `overlay-bg`, `overlay-radius` - **Eje `size`**: no · **ficheros**: `qr-code.css` @@ -20,23 +20,23 @@ _Ninguno._ _Ninguno._ -### 1.3 Literales (4) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `qr-code.css:19` | `[data-qr-code-overlay]` | `width` | `100%` | -| 2 | `qr-code.css:20` | `[data-qr-code-overlay]` | `height` | `100%` | -| 3 | `qr-code.css:33` | `[data-qr-code-overlay] :where(img, svg)` | `width` | `100%` | -| 4 | `qr-code.css:34` | `[data-qr-code-overlay] :where(img, svg)` | `height` | `100%` | +_Ninguno._ -### 1.4 Excepciones firmadas (0) — fuera del ratio +### 1.4 Excepciones firmadas (4) — 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 | `qr-code.css:19` | `[data-qr-code-overlay]` | `width` | `100%` | +| 2 | `qr-code.css:20` | `[data-qr-code-overlay]` | `height` | `100%` | +| 3 | `qr-code.css:33` | `[data-qr-code-overlay] :where(img, svg)` | `width` | `100%` | +| 4 | `qr-code.css:34` | `[data-qr-code-overlay] :where(img, svg)` | `height` | `100%` | ## 2. Sistema transversal (0) — informativo, fuera del ratio Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). @@ -77,6 +77,14 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 (cola pequeña) — 43 % → 100 %** sin acuñar nada: los +cuatro knobs fuera eran `100 %` —la superposición y su imagen llenan el módulo—, +identidad con su razón escrita. + +Sus seis rojos del guard son de MEDIDA, no de contrato: `fg` y `bg` se pintan +como `fill` del `` y del `` del SVG —no como `color` y `background` +del nodo raíz—, y las cinco tallas son **un paso por instancia**: el wrapper +escribe `var(--qr-code-size-{k})` inline desde la prop y la demo corre `lg`. +Medidos en el nodo correcto y forzando el paso, todos alcanzan. diff --git a/docs/audit/theming/section.md b/docs/audit/theming/section.md index 98e63ff6c..b1488c952 100644 --- a/docs/audit/theming/section.md +++ b/docs/audit/theming/section.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**: **0%** — 0 de 1 knobs por token público -- **Knobs de apariencia**: 1 — público 0 · privado 0 · global 0 · literal 1 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ +- **Medido**: 2026-08-23 · **Alcance**: **—** — 0 de 0 knobs por token público +- **Knobs de apariencia**: 0 — público 0 · privado 0 · global 0 · literal 0 · sistema 0 · excepción 1 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 4 pública(s) — `padding-block-sm`, `padding-block-md`, `padding-block-lg`, `padding-block-xl` - **Eje `size`**: no · **ficheros**: `section.css` @@ -20,20 +20,20 @@ _Ninguno._ _Ninguno._ -### 1.3 Literales (1) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `section.css:19` | `[data-box][data-section]` | `inline-size` | `100%` | +_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 | `section.css:18` | `[data-box][data-section]` | `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). @@ -46,6 +46,8 @@ _La receta no declara privados propios en su CSS._ ## 4. Propuesta de corrección +- **Sin knobs de apariencia**: el visual vive en un componente compuesto o en una capa compartida. «Declarado y nunca pintado» no es deuda por sí solo (architecture/eidos.md, columna `unused`). + ### 4.1 Tokens a declarar en `lib/recipes/base.ts` (0) Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién @@ -73,6 +75,19 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 (cola pequeña) — sin knobs que medir, y CUATRO claves +resucitadas.** La receta declaraba sus propios pasos de relleno EN EL HOST +(`[data-box][data-section]`), y esa declaración **tapaba las cuatro públicas que +el contrato ya emite en `:root`**: un tema que escribiera `--section-padding-block-lg` +perdía contra la receta. Borrado el bloque —mismos valores, una sola fuente— y el +computado no se mueve (48px antes y después). + +El `inline-size: 100%` pasa a excepción por identidad: una sección ES el ancho de +su caja. + +Los tres pasos que el guard sigue viendo quietos son **un paso por instancia**: +el wrapper mapea la prop `padding` a `var(--section-padding-block-{k})` y lo +escribe INLINE, así que sólo el activo alcanza. Forzando el paso, alcanza +(96px → 123px). diff --git a/docs/audit/theming/split-button.md b/docs/audit/theming/split-button.md index ebac33434..923f00631 100644 --- a/docs/audit/theming/split-button.md +++ b/docs/audit/theming/split-button.md @@ -77,6 +77,9 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**ADJUDICADO 2026-08-23 (cola pequeña) — el 50 % es su techo.** Compone +ButtonGroup + Button + DropdownMenu y sólo posee el relleno del rabillo `▾`; el +knob que queda fuera lee `--dropdown-menu-content-min-width`, **un público +PRESTADO de su menú**, que se tema en la ficha del dueño. Nada que acuñar. diff --git a/docs/audit/theming/sticky.md b/docs/audit/theming/sticky.md index d5b79c203..71fcba130 100644 --- a/docs/audit/theming/sticky.md +++ b/docs/audit/theming/sticky.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**: **0%** — 0 de 2 knobs por token público -- **Knobs de apariencia**: 2 — público 0 · privado 1 · global 0 · literal 1 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ +- **Medido**: 2026-08-23 · **Alcance**: **100%** — 1 de 1 knobs por token público +- **Knobs de apariencia**: 1 — público 1 · privado 0 · global 0 · literal 0 · sistema 0 · excepción 1 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 1 pública(s) — `z-index` - **Eje `size`**: no · **ficheros**: `sticky.css` @@ -16,26 +16,24 @@ _Ninguno._ -### 1.2 A través de un privado (1) +### 1.2 A través de un privado (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `sticky.css:44` | `[data-sticky-sentinel]` | `block-size` | `var(--_sticky-sentinel-size)` | +_Ninguno._ -### 1.3 Literales (1) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `sticky.css:45` | `[data-sticky-sentinel]` | `inline-size` | `100%` | +_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 | `sticky.css:45` | `[data-sticky-sentinel]` | `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). @@ -46,13 +44,13 @@ _Ninguno._ | privado | declaraciones | valor(es) | origen | ¿deriva de un público? | | --- | ---: | --- | --- | :-: | -| `--_sticky-sentinel-size` | 1 | `2px` | literal | no | +| `--_sticky-sentinel-size` | 1 | `2px` | exception | no | Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_sticky-offset`. ## 4. Propuesta de corrección -### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1) +### 4.1 Tokens a declarar en `lib/recipes/base.ts` (0) 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 @@ -62,16 +60,11 @@ no distingue lo que debería — se marca `⚠`. | token (`--sticky-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | -| `sentinel-height` | `root` | `2px` | 1 | ### 4.2 Sin nombre mecánico (1) - **⚠ decisión: `100%` 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** — 1: `inline-size`. -### 4.3 Avisos sobre los tokens propuestos (1) - -- **el privado `--_sticky-sentinel-size` debe pasar a leer este público (o desaparecer)** — `--sticky-sentinel-height` - ### 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. @@ -84,6 +77,9 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 (cola pequeña) — 0 % → 100 %** sin acuñar nada: sus dos +knobs eran identidad. El centinela del sticky (`--_sticky-sentinel-size: 2px`) es +el hilo invisible que el observador vigila —ni se ve ni se tematiza— y el +`inline-size: 100%` es el ancho de su caja. Los dos con su razón escrita. diff --git a/docs/audit/theming/toggle-group.md b/docs/audit/theming/toggle-group.md index 6b49f506e..d7bfebd99 100644 --- a/docs/audit/theming/toggle-group.md +++ b/docs/audit/theming/toggle-group.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**: **33%** — 1 de 3 knobs por token público -- **Knobs de apariencia**: 3 — público 1 · privado 0 · global 1 · literal 1 · sistema 0 · excepción 0 _(los dos últimos, fuera del ratio)_ +- **Medido**: 2026-08-23 · **Alcance**: **50%** — 1 de 2 knobs por token público +- **Knobs de apariencia**: 2 — público 1 · privado 0 · global 1 · literal 0 · sistema 0 · excepción 1 _(los dos últimos, fuera del ratio)_ - **Contrato hoy** (`lib/recipes/base.ts`): 1 pública(s) — `gap` - **Eje `size`**: no · **ficheros**: `toggle-group.css` @@ -16,26 +16,26 @@ | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `toggle-group.css:101` | `[data-toggle-group][data-disabled]` | `opacity` | `var(--toggle-disabled-opacity, 0.55)` ⤴ prestado de `toggle` | +| 1 | `toggle-group.css:103` | `[data-toggle-group][data-disabled]` | `opacity` | `var(--toggle-disabled-opacity)` ⤴ prestado de `toggle` | ### 1.2 A través de un privado (0) _Ninguno._ -### 1.3 Literales (1) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `toggle-group.css:32` | `[data-toggle-group][data-block]` | `inline-size` | `100%` | +_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 | `toggle-group.css:32` | `[data-toggle-group][data-block]` | `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). @@ -60,7 +60,7 @@ no distingue lo que debería — se marca `⚠`. | token (`--toggle-group-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | -| `disabled-opacity` | `root` | `var(--toggle-disabled-opacity, 0.55)` | 1 | +| `disabled-opacity` | `root` | `var(--toggle-disabled-opacity)` | 1 | ### 4.2 Sin nombre mecánico (1) @@ -78,6 +78,17 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 (cola pequeña) — 33 % → 50 %**, que es su techo. Un +literal anotado (`inline-size: 100%` de la variante `block`) y un respaldo muerto +retirado. + +El knob que queda fuera es **un token PRESTADO de `toggle`** +(`--toggle-disabled-opacity`): el velo de un grupo de toggles deshabilitado ES el +del toggle, y se tema en SU ficha (recipe-contract §5.3-3). Llevaba además un +respaldo `0.55` que no puede ocurrir —la clave está en el contrato del dueño— y +que sólo servía para envejecer contra él. + +`gap` leía quieto porque el grupo de la demo es ATTACHED (segmentado), donde el +hueco colapsa a 0; sin `data-attached` alcanza (4px → 123px). diff --git a/docs/process/PLAN-theming.md b/docs/process/PLAN-theming.md index 16b656ed6..7f126f405 100644 --- a/docs/process/PLAN-theming.md +++ b/docs/process/PLAN-theming.md @@ -455,6 +455,35 @@ de commit. Sin artefacto, el paso no se ha hecho. ## 8. Registro +- 2026-08-23 — **COLA PEQUEÑA: seis componentes, cero claves nuevas.** `section` + (— · cuatro claves RESUCITADAS), `sticky` 0 → **100 %**, `button-group` 40 → + **100 %**, `qr-code` 43 → **100 %**, `toggle-group` 33 → **50 %** (su techo) y + `split-button` ADJUDICADO en 50 % (su techo). Global 65 %; **25 componentes al + 100 %** y los de alcance <20 % bajan de 14 a 12. + + **El hallazgo está en `section`: la receta TAPABA sus propias claves públicas.** + Declaraba los cuatro pasos de relleno en el host + (`[data-box][data-section] { --section-padding-block-sm: … }`), y esa + declaración gana a la que el contrato emite en `:root`: un tema que escribiera + `--section-padding-block-lg` perdía contra la receta. Borrado el bloque —mismos + valores, una sola fuente—, computado idéntico (48px antes y después), y las + cuatro claves alcanzan. + + Lo demás es **identidad**: once `100 %` entre las seis recetas y el centinela + de 2px del sticky —el hilo invisible que el observador vigila—, todos con su + razón escrita. Más un respaldo muerto retirado en `toggle-group`, sobre un + token PRESTADO de `toggle` (el velo de un grupo deshabilitado es el del + toggle, y se tema en su ficha). + + **Y una clase de adjudicación nueva, que se repetirá: UN PASO POR INSTANCIA.** + `section` y `qr-code` mapean su prop de talla a `var(--{c}-{eje}-{k})` y la + escriben INLINE, así que sólo el paso ACTIVO alcanza; los otros cuatro leen + muertos hasta que la prop cambia. No es deuda ni ceguera del instrumento: es + cómo funciona una escala resuelta por prop. + + Gates: seis guards en verde · censo · audit PASS · suite con el rojo conocido + ajeno · rtl 0 · docs 0. + - 2026-08-23 — **`tooltip` 52 % → 75 %** (3 claves, 4 literales anotados) **y el plano `overlay` ANULA SUS TRES VARIANTES**. Global 65 %. diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index f4a21a161..1deb32c57 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -632,6 +632,29 @@ export const SENTINEL_EXCEPTIONS: Record> = { 'outline-arrow-bg': 'the arrow is painted through a SIBLING selector that needs the content carrying data-variant=outline AND the panel open; measured with the demo switch -> reaches (oklch(0.9911 0 0) -> rgb(1,2,3))', 'outline-arrow-border': 'same sibling rule; measured -> reaches (oklch(0.7921 0 0) -> rgb(1,2,3))' }, + section: { + // ONE STEP PER INSTANCE: the wrapper maps the `padding` prop to + // var(--section-padding-block-{k}) and writes it INLINE, so only the active + // step is reachable at a time. Forcing another step reaches (2026-08-23). + 'padding-block-sm': 'one step per instance; the demo runs md. Forced the step inline -> reaches', + 'padding-block-lg': 'same, one step per instance', + 'padding-block-xl': 'same; forced -> reaches (96px -> 123px)' + }, + 'qr-code': { + 'size-xs': 'one step per instance: the wrapper writes var(--qr-code-size-{k}) inline from the size prop and the demo runs lg. Forced -> reaches (96px -> 123px)', + 'size-sm': 'same, one step per instance', + 'size-md': 'same, one step per instance', + 'size-xl': 'same, one step per instance', + 'fg': 'painted as the SVG fill of the module path, not as color on the root; read on the path -> reaches (rgb(24,24,27) -> rgb(1,2,3))', + 'bg': 'painted as the fill of the backing rect, not as background on the root; read on the rect -> reaches (white -> rgb(1,2,3))' + }, + 'toggle-group': { + gap: 'the demo group is ATTACHED (segmented), where the gap collapses to 0; removed data-attached -> reaches (4px -> 123px)' + }, + 'button-group': { + 'divider-strength': + 'the seam only draws between ATTACHED buttons of a filled variant (solid/soft/ghost/plain) and the demo runs surface; forced -> reaches (0.18 -> 0.9 alpha)' + }, button: { // Measured one by one 2026-08-23. The icon scale needs a real child, // the radius scale is an OPT-IN prop the demo does not set, and the four diff --git a/src/uix/eidos/components/button-group/button-group.css b/src/uix/eidos/components/button-group/button-group.css index d16c721b1..9b8c0fbe9 100644 --- a/src/uix/eidos/components/button-group/button-group.css +++ b/src/uix/eidos/components/button-group/button-group.css @@ -28,7 +28,7 @@ } [data-button-group][data-block] { - inline-size: 100%; + inline-size: 100%; /* literal: fills its box — identity, not a knob */ } /* `equal` — every button the same width. Pure CSS can only equalise on the @@ -44,11 +44,11 @@ } [data-button-group][data-equal][data-orientation='vertical'] > [data-button] { - inline-size: 100%; + inline-size: 100%; /* literal: fills its box — identity, not a knob */ } [data-button-group][data-equal]:not([data-orientation='vertical']) { - inline-size: 100%; + inline-size: 100%; /* literal: fills its box — identity, not a knob */ } [data-button-group][data-equal]:not([data-orientation='vertical']) > [data-button] { diff --git a/src/uix/eidos/components/qr-code/qr-code.css b/src/uix/eidos/components/qr-code/qr-code.css index cc074eba3..995179aac 100644 --- a/src/uix/eidos/components/qr-code/qr-code.css +++ b/src/uix/eidos/components/qr-code/qr-code.css @@ -16,8 +16,8 @@ * for HTML inside a viewBox-scaled foreignObject. */ [data-qr-code-overlay] { box-sizing: border-box; - width: 100%; - height: 100%; + width: 100%; /* literal: fills its box — identity, not a knob */ + height: 100%; /* literal: fills its box — identity, not a knob */ display: flex; align-items: center; justify-content: center; @@ -30,7 +30,7 @@ } [data-qr-code-overlay] :where(img, svg) { - width: 100%; - height: 100%; + width: 100%; /* literal: fills its box — identity, not a knob */ + height: 100%; /* literal: fills its box — identity, not a knob */ object-fit: contain; } diff --git a/src/uix/eidos/components/section/section.css b/src/uix/eidos/components/section/section.css index 8ef0ada75..f8f99bc56 100644 --- a/src/uix/eidos/components/section/section.css +++ b/src/uix/eidos/components/section/section.css @@ -11,10 +11,9 @@ */ [data-box][data-section] { - --section-padding-block-sm: var(--space-8); - --section-padding-block-md: var(--space-12); - --section-padding-block-lg: var(--space-16); - --section-padding-block-xl: calc(var(--space-16) * 1.5); - - inline-size: 100%; + /* The four steps live in the CONTRACT (recipes.section.padding-block-*) and are + emitted at :root. Re-declaring them here shadowed them: a theme writing the + public token lost against this block, so the four keys were dead (measured + 2026-08-23). Same values, one source. */ + inline-size: 100%; /* literal: fills its box — identity, not a knob */ } diff --git a/src/uix/eidos/components/sticky/sticky.css b/src/uix/eidos/components/sticky/sticky.css index e1f9b24f2..b6f609520 100644 --- a/src/uix/eidos/components/sticky/sticky.css +++ b/src/uix/eidos/components/sticky/sticky.css @@ -40,9 +40,9 @@ * box because both derive from the same soma offset. */ [data-sticky-sentinel] { - --_sticky-sentinel-size: 2px; + --_sticky-sentinel-size: 2px; /* literal: the invisible tripwire the observer watches — identity, not a knob */ block-size: var(--_sticky-sentinel-size); - inline-size: 100%; + inline-size: 100%; /* literal: fills its box — identity, not a knob */ pointer-events: none; visibility: hidden; } diff --git a/src/uix/eidos/components/toggle-group/toggle-group.css b/src/uix/eidos/components/toggle-group/toggle-group.css index 941e20816..72f920e5b 100644 --- a/src/uix/eidos/components/toggle-group/toggle-group.css +++ b/src/uix/eidos/components/toggle-group/toggle-group.css @@ -29,7 +29,7 @@ [data-toggle-group][data-block] { display: flex; - inline-size: 100%; + inline-size: 100%; /* literal: fills its box — identity, not a knob */ } [data-toggle-group][data-block] [data-toggle-group-item] { @@ -98,7 +98,9 @@ * onto each item, so Toggle's `[data-toggle][data-disabled]` rule paints * each one; this rule disables interaction at the container. */ [data-toggle-group][data-disabled] { - opacity: var(--toggle-disabled-opacity, 0.55); + /* Borrowed on purpose: the disabled veil of a group of toggles IS the + toggle’s, and it is themed in ITS sheet (recipe-contract 5.3-3). */ + opacity: var(--toggle-disabled-opacity); pointer-events: none; }