33 claves en una entrada NUEVA de `recipes/base.ts`. Censo 29 % → 75 %.
El knob ya tenía siete públicos «de facto» —consumidos como
`var(--knob-x, <default>)`— pero **ninguno declarado**, así que un tema no los
veía en `getRecipeTokens()`. Tokenizar ha sido sobre todo DECLARAR lo que ya se
consumía, y separar dos cosas que compartían nombre.
1. **Cuatro `--knob-*` quedan FUERA a propósito**: `progress`, `angle`,
`start-angle` y `sweep` los escribe el provider INLINE desde el estado del
control (`knob-provider.svelte.ts:358`). Canal de valor, no superficie de
tema \u2014 ningún contrato gana a un estilo inline. Misma clase que
`carousel.item-gap` y que la `z` del preview de drag-drop que retiró la
revisión adversarial.
2. **⚠ `--knob-arc-width` significaba DOS cosas con DOS defaults distintos**: el
inset de la cara del dial (`--space-3`, líneas 80 y 113) y la separación
superior del puntero (`--space-2`, línea 165). Un tema que lo escribiera
movía las tres declaraciones a la vez, pero **sin tema cada una valía algo
distinto** \u2014 «un nombre que no distingue lo que debería», que es la
definición de colisión que usa el propio generador. Separado en `arc-width`
(la cara) y `pointer-inset` (el puntero), cada uno con su valor verbatim:
cambia la superficie de tema, **no el píxel**.
3. **Las coordenadas por talla SUBEN al TSC, y no era opcional.** El default por
talla vivía en un privado y el público era un override que iba DELANTE
(`var(--knob-gap, var(--_knob-gap))`): declarar `--knob-gap` en el contrato
**habría matado la escala**, porque ganaría siempre. Con el patrón del resto
del eje (coordenadas + resuelto por `data-size`) el default sigue vivo y el
tema alcanza. Eje sm|md|lg|xl, sin xs; el diámetro es `control-height × 2` en
cada talla, proporción del bundle conservada verbatim.
`--_knob-value-digits` sigue privado: es el número de dígitos del valor máximo,
dato del consumidor.
Artefactos y gates:
- Sonda antes/después = 0 diffs en 754 valores × 7 estados. Determinismo
verificado.
- R-5.4: 25/33; los ocho mudos adjudicados con su medición (la demo no monta
etiqueta \u2014 0 nodos contados \u2014 ni estado de arrastre; forzados, los ocho
mueven).
- censo --only 75 % · component:audit PASS · eidos-lint invalid 0, class-hooks
0 · suite eidos 439/440 (el rojo conocido) · rtl:check 0 · docs:check 0 ·
check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (33 claves).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| `--_knob-label-font` | 4 | `var(--size-md-font-size)`, `var(--size-sm-font-size)`, `var(--size-lg-font-size)`, `var(--size-xl-font-size)` | global | no |
| `--_knob-value-font` | 4 | `var(--size-md-font-size)`, `var(--size-sm-font-size)`, `var(--size-lg-font-size)`, `var(--size-xl-font-size)` | global | no |
_La receta no declara privados propios en su CSS._
Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_knob-value-digits`.
## 4. Propuesta de corrección
- **Consume la capa compartida `spin-field`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña**`--knob-{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` (22)
### 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
@ -80,39 +63,15 @@ no distingue lo que debería — se marca `⚠`.
- **⚠ decisión: `1` 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** — 2: `line-height`.
- **⚠ 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** — 1: `inline-size`.
### 4.3 Avisos sobre los tokens propuestos (2)
- **el privado `--_knob-value-font` debe pasar a leer este público (o desaparecer)** — `--knob-value-text-font-size-md`, `--knob-value-text-font-size-sm`, `--knob-value-text-font-size-lg`, `--knob-value-text-font-size-xl`, `--knob-value-field-font-size-md`, `--knob-value-field-font-size-sm`, `--knob-value-field-font-size-lg`, `--knob-value-field-font-size-xl`
- **el privado `--_knob-label-font` debe pasar a leer este público (o desaparecer)** — `--knob-label-font-size-md`, `--knob-label-font-size-sm`, `--knob-label-font-size-lg`, `--knob-label-font-size-xl`
### 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.
@ -125,6 +84,33 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido 2026-08-22** (sonda ×2 = 0 diffs en 754 valores · 7 estados). El knob
ya tenía siete públicos «de facto» —consumidos con la forma
`var(--knob-x, <default>)`— pero **ninguno declarado en el contrato**, así que
un tema no los ve en `getRecipeTokens()` y el censo los cuenta bien por
casualidad. La tokenización consiste sobre todo en DECLARAR lo que ya se
consume, y en separar dos cosas que compartían nombre.
1. **Cuatro `--knob-*` NO son de tema: son canal de valor.** El provider los