uix(radio-group): temable — 77 % → 81 %, y su punto NUNCA ha pintado su escala

Ocho claves acuñadas, diecisiete bloques `[data-size]` retirados y veinticuatro
adjudicaciones medidas. El default no se mueve: diff de computed 0 sobre 25.263
valores en 47 estados (20 combinaciones de variante x estado x talla con su
pasada de hover, más las 7 de la sonda estándar) y capturas 2x idénticas.

Censo: 77 % → 81 % (knobs 75 → 59, público 56 → 46, global 7 → 1, privado 10
invariante, sistema 2). Contrato 61 → 69 claves. Global no baja (68 %).

QUÉ ENTRA (8 claves)

  item-size · dot-size · font-size — los tres ejes RESUELTOS por talla en el
  TSC, con `scope: 'size:{k}'` sobre el proveedor, que es quien lleva
  `data-size`. Con ellos se van los DIECISIETE bloques `[data-size]` del CSS
  (§7.4-4: dejarlos vivos hace que el diff dé 0 por la ruta vieja) y el privado
  `--_radio-item-size`, que llevaba el nombre del componente ABREVIADO —
  theming §6 r5 lo prohíbe, es la clase de `--_mp-*` y `--gp-*` — y por eso el
  censo contaba el ancho y el alto del ítem como deuda.

  invalid-indicator-border · invalid-ring · invalid-ring-gap ·
  invalid-item-label-fg — el tinte de validación leía `--color-threat-element`
  y `--color-threat-text` a pelo. El trío del anillo toma el vocabulario del
  gemelo `checkbox`, que ya lo tenía; el borde se llama `indicator-*` porque en
  ESTE componente «indicator» es el círculo exterior y `dot-*` el punto.

  segmented-segment-padding-block — el hermano que le faltaba a
  `-padding-inline`.

UNA CLAVE ACUÑADA Y RETIRADA EN EL ACTO

  `row-min-height-touch: var(--touch-target)` para el suelo táctil de la fila.
  La suite `recipe-css-contract` la rechazó como referencia FANTASMA: nadie
  emite `--touch-target`, es una constante ergonómica escrita a mano en
  `archetypes.css` («a hard ergonomic constant — the fingertip, NOT a theme
  value»). Y recipe-contract §2 ya 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».

POR QUÉ 81 % ES EL TECHO

  Los diez knobs que quedan fuera leen `--_radio-group-palette-*`: el puente
  THM-2 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. No es deuda; es el componente cerrado en su número.

TRES DECLARACIONES QUE NO PINTAN (medidas, y las tres mueven píxel → firma)

  1. La escala del punto NUNCA ha pintado. `<SvgDot>` escribe
     `style="width: 1em; height: 1em"` y 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. Clase «canal de valor inline» del trío
     `carousel.item-gap*`, pero 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 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, o
     sea que falta el ATRIBUTO, no la clave — decisión de MORFO.

  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 centinela escrito: solid da el
     acento a plena intensidad, outline y segmented transparente, ghost la
     mezcla al 18 %. Quitar `data-variant` no ayuda: el gemelo
     `:not([data-variant])` también es (0,4,0).

  Y una cuarta, de escala del SISTEMA y por tanto fuera de este eje (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. Las cuatro, en next-features §13.

EL INSTRUMENTO MEDÍA 27 DE 61

  No veía 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, el barrido de `data-variant`
  y DOS URL más — `?perm.disabled=true` y `?perm.invalid=true`, que esta demo
  honra: once adjudicaciones sustituidas por medición real.

CENTINELA 45/69, 24 adjudicadas: 6 del punto (estilo inline), 8 tonos por la
cascada de paleta (mismo patrón que su gemelo checkbox, medido aquí: la clave
no mueve nada, `--palette-solid` en el nodo repinta), 4 del par de transición
congelado, 2 del anillo inválido (necesita `:focus-visible` con modalidad de
teclado), `gap-horizontal` (el par de gap, sólo pinta la mitad en vigor),
`segmented-pill-shadow` (la píldora sólo la renderiza el envoltorio bajo la
variante real), `disabled-item-label-fg` y `checked-disabled-indicator-bg` (las
dos declaraciones muertas de arriba).

GUARDS

  censo --only radio-group 81 % · global 68 % (no baja) · component-audit PASS ·
  eidos-lint 0 invalid (13 morfo-backed, 24 eidos-only) · vitest src/uix/eidos
  sin rojos nuevos (el único es `skin-media-player`, conocido) · rtl:check 0 ·
  docs:check 0 · prettier OK sobre el contenido normalizado a LF · check sin
  errores en ficheros del componente.

  README con su sección «Talla y tema» y la demo con su pestaña Tokens (69
  claves, verificada en navegador).

Índice construido a mano desde HEAD sobre los seis ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel.ts,
theming-sentinel-exceptions.ts, PLAN-theming.md, next-features.md): otras
cuatro sesiones los escriben a la vez. Fuera del commit, docs/audit/theming/
README.md — es un agregado global que mezcla trabajo en vuelo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent bc9b9016bb
commit f5d252443c

