8 claves nuevas (60 → 68) y 8 literales ANOTADOS. Lo que falta es el PUENTE DE
PALETA (`_palette-text`, THM-2), que no es deuda. Global 63 %.
Field es el componente más COMPUESTO del sistema: lo que otras fichas llaman
«préstamo con dueño» se cobra aquí, así que su techo pesa más que el de nadie.
Acuñados el DISPARADOR DE CONTROL —el icono que abre el calendario, el reloj o
la muestra, y revela la contraseña: `size`, `radius`, `fg` y sus dos estados—,
el indicador `optional` (gemelo del `required`, que ya tenía token) y el lateral
de la etiqueta flotante, que es lo que CORTA el borde bajo el texto. La caja del
disparador se mide en `em` a propósito, así que el token guarda la RAZÓN
(`1.75em`) y no un píxel por talla.
OCHO LITERALES PASAN A EXCEPCIÓN FIRMADA POR IDENTIDAD, no para inflar el ratio:
cuatro `inline-size: 100%` (el campo, el control, el input y el control flotante
llenan su fila), dos `line-height: 1` (un glifo suelto no tiene interlínea) y
dos `1em` del icono dentro del disparador (el glifo ES el tamaño del texto).
LOS 20 ROJOS DEL GUARD ESTABAN VIVOS, uno por uno, y tres enseñan algo:
- `disabled-opacity` vive en el CONTROL, no en la raíz: el guard leía el nodo
equivocado.
- `control-placeholder-fg` no leía porque el input de la demo NO TIENE
`placeholder`: sin el atributo no existe la caja `::placeholder`, y
`getComputedStyle(nd, '::placeholder')` devuelve entonces el estilo del
ELEMENTO. Con un placeholder puesto, alcanza. Es genérico → §13.
- `segment-height` es un token de FAMILIA que field posee y los pickers
consumen —en las rutas de field no lo lee nadie— y además es RESUELTO
(declarado por `[data-field][data-size]`), así que escribirlo en `:root`
pierde POR DISEÑO: el tema mueve la coordenada. Medido en `/time-picker`:
desde `:root` nada, desde el host del campo 28px → 1234px, y moviendo
`--field-control-height-md` 28px → 992px.
El resto son estado (invalid, disabled, readonly, foco), variante ghost,
orientación horizontal, la franja de segmentos, el texto de error —que sólo se
RENDERIZA con el interruptor de la demo: forzar el atributo no hace que Svelte
monte el nodo— y los dos de transición, que el guard congela.
Instrumento: el disparador de control no lo monta NINGUNA demo de field, así que
el guard mide ahora `field` sobre DOS rutas (`/field` + `/date-picker`).
Gates: sonda 0 diffs en 2.280 valores × 8 estados · equivalencia del disparador
comprobada aparte en `/date-picker` (28px = 1.75em con fuente 16px · 4px =
`--radius-sm` · misma tinta) · centinela 48/68 con las 20 adjudicadas · censo
98 % · `--names` 0 · `component:audit` PASS · `rtl:check` 0 · `docs:check` 0 ·
suite eidos con el rojo conocido ajeno · `check` sin errores propios · capturas
de reposo, invalid, disabled, etiqueta flotante y disparador.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## 2. Sistema transversal (5) — informativo, fuera del ratio
Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2).
@ -85,7 +74,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
- **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
@ -95,15 +84,9 @@ no distingue lo que debería — se marca `⚠`.
'disabled-opacity':'it lives on the CONTROL, not on the field root, so the guard read the wrong node; forced data-disabled and read on the control -> reaches (0.4 -> 0.137)',
'control-ghost-border':'the ghost variant and the demo boots surface; forced data-variant=ghost -> reaches (transparent -> rgb(1,2,3))',
'layout-column-gap':'only in the horizontal orientation; forced data-orientation=horizontal -> reaches (16px -> 1234px)',
'focus-control-border':'only under :focus-within, and the guard blurs ON PURPOSE (the click-focus poisoning fix); focused from the keyboard -> reaches (the focus ring colour -> rgb(1,2,3))',
'focus-control-trigger-fg':'only under :focus-visible on the picker trigger, which the guard blurs; focused from the keyboard on /date-picker -> reaches (oklch(0.2435 0 0) -> rgb(1,2,3))',
'floating-label-top':'floatingLabel is a demo switch that boots OFF, and the label must also be floated (focused or filled); switch on + focus -> reaches (0px -> 1234px)',
'floating-label-padding-inline':'same switch; turned on -> reaches (4px -> 1234px)',
'error-fg':'the error text only RENDERS when the field is invalid, and it is the demo switch that mounts it - forcing the data-invalid attribute does not make Svelte render the node; switch on -> reaches (oklch(0.5855 0.1743 42.74) -> rgb(1,2,3))',
'control-placeholder-fg':'the ::placeholder box exists only when the input HAS a placeholder attribute, and the demo input has none: getComputedStyle then returns the ELEMENT style and the token reads dead. With a placeholder set -> reaches (oklch(0.61 0 0) -> rgb(1,2,3))',
'segment-active-bg':'the ACTIVE segment being edited: no segment on the field route and none focused; measured on /date-field with the segment clicked -> reaches (the 40% mix -> rgb(1,2,3))',
'segment-height':'a FAMILY token that field OWNS and the pickers CONSUME - nothing on the field routes reads it - and it is RESOLVED (declared per [data-field][data-size]), so a :root write loses BY DESIGN: a theme moves the coordinate. Measured on /time-picker: from :root nothing, from the field host 28px -> 1234px, and moving --field-control-height-md 28px -> 992px',
'transition-duration':'the guard freezes transitions to measure and this token IS the transition; measured unfrozen -> reaches (0.12s -> 11.5s)',