You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/audit/theming/radio-group.md

175 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# radio-group — alcance de tema: análisis y propuesta
> Generado por `node --import tsx/esm scripts/theming-census.ts --report`.
> Lo **medido** y la **propuesta** se regeneran; el **Veredicto** (§5) se conserva.
> 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**: **81%** — 46 de 57 knobs por token público
- **Knobs de apariencia**: 59 — público 46 · privado 10 · global 1 · literal 0 · sistema 2 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 69 pública(s) — `gap-vertical`, `gap-horizontal`, `item-size-xs`, `item-size-sm`, `item-size-md`, `item-size-lg`, `item-size-xl`, `dot-size-xs`, `dot-size-sm`, `dot-size-md`, `dot-size-lg`, `dot-size-xl`, `item-gap`, `font-family`, `font-size-xs`, `font-size-sm`, `font-size-md`, `font-size-lg`, `font-size-xl`, `item-size`, `dot-size`, `font-size`, `font-weight`, `line-height`, `item-label-fg`, `disabled-item-label-fg`, `invalid-item-label-fg`, `indicator-radius`, `indicator-border-width`, `indicator-bg`, `indicator-border`, `hover-indicator-border`, `disabled-indicator-bg`, `disabled-indicator-border`, `checked-disabled-indicator-bg`, `invalid-indicator-border`, `invalid-ring`, `invalid-ring-gap`, `dot-fg`, `disabled-dot-fg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `primary-solid`, `primary-solid-hover`, `neutral-solid`, `neutral-solid-hover`, `secondary-solid`, `secondary-solid-hover`, `affirm-solid`, `affirm-solid-hover`, `label-font-family`, `label-font-size`, `label-font-weight`, `label-line-height`, `label-fg`, `segmented-track-bg`, `segmented-track-shadow`, `segmented-track-padding`, `segmented-track-radius`, `segmented-segment-padding-inline`, `segmented-segment-padding-block`, `segmented-segment-fg`, `hover-segmented-segment-fg`, `segmented-segment-font-weight`, `segmented-pill-shadow`, `segmented-pill-radius`, `segmented-transition-duration`, `segmented-transition-ease` · 3 privada(s) forward — `_palette-solid`, `_palette-solid-hover`, `_palette-text`
- **Eje `size`**: no · **ficheros**: `radio-group.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-group.css:64` | `[data-radio-group-row]` | `min-block-size` | `var(--touch-target)` |
### 1.2 A través de un privado (10)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-group.css:109` | `[data-radio-group][data-variant='solid'] [data-radio-group-item][data-state='checked'], [data-radio-group]:not([data-variant]) [data-radio-group-item][data-state='checked']` | `background` | `var(--_radio-group-palette-solid)` |
| 2 | `radio-group.css:110` | `[data-radio-group][data-variant='solid'] [data-radio-group-item][data-state='checked'], [data-radio-group]:not([data-variant]) [data-radio-group-item][data-state='checked']` | `border-color` | `var(--_radio-group-palette-solid)` |
| 3 | `radio-group.css:116` | `[data-radio-group][data-variant='solid'] [data-radio-group-item][data-state='checked']:hover:not([data-disabled]):not([data-readonly]), [data-radio-group]:not([data-variant]) [data-radio-group-item][data-state='checked']:hover:not([data-disabled]):not([data-readonly])` | `background` | `var(--_radio-group-palette-solid-hover)` |
| 4 | `radio-group.css:117` | `[data-radio-group][data-variant='solid'] [data-radio-group-item][data-state='checked']:hover:not([data-disabled]):not([data-readonly]), [data-radio-group]:not([data-variant]) [data-radio-group-item][data-state='checked']:hover:not([data-disabled]):not([data-readonly])` | `border-color` | `var(--_radio-group-palette-solid-hover)` |
| 5 | `radio-group.css:124` | `[data-radio-group][data-variant='outline'] [data-radio-group-item][data-state='checked']` | `border-color` | `var(--_radio-group-palette-solid)` |
| 6 | `radio-group.css:136` | `[data-radio-group][data-variant='ghost'] [data-radio-group-item]:hover:not([data-disabled]):not([data-readonly])` | `background` | `color-mix(in srgb, var(--_radio-group-palette-solid) 8%, transparent)` |
| 7 | `radio-group.css:141` | `[data-radio-group][data-variant='ghost'] [data-radio-group-item][data-state='checked']` | `background` | `color-mix(in srgb, var(--_radio-group-palette-solid) 18%, transparent)` |
| 8 | `radio-group.css:179` | `[data-radio-group-indicator]` | `color` | `var(--_radio-group-palette-text)` |
| 9 | `radio-group.css:288` | `[data-radio-group][data-variant='segmented'] [data-radio-group-item][data-state='checked']` | `color` | `var(--_radio-group-palette-text)` |
| 10 | `radio-group.css:305` | `[data-radio-group-selection-indicator]` | `background` | `var(--_radio-group-palette-solid)` |
### 1.3 Literales (0)
_Ninguno._
### 1.4 Excepciones firmadas (0) — 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._
## 2. Sistema transversal (2) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-group.css:101` | `[data-radio-group-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `radio-group.css:292` | `[data-radio-group][data-variant='segmented'] [data-radio-group-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
## 3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_radio-group-palette-text` | 4 | `var(--radio-group-dot-fg)`, `var(--_radio-group-palette-solid)` | private, public | no |
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_radio-group-columns`, `--_radio-group-palette-solid`, `--_radio-group-palette-solid-hover`.
## 4. Propuesta de corrección
- **Consume la capa compartida `sliding-indicator`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña** `--radio-group-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (3)
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
al final) y theming §6.7 (slots de color, modificador delante). Un token con
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
no distingue lo que debería — se marca `⚠`.
| token (`--radio-group-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `indicator-fg` | `root` | ⚠ `var(--radio-group-dot-fg)` / `var(--_radio-group-palette-solid)` | 4 |
| `segmented-on-item-fg` | `root` | ⚠ `var(--radio-group-dot-fg)` / `var(--_radio-group-palette-solid)` | 4 |
| `row-height` | `root` | `var(--touch-target)` | 1 |
### 4.2 Sin nombre mecánico (8)
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 8: `background`, `border-color`.
### 4.3 Avisos sobre los tokens propuestos (1)
- **el privado `--_radio-group-palette-text` debe pasar a leer este público (o desaparecer)** — `--radio-group-indicator-fg`, `--radio-group-segmented-on-item-fg`
### 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.
- [ ] **Velo o acento en el nodo equivocado** (`archetype: 'item'` en un envoltorio, un `background` en shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba.
- [ ] **Doble animación** al mover un sello a una superficie con animación propia — registro de `animationstart`/`animationend`.
- [ ] **Diff de computed = 0** en reposo · hover · abierto · disabled · foco, por talla, antes y después.
- [ ] **Centinela por token nuevo**: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
## 5. Veredicto
<!-- veredicto:start -->
**Ejecutado 2026-08-23 · 77 % → 81 % · CERRADO en su número.**
Contrato 61 → **69 claves**. Censo: knobs 75 → 59, público 56 → 46, **global
7 → 1**, privado 10 (invariante), sistema 2. Diff de computed **0** sobre
21.423 valores en 40 estados (20 combinaciones de variante × estado × talla,
cada una con su pasada de hover) más 3.840 de la sonda estándar; capturas 2×
antes/después idénticas. Centinela **46/69**, 23 adjudicadas.
**Qué entra (8 claves).** Tres RESUELTAS por talla en el TSC —`item-size`,
`dot-size`, `font-size`— que retiran los **diecisiete bloques `[data-size]`**
del CSS (§7.4-4) y jubilan `--_radio-item-size`, un privado con el nombre del
componente ABREVIADO (theming §6 r5; la clase `--_mp-*` / `--gp-*`) que hacía
que el ancho y el alto del ítem contaran como deuda. Cuatro de validación
—`invalid-indicator-border`, `invalid-ring`, `invalid-ring-gap`,
`invalid-item-label-fg`— donde la receta leía `--color-threat-{element,text}` a
pelo; el trío del anillo copia el vocabulario del gemelo `checkbox`, que ya lo
tenía. Y `segmented-segment-padding-block`, el hermano que le faltaba a
`-padding-inline`.
**Por qué 81 % es el TECHO, no una deuda.** Los diez knobs restantes leen
`--_radio-group-palette-*`: el puente THM-2 (CONTINUE §3.pre) que la capa
compartida alimenta por instancia desde `[data-color]` — un público encima
dejaría que un tema lo fijara y matara el `color=` de cada instancia. El único
`global` que queda es `min-block-size: var(--touch-target)` de la fila bajo
`@media (pointer: coarse)`: **se acuñó y hubo que retirarlo**, porque
`--touch-target` no lo emite ningún generador (es una constante ergonómica
escrita a mano en `archetypes.css`, «the fingertip, NOT a theme value») y el
guard `recipe-css-contract` lo cazó como referencia FANTASMA en el acto.
recipe-contract §2 lista el touch-target entre los sistemas transversales que
una receta CONSUME y nunca acuña, así que la lectura cruda es el contrato
cumpliéndose; el censo la cuenta `global` sólo porque su lista `system` aún no
conoce el token (el pendiente de firma «extender la clase system a las capas
compartidas» del CONTINUE).
**Tres declaraciones que NO PINTAN, medidas** (en el ledger del centinela y en
`next-features.md` §13; arreglarlas mueve píxel ⇒ D-TH.5, esperan firma):
1. **La escala del punto nunca ha pintado.** `<SvgDot>` escribe
`style="width: 1em; height: 1em"` — un inline gana a cualquier regla. El
punto computa **13,3281px en las cinco tallas** mientras el token dice
5/6/7/9/11px, el valor centinela no lo mueve, y quitando el estilo inline el
MISMO token pinta 1234px. Es la clase «canal de valor inline» del trío
`carousel.item-gap*` y `drag-drop.preview-z`, pero aquí con arreglo posible:
la forma `!important` que recipe-contract §3 autoriza justo para saltarse un
estilo inline.
2. **`[data-radio-group-label][data-disabled]` no casa jamás.** Con el grupo
entero deshabilitado (`?perm.disabled=true`) la etiqueta lleva
`[id, data-radio-group-label, data-archetype]` y nada más: soma no estampa
`data-disabled` en la parte `Label` (el morfo declara `data: []`). Forzado a
mano el token alcanza, así que **la clave es sana y falta el ATRIBUTO** —
decisión de morfo (el label gemelo de `checkbox` sí lo lleva).
3. **El fondo de un radio marcado Y deshabilitado no llega.** La regla de
variante es (0,4,0) y `[data-radio-group-item][data-disabled][data-state='checked']`
sólo (0,3,0): medido con el valor centinela escrito, `solid` da el acento a
plena intensidad, `outline` y `segmented` transparente, `ghost` la mezcla al
18 % — nunca el centinela. Y quitar `data-variant` no ayuda: el gemelo
`:not([data-variant])` también es (0,4,0).
**Y una cuarta, de escala del SISTEMA, fuera de este eje por D-TH.5**: el
`:hover` del círculo es (0,4,0) y el tinte inválido (0,3,0), así que **pasar el
ratón por encima TAPA el borde de error**. Mueve píxel en cuatro componentes
con el mismo patrón: se anota, no se toca.
**El instrumento**, que medía 27 de 61: la sonda estándar no ve el punto (es un
`<svg data-svg='dot'>` sin atributo del componente), ni la variante `segmented`
(doce claves), ni los estados `disabled` / `invalid`. El guard lleva ahora
`extraNodes` para el punto, un barrido de DOS ejes (`data-variant` ×
`data-orientation` — el `gap` es un PAR y sólo pintaba la mitad) y las dos URL
con `?perm.disabled` / `?perm.invalid`, que la propia demo honra: once
adjudicaciones sustituidas por medición real.
<!-- veredicto:end -->

Powered by TurnKey Linux.