# Eidos `Eidos` es la **capa visual** de UIX. Cubre lo que en la rama muerta `air/` era el "runtime visual" más el sistema de tokens — sin heredar código. Lee del DOM lo que las capas anteriores han escrito (morfo runtime + sema visual channel) y aplica estilos, animaciones y wrappers ergonómicos. ``` Morfo declara la genética ↓ Soma transcribe el comportamiento → DOM (data-state, data-color, aria-*) ↓ Sema emite señales perceptivas → DOM (data-event-*) durante el hold ↓ Eidos aplica el visual: tokens, themes, recipes, archetypes, wrappers ``` Eidos nunca importa internals de soma ni de sema. Su única fuente de verdad es **lo que está escrito en el DOM** (parts, data-attrs, ARIA, archetypes, event signals) — y los tipos públicos del soma para componer wrappers. ## No es solo CSS El primer mental model fue "eidos = CSS reactivo". Insuficiente: hay concerns visuales puros (variant, size, layout flags, icon slots) que no son parte del comportamiento headless de soma pero sí son ortogonales al CSS. Eidos los aloja como **wrappers Svelte sobre los soma providers**. ``` src/uix/eidos/ index.css → entrypoint que importa todo el CSS archetypes.css → reglas comunes a [data-archetype=*] events.css → reacciones a [data-event-*] (sema visual) contracts/ → APIs de variables CSS (declaraciones vacías) tokens/ → valores per-componente que referencian contracts themes/base/ → light + dark + _static lib/ → tipos/helpers transversales (Size, …) components/{x}/ → recipe + wrapper + tipos por componente {x}.css recipe CSS (selectores [data-{x}], etc.) {x}.svelte wrapper Svelte sobre soma types.ts props del wrapper (extiende soma) index.ts re-exports públicos (default + Provider) ``` `components/{x}/` es la **forma actual** del componente — toggle es el piloto. Los componentes legacy quedan en `components/{x}.css` (CSS solamente) hasta su migración a la forma de subdirectorio. ## Qué consume ### De morfo (declaración) | Pieza | Eidos la usa para | |---|---| | `parts[].kebab` | selectores `[data-{component}-{kebab}]` | | `parts[].archetype` | reglas transversales `[data-archetype=trigger]` | | `parts[].states` + `data[].values` | variantes `[data-state=open]` | | `parts[].data` con `data-starting-style` / `data-ending-style` | hooks de animación enter/exit | | `events[].name` | selectores `[data-event=dismiss]`, `[data-event^=commit]` | | `events[].semantic.family` + `.intent` | tinta semántica de transiciones | | `events[].prewrite[]` (e.g. `data-last-action`) | tintar exit anim por causa | | `focus.trap` | hint de layout para overlays | ### De soma (tipos públicos) Sólo importa **tipos** del soma, vía `'$soma/components/{x}/types'`, nunca clases ni state managers internos. Ejemplo: ```ts // eidos/components/toggle/types.ts import type { ToggleProps as SomaToggleProps } from '$soma/components/toggle/types'; export type ToggleProps = SomaToggleProps & { variant?: ToggleVariant; size?: ToggleSize; … }; ``` El wrapper `.svelte` reexporta el provider headless y le añade los data-attrs de tokens visuales (`data-variant`, `data-size`, `data-block`, `data-icon-only`). ### De sema (DOM) Sólo el DOM. El visual channel escribe `data-event-*` durante el hold y eidos reacciona vía `events.css`: ```css [data-event-family='commit'][data-event-phase='active'] { animation: eidos-commit-settle 260ms var(--ease-out); } [data-event-family='commit'][data-event-intent='threat'][data-event-phase='active'] { animation: eidos-announce-pulse-threat 400ms var(--ease-spring); } ``` `events.css` documenta los hold defaults por familia y por qué se usa `animation: @keyframes` (no `transition`) para reacciones a señales. ## Qué NO consume - **Computed state lógico** del provider (e.g. la composición de `isDisabled` propio del componente con el `disabled` heredado de un Field). Eidos sólo ve el resultado: `[data-disabled]` está o no está. - **Internals de runtime**. No sabe si un attr lo escribió `dom.apply`, Svelte render o el provider manualmente. Sólo le importa que esté. - **Layers de soma** (Presence, Dismissal, ScrollLock, FocusScope). Reacciona a sus efectos visibles, no a su existencia. ## La regla de los `--*` tokens Eidos posee el namespace `--*` en el visual layer. Razones: 1. **Authorship clarity en debug** — inspeccionar un elemento y ver `--toggle-bg` informa que viene del visual layer de UIX. 2. **Override discipline** — un consumer que sobreescribe `--color-primary-element` sabe que está tocando contrato visual, no nombrando-colisionando con una variable local. Las capas superiores (sema, soma, morfo) **NO consumen** estos tokens y **no usan** el prefijo. Cada una carga sus propias concerns (perceptual durations, behavior, contract DNA) ortogonales al rendering visual. ## La regla "2-de-3" (heredada de morfo) Una extensión a morfo se justifica si **al menos dos de las tres capas** (soma, sema, eidos) la consumen. Las que entraron por el voto de eidos: - `archetype` — eidos + sema (+ soma como emisor) - `events[].semantic.family/intent` — sema + eidos - `events[].prewrite[]` — soma (ejecuta) + eidos (anima) - `data-starting-style` / `data-ending-style` — soma (Presence) + eidos (anima) ## Convenciones del API Las convenciones doctrinales (operación instantánea = un evento; sistema unificado de 8 tokens; intent ↔ color resolution; subset por componente; eidos no es solo CSS; estructura de directorios; wrapper composition; soma compound vs eidos flat; iconOnly sr-only; sound eager-init) viven en [`src/docs/sema-implementation-guide.md`](../../docs/sema-implementation-guide.md) sección **Parte IV — Convenciones del API**. Esa guía es autoritativa. Los puntos esenciales para autores que migran un componente a eidos: 1. **Wrapper, no fork.** El `.svelte` de eidos consume el provider de soma y le añade los data-attrs de tokens visuales. No reimplementa estado. 2. **`Size` desde `lib/types.ts`.** Componentes que aceptan tamaños reusan el tipo compartido y narrowingan al subset que su recipe soporta (`Extract`). 3. **Sin prefijo `Eidos`** en los tipos. El path `$uix/eidos/components/{x}` ya identifica la capa. 4. **`index.ts` exporta `default` + `Provider`.** Para componentes single-part el consumer puede usar `import Toggle` y `` plano; el `Provider` queda para `import * as Toggle` consumers que prefieren la forma compound. 5. **CSS recipe sin prefijo `--eidos-`.** Los custom properties usan `--{component}-…` para los públicos y `--_{component}-…` para los internos. ## Linter `scripts/eidos-lint.ts ` y `scripts/eidos-lint-all.ts` clasifican cada selector `[data-*]` del CSS de eidos como **morfo-backed** (declarado en el morfo, soma runtime lo emite), **eidos-only** (token visual que el wrapper añade — `data-variant`, `data-size`, etc.), o **invalid**. Es la barrera de drift entre contrato y CSS. ## Estado actual (2026-05-08) - **Capa**: existente, en migración progresiva. - **Pilot**: `components/toggle/` — wrapper + recipe + tipos. - **Migrados a wrapper**: solo toggle por ahora. - **Pendientes de migrar a wrapper**: switch, collapsible, dialog, drawer, popover, toast, avatar (sus archivos `.css` ya existen en `components/`; falta crear el subdirectorio con `.svelte` + `index.ts` + `types.ts`). - **Tokens y themes**: completos en `tokens/`, `themes/base/`. - **Linter**: funcional sobre los CSS planos; aún por validar contra los recipes en subdirectorio.