El `data-color` de los morfos declaraba enums de 3 a 8 valores cuando la prop
acepta 42. No era descuido: es que **el morfo no puede nombrar ese vocabulario**.
`COLOR_ROLES` y `PALETTE_SCALES` viven en eidos; `INTENTS` vive en
`src/uix/intent.ts`, propiedad de UIX y de ninguna capa. Y `src/uix/morfo/**`
no importa de eidos jamás — su único import cruzado es `../../intent`. Nadie
podía traer la lista real, así que se copió a mano un subconjunto y se
fosilizó: `<Switch color="teal">` siempre emitió `data-color="teal"`, un valor
que su propia declaración negaba.
## Dos ejes, no uno
- `data-color` = **direccionamiento de paleta**: qué fila de la tabla lee la
instancia. `renderSharedPaletteLayer` emite un bloque por nombre canónico y
el recipe consume los `--palette-*` resultantes. Soma lo resuelve aplicando
la doctrina «el intent evaluativo gana a la jerarquía».
- `data-intent` = **estado evaluativo persistente**, cuyo gemelo transitorio es
`data-event-intent`. Ya lo declaraban `knob`, `timeline` y `toast`.
Estaban fusionados en un solo atributo (`switch`/`toggle` llegaban a listar
`primary, secondary` junto a cuatro intents en el mismo enum), y por eso no se
podía asignar limpiamente a ninguna capa.
## Cambios
- `intentsExcept()` en `src/uix/intent.ts`: narra un subconjunto por EXCLUSIÓN,
tipado contra `Intent`. Un componente toma los 6 o menos, pero nunca vuelve a
escribir el vocabulario — si mañana entra un séptimo intent canónico llega
solo a todo el que no lo haya rechazado. `knob` migrado (conjunto idéntico,
verificado).
- `dialog` → `data-intent`. Aquí SÍ era intent mal etiquetado: 0 usos de
`--palette-*`, sin `resolveComponentColor`, y reglas propias cuyos valores
eran todos intents. El provider declaraba `intent` **y** `color` con la misma
expresión exacta — una copia, no una traducción. Migrados morfo (×2),
provider y las 4 reglas CSS.
- `switch`, `toggle`, `button`, `card`, `avatar`: enum de `data-color` fuera,
`emit: 'value'` en su lugar. El DOM no se mueve: `enumValues` no lo consume
nadie en runtime y `evalAttrPlan` hace `case 'enum': case 'string': return
raw`. Ambos comprobados antes de tocar.
- De paso, los 10 morfos que escribían `expression:` con comilla doble en un
corpus de 173 que usa comilla simple.
## Verificación
514 tests de morfo + contracts + eidos · 7/7 del provider de button (cubre
`data-color='primary'` por defecto y `'threat'` con intent evaluativo) ·
`eidos-lint` con `invalid: 0` en los seis componentes tocados · `check` sin
errores nuevos.
En navegador: `dialog` con `risk` y `threat` tiñe borde y título con los
valores exactos de `--color-risk-border` / `--color-threat-border`; `switch`
resuelve fila de paleta distinta para `neutral`, `primary`, `threat` y `teal`
— esta última una de las 33 escalas donantes que el enum viejo negaba. `rose`,
que no es canónica, sale vacía y sirvió de control negativo.
Nota: `chronos` ya declaraba la forma correcta (`emit: 'value'`, sin enum). Lo
tenía excluido de la auditoría, así que deduje desde cero un patrón que ya
estaba en el árbol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The visual layer of UIX: tokens, themes, per-component CSS recipes, archetype
rules, event reactions and Svelte wrappers over soma's headless providers.
Its source of truth is the DOM the other layers write — it never imports soma
or sema internals.
The reference moved to the docs corpus:docs/architecture/eidos.md
— the ActiveEidos runtime (config, themes, contract, persistence,
resolveToken), what eidos consumes from each layer, the --* token rule,
typographic vertebration, the disciplined-option-C API conventions, the
selector-drift defense and the picker patterns (P-1…P-5).