You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/eidos/README.md

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 Eidos desde ActiveUix;
  • ActiveEidos asume authoring, validacion, generacion CSS, persistencia y contexto visual;
  • events es el servicio perceptivo runtime y morfo.translations es 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:

  • ActiveEidos es 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 de ActiveUix cuando hay contexto: prefs, dom, langs, format y helpers visuales como resolve(...), breakpoint(...) o isBelow(...). Si applyDom esta activo, inyecta/quita <style data-uix-eidos> usando uix.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: escalas compact · comfortable · spacious para 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: anchuras none · hairline · thin · medium · thick, estilos solid · dashed · dotted y 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 fisica 1..6 más aliases semanticos none · subtle · raised · overlay por 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}-static con primitivas estables (renderStaticCss()).
  • ${styleId}-theme con 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 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 — 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:

  1. 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>.
  2. El root vive en {name}.svelte — NO en {name}-provider.svelte. Un solo fichero por root.
  3. NO exportar Provider públicamente. La separación "Provider compound vs flat" es invención retirada; hay UNA forma compound (root + hijos atados como propiedades).
  4. Hijos siguen el naming de air / headless: Trigger, Content, Overlay, Title, Description, Close, Portal, Header, Footer, Item, Indicator, HiddenInput, Group, Label. No inventar nombres.
  5. Portal se incluye donde air lo tenía (Dialog, Drawer, Popover, Tooltip — overlays portaled). Importado de $soma/components/internal.
  6. 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.
  7. 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:

  1. Wrapper, no fork. El .svelte de eidos consume el provider del 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<Size, 'sm' | 'md' | 'lg'>).
  3. Sin prefijo Eidos en los tipos. El path $uix/eidos/components/{x} ya identifica la capa.
  4. 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/actions snippets, 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 itera toaster.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 como data-variant, data-size vienen 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, extendEidosConfig y createThemeBaseEidosConfig permiten 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 y toDocument() / serialize() exponen el envelope versionado para persistencia.
  • Runtime CSS: ActiveEidos reacciona a su preferences compuesto o a fuentes explicitas modeSource / densitySource, soporta themes de config y CSS-only via themeSource, acepta variables runtime en un style block propio, y con applyDom:false no 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 tokens a1..a12, derivadas automaticamente o sobrescribibles con color.alphaScales por theme. Los roles semanticos validos son solo los declarados por el contrato de Eidos.
  • Size: xxs..xxl se renderiza como map global coordinado (control-height, font, icon, padding, gap, radius). full queda 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..6 con aliases semanticos por theme.
  • Layout: ya forma parte del contrato generado. Incluye containerWidth, containerPaddingInline, contentWidth y aspectRatio como tokens estables y authorables desde EidosConfig.
  • Density: ya forma parte del contrato generado. Incluye scale, spaceScale, controlScale y contentScale para los tres niveles canonicos compact, comfortable y spacious, conectados al data-density que proyecta ActiveEidos.
  • Tipografia: usa tamaños canonicos xxs a xxxl, familias libres por key y estilos tipograficos (h1, h2, body, etc.) como Record<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.css ya se genera desde la config base de Eidos con npm run generate:eidos-css y se importa como foundation estatica. contracts/ y themes/base/ ya no entran en el runtime CSS; quedan archivados para consulta/tooling. tokens/components/index.css queda 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 check no reporta errores en este punto; quedan warnings de runes en rutas/componentes de demo fuera del runtime ActiveEidos.

Powered by TurnKey Linux.