Una clave acuñada, UNA RETIRADA con su declaración muerta, cuatro literales
firmados y cuarenta y cuatro adjudicaciones medidas. El default no se mueve:
diff de computed 0 sobre 11.448 valores en 17 estados y capturas 2x idénticas
byte a byte.
Censo: 76 % → 83 % (público 45, global 1 → 0, literal 4 → 0, excepción 0 → 4;
knobs 61 → 56). Contrato 98 claves (una entra, otra sale). Global no baja.
QUÉ ENTRA (1 clave)
invalid-dropzone-border (var(--color-risk-border)). Era el único knob de
apariencia que iba a un primitivo global a pelo.
Lleva la PARTE en el nombre y no el `invalid-border` desnudo que el catálogo
repite seis veces (checkbox, listbox, grid-list, radio-cards…): aquí hay TRES
partes con borde —dropzone, botón e ítem— y el nombre desnudo no diría cuál
tiñe. La forma modificador + parte + ranura ya está en el catálogo
(invalid-input-border, invalid-control-border).
Su estado no lo alcanza esta ruta: isInvalid sale de un Field envolvente y la
demo no monta ninguno. Forzado: oklch(0.8059 0.1123 59.96) -> rgb(1,2,3).
QUÉ SALE (1 clave, y su declaración)
button-bg NO PINTABA. La regla compartida de los tres botones declaraba
`background: var(--file-upload-button-bg)` y los tres la pisan después: el
trigger con el sólido de la paleta, ItemRemove y ClearTrigger con transparent.
Medido sobre los tres nodos, uno a uno: el centinela no mueve NADA en ninguno.
Con UNA clave la respuesta de F2-A es retirar, no adjudicar — y retirarla da
diff 0, que es la prueba de que estaba muerta. La clave sale con ella: un
público sin consumidor es un huérfano.
CUATRO LITERALES FIRMADOS (fuera del ratio)
Los tres 100% de identidad —el campo ocupa el ancho que le dan, la miniatura
llena la caja en la que se recorta, la barra ocupa la fila del grid— más el
block-size de la miniatura. Cada uno con su anotación en la declaración
(válvula de recipe-contract §3).
CUARENTA CLAVES DE TONO NO PINTAN
8 tonos x 5 ranuras (solid, solid-hover, track, border, text), 40 de sus 98.
Es la cascada de paleta: el forward emite AL FINAL un bloque genérico
[data-file-upload][data-color] que resuelve desde el --palette-* global, misma
especificidad (0,2,0), gana el último. Medido para los ocho tonos sobre las
dos superficies que las consumen —el trigger (solid) y el dropzone en hover
(track)—: ninguna se mueve, mientras --palette-solid sobre el nodo repinta.
Adjudicadas POR PATRÓN; incidencia de fondo en next-features §13.
FUERA POR DOCTRINA (los 9 privados)
Ocho son el puente de paleta THM-2 y el noveno es EL CANAL DE VALOR
(--_file-upload-progress-value, que el envoltorio escribe inline por fila).
Lo que el componente escribe inline no es tema, y un público sobre el puente
dejaría que un tema fijara el tono y matara el color= de cada instancia. Su
techo honesto NO es el 100 %.
VERIFICACIÓN
Sonda estándar antes/después 0 diffs (4.608 valores · 8 estados · 19 nodos: la
demo arranca con dos ficheros sembrados, así que monta la superficie entera) +
barrido de los estados que la ruta no arranca (3 variantes x 3 tallas, invalid,
disabled, dragging) 0 diffs sobre 6.840 valores · capturas 2x idénticas byte a
byte · R-5.4 54/98, exit 0, sin STALE · censo --only 83 % · component:audit
PASS · eidos-lint 29 morfo-backed / 0 invalid · vitest src/uix/eidos 35/36 (el
rojo conocido es skin-media-player) · rtl:check 0 · docs:check 0 · prettier
limpio · npm run check sin errores en ficheros del componente · pestaña Tokens
verificada en el navegador (98 filas, 0 errores de consola).
⚠ Truco de medición nuevo: el «antes» del barrido de estados se reconstruye
INYECTANDO el CSS de HEAD en la página (gana por orden a igual especificidad),
sin tocar un árbol que otras tres sesiones están usando. Con muta-prueba: 19
diffs al alterar el control, 1 si sólo se altera el borde inválido.
Índice construido a mano desde HEAD sobre los tres ficheros compartidos
(recipes/base.ts, generated/base.css, theming-sentinel-exceptions.ts). Fuera del
commit, como en el de `form`, docs/audit/theming/README.md — es un agregado
global que mezcla trabajo en vuelo de otras sesiones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## 3. Privados de la receta — ¿de dónde sale su valor?
@ -74,7 +72,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` (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
@ -84,7 +82,6 @@ no distingue lo que debería — se marca `⚠`.
'rim-glow-blur':'same look, same route -> reaches (blur(4px) -> blur(13px))',
'rim-glow-opacity':'same look, and the rim is under an ANIMATION: with only the transitions frozen the read returns the animated value (0.482956) and the token looks dead. With animation frozen too it computes 0.45 and follows -> reaches (0.45 -> 0.137)'
},
'file-upload':{
// Measured one by one 2026-08-23 (`__fu-adj`). The demo mounts the WHOLE
// and clear all render): what is left is state the route cannot reach and
// the freeze the guard does on purpose.
'transition-duration':
'the guard freezes transitions and this token IS the transition; measured unfrozen on the dropzone -> reaches (0.12s -> 11.5s on its three properties)',
'only under [data-disabled]; forced on the provider -> reaches (0.4 -> 0.123)',
'invalid-dropzone-border':
'only under [data-invalid], and this component takes invalid from an enclosing Field, which the demo route does not mount; forced -> reaches (oklch(0.8059 0.1123 59.96) -> rgb(1,2,3))'
},
switch:{
// Measured one by one 2026-08-23 (`__sw-adj`). The demo stage boots
// UNCHECKED, VALID and ENABLED and the switch has no trigger to open, so
' — the eight tones x five slots, 40 of its 97 keys. Measured 2026-08-23 on the trigger (solid) and on the hovered dropzone (track) for the eight tones: not one moves, while --palette-solid on the node repaints (oklch(0.6908 …) -> rgb(4,5,6)). Its host default is `primary`, and the demo passes color explicitly, so the fallback never applies either'