27 KiB
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 fuente de verdad es lo que está escrito en el DOM (parts, data-attrs, ARIA, archetypes, event signals) y los tipos públicos de Soma que necesita para componer wrappers.
Handoff 2026-05-13
Eidos queda congelado a nivel de componentes hasta reauditar la arquitectura
de UIX. No tocar src/uix/eidos/components/* salvo orden explicita.
Antes de seguir con wrappers o recipes por componente hay que respetar estas decisiones:
- que contrato minimo consume
EidosdesdeActiveUix; ActiveEidosasume authoring, validacion, generacion CSS, persistencia y contexto visual;eventses el servicio perceptivo runtime ymorfo.translationses el catalogo declarativo de texto owned por el componente;- que parte se genera desde codigo y que parte puede venir solo por CSS;
- como se mantiene la regla de escritura DOM unica en standalone
dom:false.
La referencia de arranque esta en
../active_architecture.md, seccion
Handoff 2026-05-13.
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 pero sí son ortogonales al CSS. Eidos los aloja como wrappers Svelte sobre el Soma.
src/uix/eidos/
active-eidos.svelte.ts → runtime/contexto visual y generacion CSS
index.css → entrypoint que importa todo el CSS
archetypes.css → reglas comunes a [data-archetype=*]
events.css → reacciones a [data-event-*] (sema visual)
generated/base.css → salida estatica generada desde EidosConfig base
contracts/ → contratos CSS archivados; no se importan en runtime
tokens/ → valores CSS legacy hasta generarlos desde ActiveEidos
themes/base/ → CSS base archivado; no se importa en runtime
lib/ → soporte de config, tokens, contrato CSS y tipos compartidos
components/{x}/ → recipe + wrapper + tipos por componente
{x}.css recipe CSS (selectores [data-{x}], etc.)
{x}.svelte wrapper Svelte sobre el componente Soma
types.ts props del wrapper (extiende el contrato público Soma)
index.ts namespace publico (default + partes attached)
lib/ contiene el soporte puro de configuracion visual: primitivas, semantica
visual, themes, contrato CSS y render. generated/base.css es el primer
artefacto estatico generado desde esa configuracion (npm run generate:eidos-css).
contracts/ y themes/base/ quedan como material archivado para consulta y
tooling, pero ya no se importan desde index.css ni desde tokens/index.css.
tokens/components/index.css solo importa tokens de las recipes activas; el
resto de archivos en tokens/components/ queda como material legacy de consulta
y migracion.
Runtime activo
Eidos tiene una clase activa:
ActiveEidoses el runtime activo y contexto visual de los wrappers Svelte. Gestiona configuracion visual pura, primitivas, roles semanticos, themes, validacion, render de CSS y persistencia. Conecta esa configuracion con los servicios deActiveUixcuando hay contexto:prefs,dom,langs,formaty helpers visuales comoresolve(...),breakpoint(...)oisBelow(...). SiapplyDomesta activo, inyecta/quita<style data-uix-eidos>usandouix.dom.
Regla de dependencia: un componente en src/uix/eidos/components/* no importa
getActiveUix() directamente. Consume ActiveEidos.require() y solo conoce la
superficie visual de Eidos.
Regla de ownership: ActiveEidos no crea servicios compartidos. En modo
contexto, ActiveEidos.create(...) obtiene esos servicios de ActiveUix; si
un wrapper requiere uno que no existe, lanza error. En modo explicito,
createActiveEidos(...) recibe servicios ya construidos. Con applyDom:false
queda como runtime sin escritura DOM: puede renderizar, serializar y validar
CSS sin insertar estilos.
La configuracion vive en EidosConfig:
interface EidosConfig {
primitives: PrimitiveSet
semantics: SemanticSet
themes?: ThemeMap
}
Para authoring incremental sobre el theme base existe
EidosConfigPatch. No sustituye al objeto completo: lo extiende de
forma profunda, preservando lo no declarado y reemplazando arrays completos
(por ejemplo fallbacks de una familia tipografica).
const config = createThemeBaseEidosConfig({
primitives: {
typography: {
families: {
primary: { family: 'Inter' }
}
}
},
semantics: {
color: {
roles: {
primary: 'blue'
}
}
}
})
Si la app quiere partir de cero, usa defineEidosConfig() con un
EidosConfig completo y lo pasa a ActiveEidos.create({ config }). Si quiere
partir del theme base, usa createThemeBaseEidosConfig(patch) o directamente
ActiveEidos.create({ themeBase: patch }).
primitives contiene las bases no semanticas: escalas de color de 12 pasos,
size map canonico, espacios, alturas de control, radios, borde, opacidad,
z-index, focus ring, layout, tipografia, sombras, motion e iconos.
semantics.color.roles mapea esas escalas a los roles canonicos:
primary · secondary · tertiary · neutral · affirm · fulfill · risk · threat · loss
No hay danger, success, warning ni info: esos nombres pertenecen a
otros modelos. En Eidos los componentes hablan por intents y jerarquia visual.
ActiveEidos genera tambien la escala alpha de cada paleta fisica:
--scale-blue-a1 … --scale-blue-a12
--primitive-primary-a1 … --primitive-primary-a12
Si el theme declara color.alphaScales, esos valores se respetan. Si no, se
derivan desde el paso sólido de la escala (--scale-{name}-9) con una
progresion canonica de opacidad. Esto permite usar tintas translucidas para
overlays, rings, hovers suaves o scrims sin que cada recipe invente su propia
formula.
Size canonico
Size es discreto y estable:
xxs · xs · sm · md · lg · xl · xxl · full
full es semantica de layout y no genera primitiva fisica. La config mapea
solo xxs..xxl a tokens coordinados:
--size-md-control-height
--size-md-font-size
--size-md-font-line-height
--size-md-font-letter-spacing
--size-md-icon-size
--size-md-padding-inline
--size-md-padding-block
--size-md-gap
--size-md-radius
La regla es: md no cambia de significado por viewport. Lo responsive decide
que size activo se usa (ResponsiveProp<Size>), no redefine los tokens de
md. Porcentajes, vw, dvh, clamp() o min() pertenecen a layouts
como full, panels y containers; no al size canonico de controles, iconos o
tipografia.
Primitivas transversales
El bloque static ya genera las primitivas compartidas que suelen aparecer en Radix Themes, Ark/Panda, Chakra, Tailwind o shadcn como foundation tokens:
layout: anchuras de container, padding inline de container, anchuras de contenido y ratios canonicos. Genera:--container-width-*,--container-padding-inline,--content-width-*y--aspect-ratio-*.density: escalascompact · comfortable · spaciouspara que las recipes puedan ajustar espacio, altura de control o contenido sin redefinir los tokens canonicos. Genera--density-{key}-*y aliases activos como--density-space-scale.border: anchurasnone · hairline · thin · medium · thick, estilossolid · dashed · dottedy aliases--border-width,--border-style,--border.opacity:0 · muted · disabled · scrim · overlay · hover · press · full.zIndex:base · raised · sticky · dropdown · popover · tooltip · modal · toast.shadow: escala fisica1..6más aliases semanticosnone · subtle · raised · overlaypor theme.
La regla es la misma que en size: estos tokens no cambian de significado por
breakpoint. Un componente o wrapper puede elegir otro token en un viewport
concreto, pero --shadow-3, --opacity-disabled o --z-index-modal siguen
siendo la misma coordenada del sistema.
El bridge responsive vive en ActiveDom/ActiveEidos: los wrappers usan
ActiveEidos.resolve(...), breakpoint(...), isAtLeast(...) e
isBelow(...) para decidir que token canonico aplica en cada viewport.
--container-width-md o --content-width-lg no se recalculan por responsive;
lo que cambia es la eleccion del token. La densidad sigue el mismo principio:
ActiveEidos proyecta data-density y publica scalars activos:
:root {
--density-compact-space-scale: 0.84;
--density-comfortable-space-scale: 1;
--density-spacious-space-scale: 1.16;
--density-space-scale: var(--density-comfortable-space-scale);
}
[data-density='compact'] {
--density-space-scale: var(--density-compact-space-scale);
}
Una recipe puede usar calc(var(--space-4) * var(--density-space-scale)).
--space-4 no cambia de significado; cambia la policy visual activa.
themes permite sobrescribir escalas, roles, superficies, contenido, bordes,
focus y sombras por theme. El theme base actual expone base-light y
base-dark; defaultActiveEidosThemeResolver usa el theme activo si existe
como id exacto, preserva ids externos ya cualificados (*-light, *-dark) y,
si no, prueba ${theme}-${mode}.
ActiveEidos no persiste CSS en disco. Si applyDom esta activo, escribe en
el DOM:
${styleId}-staticcon primitivas estables (renderStaticCss()).${styleId}-themecon el theme resuelto cuando procede de la config.
themeSource decide de donde salen los valores de theme:
'auto'(default): genera CSS si la config conoce el theme; si no lo conoce, asume que viene de CSS externo y deja solo los tokens static.'config': modo estricto; si el theme resuelto no existe en la config,renderThemeCss()lanza error.'css': nunca genera CSS de theme; el integrador aporta los valores con CSS externo que respeta el contrato de custom properties.
Para inspeccionar o publicar ese contrato, ActiveEidos expone dos superficies:
getCssContract()devuelve el contrato estructurado, typed, con cada token (name,cssVar,scope,category,path). Es la superficie pensada para editores de theme, validadores, tooling o persistencia de usuario.renderContractCss()renderiza ese mismo contrato como CSS vacío para documentar o bootstrappear themes externos.renderCssVariables()acepta un mapa de custom properties y lo convierte en CSS runtime validado contra el contrato de Eidos.
const tokens = eidos.getCssContract()
const contract = eidos.renderContractCss({
staticSelector: ':root',
themeSelector: "[data-theme='acme-light']"
})
El resultado no asigna valores reales; declara las custom properties que un theme CSS-only puede aportar o sobrescribir:
[data-theme='acme-light'] {
--scale-blue-9: ;
--scale-blue-a9: ;
--primitive-primary-9: ;
--primitive-primary-a9: ;
--color-primary-solid: ;
--size-md-control-height: ;
--border-width-thin: ;
--opacity-disabled: ;
--z-index-modal: ;
--shadow-3: ;
}
La persistencia usa un envelope versionado:
const document = eidos.toDocument()
const json = eidos.serialize()
const hydrated = createActiveEidos({
config: JSON.parse(json),
prefs,
dom
})
El documento tiene forma { kind: 'uix.eidos-config', version: 1, options }.
EidosConfig no lleva version dentro: sigue siendo configuracion
visual pura. Si el shape cambia en el futuro, se sube
EIDOS_CONFIG_DOCUMENT_VERSION; Eidos no intenta compatibilidad silenciosa
con versiones no soportadas. La app decide dónde guardar ese documento
(prefs, backend, archivo, etc.) y qué metadata de producto lo envuelve
(name, owner, timestamps).
Integracion normal dentro de un arbol con ActiveUix:
const uix = createActiveUix({
langs: { schema, defaultLocale: 'es' }
})
setActiveUix(uix)
ActiveEidos.create({
themeBase: {
semantics: {
color: {
roles: { primary: 'blue' }
}
}
},
themeSource: 'auto',
themeResolver,
styleId: 'uix-eidos'
})
config acepta un EidosConfig completo o un EidosConfigDocument.
themeBase acepta solo un patch sobre el theme base. Son
mutuamente excluyentes para que no haya ambiguedad entre "config completa" y
"override del base".
theme en Eidos nombra la familia/theme visual (base, acme,
acme-light, etc.). mode nombra el esquema efectivo light | dark y
density nombra la ergonomia visual activa. Cuando applyDom esta activo,
ActiveEidos proyecta data-theme, data-mode y data-density en el
documento a traves de ActiveDom.
Un theme CSS-only puede vivir fuera de TypeScript:
[data-theme='acme-light'] {
--scale-blue-9: #006adc;
--scale-blue-a9: color-mix(in srgb, var(--scale-blue-9) 56%, transparent);
--primitive-primary-9: var(--scale-blue-9);
--primitive-primary-a9: var(--scale-blue-a9);
--color-primary-solid: var(--primitive-primary-9);
--font-family-primary: Inter, system-ui, sans-serif;
--size-md-control-height: 38px;
--shadow-3: 0 8px 24px rgb(15 23 42 / 0.12);
}
Para pasar valores desde runtime sin recompilar:
activeEidos.setCssVariables({
'--color-primary-solid': 'rebeccapurple',
'size-md-control-height': '40px',
'shadow-3': '0 10px 28px rgb(20 20 20 / 0.16)'
})
ActiveEidos lo escribe en ${styleId}-variables como un <style>
gestionado. Por defecto valida los nombres contra getCssContract(); si una
app necesita variables locales fuera del contrato puede usar
{ strict: false }.
No crear ActiveEidos desactiva la capa visual runtime de Eidos. Los
componentes Soma siguen funcionando headless porque el comportamiento pertenece
a Soma.
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
Los wrappers de components/* importan las partes públicas de Soma
directamente ($soma/components/{x}) y sólo añaden superficie visual de
Eidos: tokens, recipes, layout shells y data-attrs de presentación. No hay
facade intermedio por componente. Ejemplo:
// eidos/components/toggle/types.ts
import type { ProviderProps as SomaToggleProviderProps } from '$soma/components/toggle';
export type ToggleProps = SomaToggleProviderProps & { 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 proyecta data-event-* durante el hold
mediante SignalProjector y eidos reacciona vía events.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
isDisabledpropio del componente con eldisabledheredado 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:
- Authorship clarity en debug — inspeccionar un elemento y ver
--toggle-bginforma que viene del visual layer de UIX. - Override discipline — un consumer que sobreescribe
--color-primary-elementsabe 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 + eidosevents[].prewrite[]— soma (ejecuta) + eidos (anima)data-starting-style/data-ending-style— soma (Presence) + eidos (anima)
Convenciones del API — disciplined option C (vigente 2026-05-10)
Las convenciones doctrinales viven en
src/docs/sema-implementation-guide.md
sección Parte IV (operación instantánea = un evento; sistema
unificado de 8 tokens; intent ↔ color resolution; subset por
componente; iconOnly sr-only; sound prepare-time priming).
La convención del shape público del componente eidos vive en
components/README.md. Resumen — 7 reglas
duras:
- Un solo punto de entrada por componente — la default export es
el componente root visual, llamado igual que el componente
(
<Drawer>,<Tabs>,<Checkbox>). NO<Drawer.Provider>, NO<Drawer.Root>. - El root vive en
{name}.svelte— NO en{name}-provider.svelte. Un solo fichero por root. - NO exportar
Providerpúblicamente. La separación "Provider compound vs flat" es invención retirada; hay UNA forma compound (root + hijos atados como propiedades). - Hijos siguen el naming de air / headless:
Trigger,Content,Overlay,Title,Description,Close,Portal,Header,Footer,Item,Indicator,HiddenInput,Group,Label. No inventar nombres. Portalse incluye donde air lo tenía (Dialog, Drawer, Popover, Tooltip — overlays portaled). Importado de$soma/components/internal.- NO flat con snippet slots como API principal —
<Drawer trigger={...} title={...}>está retirado. La compound explícita expone las decisiones de composición que el flat escondía. - Hijos atados con asignación explícita, no
Object.assign(Svelte 5 puede manejar mal la mutación bulk del component constructor durante hidratación):const Drawer = DrawerRoot as DrawerNamespace Drawer.Trigger = Trigger Drawer.Content = Content
Plus reglas de implementación:
- Wrapper, no fork. El
.sveltede eidos consume el provider del Soma y le añade los data-attrs de tokens visuales. No reimplementa estado. Sizedesdelib/types.ts. Componentes que aceptan tamaños reusan el tipo compartido y narrowingan al subset que su recipe soporta (Extract<Size, 'sm' | 'md' | 'lg'>).- Sin prefijo
Eidosen los tipos. El path$uix/eidos/components/{x}ya identifica la capa. - CSS recipe sin prefijo
--eidos-. Los custom properties usan--{component}-…para los públicos y--_{component}-…para los internos.
Por qué disciplined option C
El patrón anterior (default flat con snippet slots + Provider compound duplicado) tenía dos problemas:
- Doble verdad: dos APIs (flat con
trigger/title/actionssnippets, compound con children explícitos) llevaban a la misma funcionalidad por caminos divergentes. Cualquier tweak visual exigía actualizar ambas. - Inventaba sobre air: air era pure compound (
<Drawer.Provider>con children). El flat con snippets fue una invención sin baseline ni firma del usuario.
Option C disciplinada:
- Toma de air la convención de hijos (Trigger, Content, Portal, …)
- Toma de bits-ui / shadcn-svelte la ergonomía del root con propiedades
attached (
<Drawer><Drawer.Trigger>) - Elimina la invención del flat con snippets (no era ni air ni soma)
Caso especial — Toast
Tiene dos roots independientes (no anidados):
<Toast>— manual compound (consumer iteratoaster.toasts)<Toaster />— imperative auto-mount (template default)
Export separados; Toaster NO va attached como Toast.Toaster porque
es un root competidor, no un hijo. Documentado en
components/toast/index.ts.
Defensa contra drift de selectores
La cadena morfo → soma → eidos depende de que los selectores que eidos
escribe ([data-{component}], [data-{component}-{part}],
[data-state=...], [data-event-*=...]) se mantengan en sintonía con
los attrs que el morfo declara y el runtime emite. Hay dos defensas
distintas según el tipo de consumidor:
Compile-time (TS / Svelte) — typed builder
Cualquier consumidor TypeScript que construya selectores
MUST usar semaSelector(morfo, partKebab, matchers?) desde
$uix/morfo. Esto cubre las cascade rules de sema/components/*.ts y
cualquier lógica TypeScript en eidos/components/{x}/ que apunte a
attrs morfo-backed.
import { semaSelector } from '$uix/morfo'
import { toggleMorfo } from '$uix/morfo/components/toggle'
semaSelector(toggleMorfo, 'provider', { eventName: 'commit-toggle' })
// → '[data-toggle][data-event="commit-toggle"]'
Renombrar una part o un evento en el morfo rompe el typecheck. Es
imposible que un selector TypeScript drifte silenciosamente. Ver
morfo/README.md#typed-selector-builder--semaselector.
Run-time (CSS recipes) — eidos-lint como red de seguridad opt-in
Los recipes son CSS plano (components/{x}/{x}.css); no hay typed
builder en el lado CSS. Para esa superficie:
node scripts/eidos-lint.ts toggle # un componente
node scripts/eidos-lint-all.ts # todos
Clasifica cada selector [data-*] como:
- morfo-backed — declarado en el morfo; soma runtime lo emite;
el valor (si hay enum) cae dentro de
data[].values. - eidos-only — el marker está, pero al menos un
data-*no está declarado en el morfo. Válido por convención (tokens visuales comodata-variant,data-sizevienen del wrapper). - invalid — referencia un attr declarado pero con un valor fuera del enum. Bug.
El lint es una red de seguridad, no el contrato. El contrato vive en el morfo y se defiende a nivel de tipos donde se puede. El lint existe sólo para la porción CSS-pura que aún no consume el morfo a través de TypeScript. Cuando los recipes migren a un builder, el lint podrá retirarse.
Estado actual (2026-05-13)
- ActiveEidos: implementado como runtime/contexto visual y superficie de
configuracion. Gestiona primitivas, roles canonicos, themes, validacion,
contrato CSS, persistencia y render CSS (
renderStaticCss,renderThemeCss). - Authoring API:
defineEidosConfig,extendEidosConfigycreateThemeBaseEidosConfigpermiten crear configuraciones completas o extender el theme base sin mutar las constantes del sistema.getCssContract()expone el contrato estructurado,renderContractCss()lo materializa como CSS para themes externos,renderCssVariables()permite escribir overrides runtime contract-aware ytoDocument()/serialize()exponen el envelope versionado para persistencia. - Runtime CSS:
ActiveEidosreacciona a supreferencescompuesto o a fuentes explicitasmodeSource/densitySource, soporta themes de config y CSS-only viathemeSource, acepta variables runtime en un style block propio, y conapplyDom:falseno escribe en el DOM. - Color: basado en roles canonicos de jerarquia e intents
(
primary,secondary,tertiary,neutral,affirm,fulfill,risk,threat,loss) y escalas de 12 pasos. Por cada escala genera alpha tokensa1..a12, derivadas automaticamente o sobrescribibles concolor.alphaScalespor theme. Los roles semanticos validos son solo los declarados por el contrato de Eidos. - Size:
xxs..xxlse renderiza como map global coordinado (control-height, font, icon, padding, gap, radius).fullqueda como valor de layout, no como primitiva fisica. - Border / opacity / z-index / shadow: ya forman parte del contrato
generado. Border define escala de width/style y aliases globales; opacity
cubre estados de UI y overlays; z-index cubre capas comunes; shadow combina
escala fisica
1..6con aliases semanticos por theme. - Layout: ya forma parte del contrato generado. Incluye
containerWidth,containerPaddingInline,contentWidthyaspectRatiocomo tokens estables y authorables desdeEidosConfig. - Density: ya forma parte del contrato generado. Incluye
scale,spaceScale,controlScaleycontentScalepara los tres niveles canonicoscompact,comfortableyspacious, conectados aldata-densityque proyectaActiveEidos. - Tipografia: usa tamaños canonicos
xxsaxxxl, familias libres por key y estilos tipograficos (h1,h2,body, etc.) comoRecord<string, TypographyStyle>. - Convencion del API de componentes: disciplined option C esta
documentada en
components/README.md, pero la migracion de componentes/rutas no debe mezclarse con el trabajo del modulo general. - CSS generado/legacy:
generated/base.cssya se genera desde la config base de Eidos connpm run generate:eidos-cssy se importa como foundation estatica.contracts/ythemes/base/ya no entran en el runtime CSS; quedan archivados para consulta/tooling.tokens/components/index.cssqueda reducido a las recipes activas; el resto de token files son material legacy de consulta/migracion. El siguiente paso es decidir que queda como recipe estable y que se elimina. - Primitivas pendientes de cerrar: decision final sobre que CSS legacy queda como recipe frente a lo generado.
- Check del repo:
npm run checkno reporta errores en este punto; quedan warnings de runes en rutas/componentes de demo fuera del runtimeActiveEidos.