El mecanismo genérico ya existía (universalPaletteDecls auto-completaba las 33
escalas para recetas con tokens palette-*), pero con PREFIJO de componente
(--button-palette-*) — cada superficie pagaba ~21 KB, por eso solo button/toggle
lo tenían (rollout a las 17 = ~+315 KB). Colapso al modelo compartido:
- renderSharedPaletteLayer: UNA cascada component-agnóstica
[data-color='{role|scale}'] { --palette-{slot}: … } — 8 roles + 33 escalas,
emitida una vez (~19 KB). Roles 1:1 a --color-{role}-*; escalas con el step
map + on-solid contrast, valor idéntico a la cascada per-component retirada.
- renderRecipePaletteForward: toda receta con palette-* (público) o _palette-*
(privado) recibe [data-{c}][data-color] { --{c}-palette-{slot}:
var(--palette-{slot}, host-default) }. La guarda de PRESENCIA es la seguridad
de anidamiento — un hijo sin color dentro de un ancestro coloreado matchea
solo [data-{c}] (su default) y nunca lee el --palette-* heredado.
- Generalización a la paleta PRIVADA: checkbox/radio-group/switch ganan las 33
escalas con CERO cambio de CSS (ya consumen --_{c}-palette-*).
- universalPaletteDecls eliminado; 2 guards migrados al modelo compartido
(recipe-css-contract "shared layer + forward"; active-eidos-config contrast
en la capa compartida).
DONE (per-instance): button, toggle, checkbox, radio-group, switch (5/17).
Rollout de las 12 restantes en docs/process/continue-cleanroom-fixes §THM-2.
Doctrina en theming/reference §25 ("The per-instance palette layer").
Verificado en vivo (button + checkbox): color="teal" → escala teal exacta ·
affirm → rol exacto · primary → primary · nested-nocolor en ancestro teal →
primary (sin contaminación). Peso 345K → 329K. Gates: matriz 141/141 ·
eidos 314 · recipe-css-contract + active-eidos-config 98/98 · contracts 38/38.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>