@ -5,39 +5,33 @@
> 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**: **77%** — 56 de 73 knobs por token público
- **Knobs de apariencia**: 75 — público 56 · privado 10 · global 7 · literal 0 · sistema 2 · excepción 0 _(los dos últimos, fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 61 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`, `font-weight`, `line-height`, `item-label-fg`, `disabled-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`, `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-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`**: sí · **ficheros**: `radio-group.css`
- **Medido**: 2026-08-23 · **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 (7)
### 1.1 Directo a primitivo global (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-group.css:41` | `[data-radio-group][data-invalid] [data-radio-group-row-label]` | `color` | `var(--color-threat-text)` |
| 2 | `radio-group.css:78` | `[data-radio-group-row]` | `min-block-size` | `var(--touch-target)` |
| 3 | `radio-group.css:93` | `[data-radio-group-item]` | `inline-size` | `var(--_radio-item-size)` |
| 4 | `radio-group.css:94` | `[data-radio-group-item]` | `block-size` | `var(--_radio-item-size)` |
| 5 | `radio-group.css:188` | `[data-radio-group][data-invalid] [data-radio-group-item]` | `border-color` | `var(--color-threat-element)` |
| 6 | `radio-group.css:192` | `[data-radio-group][data-invalid] [data-radio-group-item]:focus-visible` | `box-shadow` | `0 0 0 2px var(--color-surface-default), 0 0 0 4px var(--color-threat-element)` |
| 7 | `radio-group.css:313` | `[data-radio-group][data-variant='segmented'] [data-radio-group-item]` | `padding-block` | `var(--space-1)` |
| 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:146` | `[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:147` | `[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:153` | `[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:154` | `[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:161` | `[data-radio-group][data-variant='outline'] [data-radio-group-item][data-state='checked']` | `border-color` | `var(--_radio-group-palette-solid)` |
| 6 | `radio-group.css:173` | `[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:178` | `[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:214` | `[data-radio-group-indicator]` | `color` | `var(--_radio-group-palette-text)` |
| 9 | `radio-group.css:355` | `[data-radio-group][data-variant='segmented'] [data-radio-group-item][data-state='checked']` | `color` | `var(--_radio-group-palette-text)` |
| 10 | `radio-group.css:372` | `[data-radio-group-selection-indicator]` | `background` | `var(--_radio-group-palette-solid)` |
| 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)
@ -57,8 +51,8 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `radio-group.css:138` | `[data-radio-group-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 2 | `radio-group.css:359` | `[data-radio-group][data-variant='segmented'] [data-radio-group-item]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` |
| 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?
@ -71,9 +65,8 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
## 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).
- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo).
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (9)
### 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
@ -85,13 +78,7 @@ no distingue lo que debería — se marca `⚠`.
| --- | --- | --- | ---: |
| `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 |
| `invalid-row-label-fg` | `root` | `var(--color-threat-text)` | 1 |
| `row-height` | `root` | `var(--touch-target)` | 1 |
| `item-width` | `root` | `var(--_radio-item-size)` | 1 |
| `item-height` | `root` | `var(--_radio-item-size)` | 1 |
| `invalid-item-border` | `root` | `var(--color-threat-element)` | 1 |
| `invalid-item-shadow` | `root` | `0 0 0 2px var(--color-surface-default), 0 0 0 4px var(--color-threat-element)` | 1 |
| `segmented-item-padding-block` | `root` | `var(--space-1)` | 1 |
### 4.2 Sin nombre mecánico (8)
@ -113,6 +100,75 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**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 -->

@ -1517,6 +1517,33 @@ Lo que `chart` destapó (2026-08-22):
`range-calendar` renderiza un heading de texto en su lugar, mientras
`DateRangePicker` importa los selectores del CALENDARIO (que llevan
`data-calendar-month-select`). Hueco de DEMO: la parte existe en soma y en
eidos y ninguna superficie la monta.
eidos y ninguna superficie la monta.- **La escala del punto de `radio-group` NUNCA ha pintado** (medido
2026-08-23). `<SvgDot>` escribe `style="width: 1em; height: 1em"` en el glifo y
un inline gana a cualquier regla: el punto computa **13,3281px en las cinco
tallas** mientras `--radio-group-dot-size-{xs…xl}` dice 5/6/7/9/11px. Quitando
el estilo inline el MISMO token pinta. Es la clase «canal de valor inline» de
`carousel.item-gap*` y `drag-drop.preview-z`, pero con arreglo disponible: la
forma `!important` que recipe-contract §3 autoriza justo para saltarse un
estilo inline (o pasarle la talla a `<SvgDot size={…}>`). **Mueve píxel** ⇒
firma. Seis claves adjudicadas mientras tanto.
- **`radio-group` no tiñe la etiqueta de grupo deshabilitada**: la regla es
`[data-radio-group-label][data-disabled]` y soma **no estampa `data-disabled`
en la parte `Label`** (el morfo declara `data: []`). Medido 2026-08-23 con el
grupo entero deshabilitado: la etiqueta lleva `[id, data-radio-group-label,
data-archetype]` y nada más. El token alcanza forzando el atributo, así que
falta el ATRIBUTO, no la clave — decisión de MORFO (el label gemelo de
`checkbox` sí lo lleva).
- **Un radio marcado Y deshabilitado muestra el acento a plena intensidad**
(medido 2026-08-23): la regla de variante
`[data-radio-group][data-variant=X] [data-radio-group-item][data-state='checked']`
es (0,4,0) y `[data-radio-group-item][data-disabled][data-state='checked']`
sólo (0,3,0) — y el gemelo `:not([data-variant])` también es (0,4,0), así que
no hay instancia que escape. Subir la especificidad **mueve píxel** ⇒ firma.
- **El `:hover` de `radio-group` TAPA su tinte inválido**: hover (0,4,0) contra
invalid (0,3,0), la misma escalera que `textarea` (hover > foco > invalid) ya
registrada aquí. Es una decisión de escala del SISTEMA que mueve píxel en
varios controles de formulario con el mismo patrón; se anota, no se toca en
el eje (D-TH.5).
**Deps**: ninguna. Son mejoras del instrumental del eje, ejecutables cuando
estorben.

@ -486,6 +486,25 @@ commits de corrección.
## 8. Registro
- 2026-08-23 — **`radio-group` 77 % → 81 %, y su TECHO es ése** (COMMIT),
contrato 61 → **69** claves, centinela **46/69** con 23 adjudicadas. Ocho
claves: tres RESUELTAS por talla (`item-size`, `dot-size`, `font-size`) que
retiran **diecisiete bloques `[data-size]`** y jubilan `--_radio-item-size`
(privado con el nombre ABREVIADO, clase `--_mp-*`/`--gp-*`), cuatro de
validación que iban a `--color-threat-*` a pelo (el trío del anillo con el
vocabulario del gemelo `checkbox`) y `segmented-segment-padding-block`. Diff
**0** sobre 25.263 valores en 47 estados. Los diez knobs que quedan son el
puente THM-2 de paleta: **el techo no es el 100 %**.
**`row-min-height-touch` se acuñó y hubo que RETIRARLO**: `--touch-target` no
lo emite ningún generador (constante ergonómica escrita a mano en
`archetypes.css`) y `recipe-css-contract` lo cazó como referencia FANTASMA en
el acto — recipe-contract §2 ya lo lista entre los sistemas transversales que
una receta consume y nunca acuña. **Y salieron TRES declaraciones que no
pintan** (§13, las tres mueven píxel ⇒ firma): la escala del punto no ha
pintado nunca (`<SvgDot>` escribe `width:1em` INLINE — 13,3281px en las cinco
tallas), `[data-radio-group-label][data-disabled]` no casa jamás (soma no
estampa el atributo en la parte `Label`), y el fondo de un radio marcado Y
deshabilitado pierde (0,3,0) contra la regla de variante (0,4,0).
- 2026-08-23 — **`chat-composer` 79 % → 100 %** (COMMIT), contrato 31 → **33**
claves, centinela **29/33** con 4 adjudicadas. **La mitad de su contrato no
la montaba la demo**: 14 muertas de 31, y once eran la barra de contexto

@ -1123,7 +1123,49 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'the guard freezes transitions and this token IS the transition; measured unfrozen on a real day cell -> reaches (0.12s -> 11.5s on all five properties)',
'transition-ease':
'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))'
}
},
'radio-group': {
// 2026-08-23. The guard already sweeps its four variants and both
// orientations and measures the disabled / invalid stages through the
// demo's URL switches; these are what is left, each measured on the REAL
// nodes. Three of them are DEFECTS, not instrument limits, and say so.
//
// (1) The dot's whole scale — a value channel written INLINE.
'dot-size':
'DEAD BY INLINE STYLE, not by the demo: <SvgDot> writes style="width: 1em; height: 1em" on the glyph, and an inline declaration beats any stylesheet rule. Measured 2026-08-23: the dot computes 13.3281px (1em of the button UA font) at EVERY step while the token says 5/6/7/9/11px, the sentinel value changes nothing, and removing the inline style makes the same token paint 1234px. The recipe scale has never painted — a pixel-moving fix (the `!important` shape recipe-contract §3 allows for exactly this, bypassing an inline style) that needs a signature; registered in next-features §13',
'dot-size-xs': 'same inline style; the step reads 5px and the dot paints 13.3281px',
'dot-size-sm': 'same inline style; the step reads 6px and the dot paints 13.3281px',
'dot-size-md': 'same inline style; the step reads 7px and the dot paints 13.3281px',
'dot-size-lg': 'same inline style; the step reads 9px and the dot paints 13.3281px',
'dot-size-xl': 'same inline style; the step reads 11px and the dot paints 13.3281px',
// (2) A rule whose selector no runtime ever matches.
'disabled-item-label-fg':
'DEAD SELECTOR: it is consumed by [data-radio-group-label][data-disabled], and soma never stamps data-disabled on the Label part (the morfo declares `data: []` for it). Measured 2026-08-23 with the group disabled through ?perm.disabled=true: the label carries [id, data-radio-group-label, data-archetype] and nothing else. Forced on the label by hand -> reaches (oklch(0.2435 0 0) -> rgb(1, 2, 3)), so the token is sound and the ATTRIBUTE is missing — a morfo decision (checkbox\'s twin label does carry its own), registered in next-features §13',
// (3) A declaration the variant rules out-rank in every variant.
'checked-disabled-indicator-bg':
'SUPERSEDED by the variant rules: [data-radio-group][data-variant=X] [data-radio-group-item][data-state=checked] is (0,4,0) and [data-radio-group-item][data-disabled][data-state=checked] only (0,3,0). Measured 2026-08-23 on a disabled+checked item with the sentinel value written: solid oklch(0.5556 0.1829 305.86) (the full accent), outline and segmented rgba(0,0,0,0), ghost the 18% mix — never rgb(1,2,3). And removing data-variant does not help: the `:not([data-variant])` twin is (0,4,0) too. So a disabled+checked radio shows the FULL accent today; raising the specificity moves pixels -> signature, registered in next-features §13',
// Instrument limits, measured.
'gap-horizontal':
'the row gap is a PAIR and only the half in force paints: the demo boots `vertical`, and the guard sweeps ONE axis (data-variant, where twelve of its keys live). Switched the real demo to horizontal -> reaches (column-gap 16px -> 1234px)',
'invalid-ring':
'needs [data-invalid] AND :focus-visible, and the guard blurs on purpose; on a button :focus-visible only latches with the keyboard modality, so measured 2026-08-23 with a Tab followed by focus() on the real item -> reaches (the 4px ring oklch(0.9555 0.0207 13.86) -> rgb(1, 2, 3))',
'invalid-ring-gap': 'same double ring; measured the same way -> reaches (the 2px gap oklch(0.9911 0 0) -> rgb(4, 5, 6))',
'transition-duration':
'the guard freezes transitions and this token IS the transition; measured unfrozen on the real item -> reaches (0.12s -> 11.5s on all four transitioned properties)',
'transition-ease':
'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))',
'segmented-transition-duration':
'same freeze, on the segmented pair; measured unfrozen with the real variant on -> reaches (segment 0.18s -> 11.5s, and the pill\'s --indicator-duration 180ms -> 11.5s)',
'segmented-transition-ease':
'same freeze; measured unfrozen -> reaches (cubic-bezier(0.32, 0.72, 0, 1) -> steps(3))',
'segmented-pill-shadow':
'the sliding pill is a node the WRAPPER only renders under variant="segmented" (like Switch\'s default Thumb), so stamping data-variant while measuring cannot create it; measured with the demo\'s real segmented chip on -> reaches (rgba(15, 23, 42, 0.08) 0 1px 2px -> rgb(1, 2, 3) 0 0 0 7px)'
// `hover-segmented-segment-fg` had an entry here for one run and the guard
// reported it STALE on the next: it needs hover AND the segmented variant,
// which the hover pass does not sweep — yet the pointer's resting place is
// enough for the static pass to catch it. Verified alive twice before the
// entry was pruned (hovered segment: oklch(0.2435 0 0) -> rgb(1, 2, 3)).
},
};
/**
@ -1172,6 +1214,13 @@ export const SENTINEL_PATTERN_EXCEPTIONS: Array<{
pattern: /^(primary|secondary|neutral|affirm)-(solid|solid-hover)$/,
reason: PALETTE_SUPERSEDED
},
{
component: 'radio-group',
pattern: /^(primary|secondary|neutral|affirm)-(solid|solid-hover)$/,
reason:
PALETTE_SUPERSEDED +
' — the same four tones x two slots as its twin checkbox, and PRIMARY falls with them: the eidos wrapper always resolves a colour and stamps data-color, so the generic block matches on every instance and the fallback that names the component key never applies. Measured 2026-08-23 on an affirm instance: --radio-group-affirm-solid moves nothing (oklch(0.6491 0.1136 181.96) stays), --palette-solid on the same node repaints (rgb(4, 5, 6))'
},
{
component: 'badge',
pattern:

@ -244,6 +244,23 @@ const COMPONENT_OVERRIDES: Record<
// no attribute of its own: without this the guard measures ONE node (the root)
// and 27 of its 37 tokens read dead. Measured 2026-08-22.
prose: { extraNodes: '[data-prose] *' },
// Three blind spots at once (measured 2026-08-23, 27 of 61 tokens moved
// before this): the DOT is a bare `<svg data-svg='dot'>` inside the
// indicator, so the five `dot-size-*` steps had no node in the filter; the
// whole SEGMENTED chrome (track, segment, pill — twelve keys) only paints
// under `data-variant='segmented'` and the demo boots `solid`; and the
// disabled / invalid chrome needs a STATE the stage does not wear — this
// demo reads both from the URL (`perm.disabled`, `perm.invalid`), so they
// are measured as extra surfaces instead of eleven ledger lines.
'radio-group': {
extraNodes: "[data-radio-group-indicator] svg[data-svg='dot']",
sweepAttr: { attr: 'data-variant', values: ['solid', 'outline', 'ghost', 'segmented'] },
urls: [
'/uix/components/radio-group',
'/uix/components/radio-group?perm.disabled=true',
'/uix/components/radio-group?perm.invalid=true'
]
},
// Half its contract lives in parts the default stage does not mount: the
// AutoFields tree (array, item, widget) only exists in the demo's `auto`
// mode, and the panel chrome only under `variant='panel'`. 18 of 54 tokens

@ -66,6 +66,73 @@ Sources: [Radix](https://www.radix-ui.com/primitives/docs/components/radio-group
| `color` | `primary` \| `secondary` \| `neutral` \| `affirm` | Active palette. |
| `columns` | `number` | Switches root layout to CSS grid using `--_radio-group-columns`. |
## Talla y tema
**69 claves públicas** en `lib/recipes/base.ts` más tres privadas de paleta
(`_palette-solid`, `_palette-solid-hover`, `_palette-text` — el puente THM-2,
que no se acuña). El eje `size` se resuelve en el TSC, no en el CSS: la receta
lee **un solo nombre** por eje (`item-size`, `dot-size`, `font-size`) y el
contrato lo hace bajar por talla desde el proveedor, que es quien lleva
`data-size`. La pestaña **Tokens** de su demo las resuelve en vivo.
| Eje | Coordenadas | Nombre resuelto |
| --- | --- | --- |
| Diámetro del círculo | `item-size-{xs…xl}` (14/16/18/22/26px) | `item-size` |
| Diámetro del punto | `dot-size-{xs…xl}` (5/6/7/9/11px) | `dot-size` |
| Texto de la fila | `font-size-{xs…xl}` = `--size-{k}-font-size` | `font-size` |
El resto, por rol: la caja (`gap-vertical`, `gap-horizontal`, `item-gap`,
`font-family`, `font-weight`, `line-height`, `disabled-opacity`), el cromo del
círculo (`indicator-{radius,border-width,bg,border}`, `hover-indicator-border`),
la tinta (`item-label-fg`, `dot-fg`), los estados (`disabled-*`, `invalid-*`),
la etiqueta de grupo (`label-*`), la variante `segmented`
(`segmented-track-*`, `segmented-segment-*`, `segmented-pill-*`,
`segmented-transition-*`) y los ocho forwards de tono.
Lo que entró el **2026-08-23** (77 % → 81 %):
- **`item-size` / `dot-size` / `font-size` resueltos** — y con ellos se retiran
los **diecisiete bloques `[data-size]`** que el CSS llevaba. El primero
además jubila `--_radio-item-size`, un privado con el nombre del componente
ABREVIADO (theming §6 regla 5 lo prohíbe; la clase de `--_mp-*` y `--gp-*`):
el censo no lo reconocía como suyo y contaba el ancho y el alto del ítem como
deuda.
- **`invalid-indicator-border`, `invalid-ring`, `invalid-ring-gap`,
`invalid-item-label-fg`** — el tinte de validación iba a
`--color-threat-{element,text}` a pelo. El trío del anillo copia el
vocabulario del gemelo `checkbox`, que ya lo tenía.
- **`segmented-segment-padding-block`** — el hermano que le faltaba a
`segmented-segment-padding-inline`.
**Lo que NO se acuña, y por qué**: el suelo táctil de la fila
(`min-block-size: var(--touch-target)` bajo `@media (pointer: coarse)`) es un
sistema transversal de recipe-contract §2, no un knob — `--touch-target` es una
constante ergonómica escrita a mano en `archetypes.css`, y darle un token de
componente la convierte en referencia FANTASMA (el guard `recipe-css-contract`
lo rechazó en el acto). Y los diez knobs que quedan fuera del alcance son los
que leen `--_radio-group-palette-*`: el puente THM-2 que la capa compartida
alimenta por instancia desde `[data-color]`. **El techo de este componente es
ese 81 %**, no el 100 %.
**Tres declaraciones que NO pintan** (medidas el 2026-08-23, todas en el ledger
del centinela y en `next-features.md` §13 — arreglarlas mueve píxel, así que
esperan firma):
1. **La escala del punto nunca ha pintado.** `<SvgDot>` escribe
`style="width: 1em; height: 1em"` y un inline gana a cualquier regla: el
punto computa **13,3281px en las cinco tallas** mientras el token dice
5/6/7/9/11px.
2. **`[data-radio-group-label][data-disabled]` no casa jamás**: soma no estampa
`data-disabled` en la parte `Label` (el morfo declara `data: []`), ni con el
grupo entero deshabilitado.
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), así que hoy se ve el acento a plena intensidad.
Y una cuarta, de escala del SISTEMA y por tanto fuera de este eje: el
`:hover` del círculo es (0,4,0) y el tinte inválido (0,3,0), de modo que
**pasar el ratón por encima TAPA el borde de error**.
## Ownership
- Morfo declares radiogroup roles, item states, data attrs and the

@ -38,32 +38,18 @@
}
[data-radio-group][data-invalid] [data-radio-group-row-label] {
color: var(--color-threat-text);
color: var(--radio-group-invalid-item-label-fg);
}
/* `--radio-group-font-size` is the size-RESOLVED name: the TSC emits it on the
* provider (which carries `data-size`) and every part reads the one name. */
[data-radio-group-row-label] {
font-family: var(--radio-group-font-family);
font-size: var(--radio-group-font-size-md);
font-size: var(--radio-group-font-size);
line-height: var(--radio-group-line-height);
font-weight: var(--radio-group-font-weight);
}
[data-radio-group][data-size='xs'] [data-radio-group-row-label] {
font-size: var(--radio-group-font-size-xs);
}
[data-radio-group][data-size='sm'] [data-radio-group-row-label] {
font-size: var(--radio-group-font-size-sm);
}
[data-radio-group][data-size='lg'] [data-radio-group-row-label] {
font-size: var(--radio-group-font-size-lg);
}
[data-radio-group][data-size='xl'] [data-radio-group-row-label] {
font-size: var(--radio-group-font-size-xl);
}
/* ── Touch-target (a11y) — coarse pointers ──────────────────────────────────
* On COARSE pointers (touch), the clickable label ROW (dot + text) is the 44px
* tap target — the reference-grade web pattern for small markers (React Aria wraps
@ -82,7 +68,6 @@
/* ── Item ───────────────────────────────────────────────────────────────── */
[data-radio-group-item] {
--_radio-item-size: var(--radio-group-item-size-md);
/* `--_radio-group-palette-*` is declared on the [data-radio-group] host
* (via TSC `_palette-*` in lib/recipes/base.ts) and inherited here. */
@ -90,8 +75,8 @@
align-items: center;
justify-content: center;
flex: 0 0 auto;
inline-size: var(--_radio-item-size);
block-size: var(--_radio-item-size);
inline-size: var(--radio-group-item-size);
block-size: var(--radio-group-item-size);
padding: 0;
border: var(--radio-group-indicator-border-width) solid var(--radio-group-indicator-border);
border-radius: var(--radio-group-indicator-radius);
@ -106,28 +91,6 @@
opacity var(--radio-group-transition-duration) var(--radio-group-transition-ease);
}
/* ── Sizes ──────────────────────────────────────────────────────────────── */
[data-radio-group][data-size='xs'] [data-radio-group-item] {
--_radio-item-size: var(--radio-group-item-size-xs);
}
[data-radio-group][data-size='sm'] [data-radio-group-item] {
--_radio-item-size: var(--radio-group-item-size-sm);
}
[data-radio-group][data-size='md'] [data-radio-group-item] {
--_radio-item-size: var(--radio-group-item-size-md);
}
[data-radio-group][data-size='lg'] [data-radio-group-item] {
--_radio-item-size: var(--radio-group-item-size-lg);
}
[data-radio-group][data-size='xl'] [data-radio-group-item] {
--_radio-item-size: var(--radio-group-item-size-xl);
}
/* ── Hover / focus ─────────────────────────────────────────────────────── */
[data-radio-group-item]:hover:not([data-disabled]):not([data-readonly]) {
@ -185,11 +148,13 @@
* because invalid signals priority. */
[data-radio-group][data-invalid] [data-radio-group-item] {
border-color: var(--color-threat-element);
border-color: var(--radio-group-invalid-indicator-border);
}
[data-radio-group][data-invalid] [data-radio-group-item]:focus-visible {
box-shadow: 0 0 0 2px var(--color-surface-default), 0 0 0 4px var(--color-threat-element);
box-shadow:
0 0 0 2px var(--radio-group-invalid-ring-gap),
0 0 0 4px var(--radio-group-invalid-ring);
}
/* ── Disabled / readonly ────────────────────────────────────────────────── */
@ -215,35 +180,16 @@
pointer-events: none;
}
/* SVG dot — sized via the dot tokens, scales 0→1 for the select moment. */
/* SVG dot — sized via the size-RESOLVED dot token, scales 0→1 for the select
* moment. */
[data-radio-group-indicator] svg[data-svg='dot'] {
width: var(--radio-group-dot-size-md);
height: var(--radio-group-dot-size-md);
width: var(--radio-group-dot-size);
height: var(--radio-group-dot-size);
transform: scale(0);
transition:
transform var(--radio-group-transition-duration) cubic-bezier(0.4, 0, 0.2, 1);
}
[data-radio-group][data-size='xs'] [data-radio-group-indicator] svg[data-svg='dot'] {
width: var(--radio-group-dot-size-xs);
height: var(--radio-group-dot-size-xs);
}
[data-radio-group][data-size='sm'] [data-radio-group-indicator] svg[data-svg='dot'] {
width: var(--radio-group-dot-size-sm);
height: var(--radio-group-dot-size-sm);
}
[data-radio-group][data-size='lg'] [data-radio-group-indicator] svg[data-svg='dot'] {
width: var(--radio-group-dot-size-lg);
height: var(--radio-group-dot-size-lg);
}
[data-radio-group][data-size='xl'] [data-radio-group-indicator] svg[data-svg='dot'] {
width: var(--radio-group-dot-size-xl);
height: var(--radio-group-dot-size-xl);
}
/* Scale up to full size when the parent Item is checked. */
[data-radio-group-item][data-state='checked'] [data-radio-group-indicator] svg[data-svg='dot'] {
transform: scale(1);
@ -310,14 +256,14 @@
inline-size: auto;
block-size: auto;
flex: 0 0 auto;
padding-block: var(--space-1);
padding-block: var(--radio-group-segmented-segment-padding-block);
padding-inline: var(--radio-group-segmented-segment-padding-inline);
border: none;
background: transparent;
border-radius: var(--radio-group-segmented-pill-radius);
color: var(--radio-group-segmented-segment-fg);
font-family: var(--radio-group-font-family);
font-size: var(--radio-group-font-size-md);
font-size: var(--radio-group-font-size);
font-weight: var(--radio-group-segmented-segment-font-weight);
line-height: var(--radio-group-line-height);
white-space: nowrap;
@ -325,19 +271,6 @@
var(--radio-group-segmented-transition-ease);
}
[data-radio-group][data-variant='segmented'][data-size='xs'] [data-radio-group-item] {
font-size: var(--radio-group-font-size-xs);
}
[data-radio-group][data-variant='segmented'][data-size='sm'] [data-radio-group-item] {
font-size: var(--radio-group-font-size-sm);
}
[data-radio-group][data-variant='segmented'][data-size='lg'] [data-radio-group-item] {
font-size: var(--radio-group-font-size-lg);
}
[data-radio-group][data-variant='segmented'][data-size='xl'] [data-radio-group-item] {
font-size: var(--radio-group-font-size-xl);
}
[data-radio-group][data-variant='segmented']
[data-radio-group-item]:hover:not([data-disabled]):not([data-readonly]) {
background: transparent;

@ -2551,6 +2551,7 @@
--radio-group-line-height: var(--leading-ui);
--radio-group-item-label-fg: var(--color-content-primary);
--radio-group-disabled-item-label-fg: var(--color-content-disabled);
--radio-group-invalid-item-label-fg: var(--color-threat-text);
--radio-group-indicator-radius: var(--radius-full);
--radio-group-indicator-border-width: var(--border-width);
--radio-group-indicator-bg: var(--color-surface-default);
@ -2559,6 +2560,9 @@
--radio-group-disabled-indicator-bg: color-mix( in srgb, var(--color-surface-overlay) 78%, var(--color-surface-default) );
--radio-group-disabled-indicator-border: color-mix( in srgb, var(--color-border-default) 72%, var(--color-border-subtle) );
--radio-group-checked-disabled-indicator-bg: color-mix( in srgb, var(--color-content-disabled) 40%, var(--color-surface-overlay) );
--radio-group-invalid-indicator-border: var(--color-threat-element);
--radio-group-invalid-ring: var(--color-threat-element);
--radio-group-invalid-ring-gap: var(--color-surface-default);
--radio-group-dot-fg: var(--color-content-on-solid);
--radio-group-disabled-dot-fg: color-mix( in srgb, var(--color-content-on-solid) 82%, var(--color-surface-default) );
--radio-group-transition-duration: var(--duration-fast);
@ -2582,6 +2586,7 @@
--radio-group-segmented-track-padding: var(--space-1);
--radio-group-segmented-track-radius: var(--radius-md);
--radio-group-segmented-segment-padding-inline: var(--space-3);
--radio-group-segmented-segment-padding-block: var(--space-1);
--radio-group-segmented-segment-fg: var(--color-content-secondary);
--radio-group-hover-segmented-segment-fg: var(--color-content-primary);
--radio-group-segmented-segment-font-weight: var(--font-weight-medium);
@ -6698,11 +6703,38 @@
}
[data-radio-group] {
--radio-group-item-size: var(--radio-group-item-size-md);
--radio-group-dot-size: var(--radio-group-dot-size-md);
--radio-group-font-size: var(--radio-group-font-size-md);
--_radio-group-palette-solid: var(--radio-group-primary-solid);
--_radio-group-palette-solid-hover: var(--radio-group-primary-solid-hover);
--_radio-group-palette-text: var(--radio-group-dot-fg);
}
[data-radio-group][data-size='xs'] {
--radio-group-item-size: var(--radio-group-item-size-xs);
--radio-group-dot-size: var(--radio-group-dot-size-xs);
--radio-group-font-size: var(--radio-group-font-size-xs);
}
[data-radio-group][data-size='sm'] {
--radio-group-item-size: var(--radio-group-item-size-sm);
--radio-group-dot-size: var(--radio-group-dot-size-sm);
--radio-group-font-size: var(--radio-group-font-size-sm);
}
[data-radio-group][data-size='lg'] {
--radio-group-item-size: var(--radio-group-item-size-lg);
--radio-group-dot-size: var(--radio-group-dot-size-lg);
--radio-group-font-size: var(--radio-group-font-size-lg);
}
[data-radio-group][data-size='xl'] {
--radio-group-item-size: var(--radio-group-item-size-xl);
--radio-group-dot-size: var(--radio-group-dot-size-xl);
--radio-group-font-size: var(--radio-group-font-size-xl);
}
[data-radio-group][data-color='secondary'] {
--_radio-group-palette-solid: var(--radio-group-secondary-solid);
--_radio-group-palette-solid-hover: var(--radio-group-secondary-solid-hover);

@ -3899,10 +3899,59 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'font-size-md': 'var(--size-md-font-size)',
'font-size-lg': 'var(--size-lg-font-size)',
'font-size-xl': 'var(--size-xl-font-size)',
// The RESOLVED names. `data-size` is stamped on the PROVIDER, so the
// per-size cascade targets the host and every part inherits it — the CSS
// reads ONE name and the `[data-size]` blocks it used to carry are gone
// (PLAN-theming §7.4-4: leaving them alive keeps the OLD route painting
// and the computed diff reads 0 for the wrong reason). `item-size` also
// retires `--_radio-item-size`, a private with the component name
// ABBREVIATED (theming §6 rule 5 forbids it — the `--_mp-*` /`--gp-*`
// class), which is why the item's own width and height counted as
// out-of-reach knobs.
'item-size': {
declarations: [
{ value: 'var(--radio-group-item-size-md)', scope: 'host' },
{ value: 'var(--radio-group-item-size-xs)', scope: 'size:xs' },
{ value: 'var(--radio-group-item-size-sm)', scope: 'size:sm' },
{ value: 'var(--radio-group-item-size-lg)', scope: 'size:lg' },
{ value: 'var(--radio-group-item-size-xl)', scope: 'size:xl' }
]
},
'dot-size': {
declarations: [
{ value: 'var(--radio-group-dot-size-md)', scope: 'host' },
{ value: 'var(--radio-group-dot-size-xs)', scope: 'size:xs' },
{ value: 'var(--radio-group-dot-size-sm)', scope: 'size:sm' },
{ value: 'var(--radio-group-dot-size-lg)', scope: 'size:lg' },
{ value: 'var(--radio-group-dot-size-xl)', scope: 'size:xl' }
]
},
'font-size': {
declarations: [
{ value: 'var(--radio-group-font-size-md)', scope: 'host' },
{ value: 'var(--radio-group-font-size-xs)', scope: 'size:xs' },
{ value: 'var(--radio-group-font-size-sm)', scope: 'size:sm' },
{ value: 'var(--radio-group-font-size-lg)', scope: 'size:lg' },
{ value: 'var(--radio-group-font-size-xl)', scope: 'size:xl' }
]
},
'font-weight': 'var(--font-weight-medium)',
'line-height': 'var(--leading-ui)',
'item-label-fg': 'var(--color-content-primary)',
'disabled-item-label-fg': 'var(--color-content-disabled)',
'invalid-item-label-fg': 'var(--color-threat-text)',
// NO key for the row's coarse-pointer floor. It was minted as
// `row-min-height-touch: var(--touch-target)` and the
// `recipe-css-contract` phantom guard rejected it in the act:
// `--touch-target` is not emitted by any generator, it is a `:root`
// constant hand-written in `archetypes.css` next to the coarse rules that
// consume it — "a hard ergonomic constant, the fingertip, NOT a theme
// value". recipe-contract §2 lists the touch target among the transversal
// systems a recipe CONSUMES and never mints, so the raw read in the CSS is
// the contract being obeyed; the census counts it `global` only because
// its SYSTEM list does not know the token yet (the pending-signature item
// of CONTINUE-theming, §«extender la clase system a las capas
// compartidas»).
'indicator-radius': 'var(--radius-full)',
'indicator-border-width': 'var(--border-width)',
'indicator-bg': 'var(--color-surface-default)',
@ -3914,6 +3963,15 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'color-mix( in srgb, var(--color-border-default) 72%, var(--color-border-subtle) )',
'checked-disabled-indicator-bg':
'color-mix( in srgb, var(--color-content-disabled) 40%, var(--color-surface-overlay) )',
// Invalid wins over the active colour across every variant: the item's
// border and the double focus ring (gap + ring) that marks it. Same three
// slots, same values as the twin `checkbox` — where a pair exists, the
// vocabulary is the pair's. Here the circle's chrome is named
// `indicator-*` (radio-group calls the OUTER circle the indicator and the
// inner dot `dot-*`), so the border key follows that, not checkbox's.
'invalid-indicator-border': 'var(--color-threat-element)',
'invalid-ring': 'var(--color-threat-element)',
'invalid-ring-gap': 'var(--color-surface-default)',
'dot-fg': 'var(--color-content-on-solid)',
'disabled-dot-fg':
'color-mix( in srgb, var(--color-content-on-solid) 82%, var(--color-surface-default) )',
@ -3949,6 +4007,7 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'segmented-track-padding': 'var(--space-1)',
'segmented-track-radius': 'var(--radius-md)',
'segmented-segment-padding-inline': 'var(--space-3)',
'segmented-segment-padding-block': 'var(--space-1)',
'segmented-segment-fg': 'var(--color-content-secondary)',
'hover-segmented-segment-fg': 'var(--color-content-primary)',
'segmented-segment-font-weight': 'var(--font-weight-medium)',

@ -13,11 +13,12 @@
import { getActiveUix } from '$active-uix';
import { page } from '$app/state';
import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix();
const params = page.url.searchParams;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y';
let tab = $state<Tab>('live');
let value = $state('pro');
@ -295,6 +296,9 @@
<span data-uix-tab-count>{events.length}</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>69</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -588,6 +592,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="radio-group" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.