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
dev 72e8fb57ea
feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout
3 months ago
..
active-uix fix(uix): sema timing on uix.timers + overlay open is sequence:'post' 3 months ago
eidos feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout 3 months ago
langs feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout 3 months ago
morfo feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout 3 months ago
sema fix(uix): sema timing on uix.timers + overlay open is sequence:'post' 3 months ago
soma feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout 3 months ago
words feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage 4 months ago
COMPONENT_COMPLETION_CHECKLIST.md docs(corpus): give the two component checklists distinct roles + cross-links 4 months ago
README.md docs(corpus): point the vocabulary docs at CANON.md (authority), wire it into the reading order 4 months ago
active_architecture.md docs(active-architecture): §0 hand-off → contracts reference, §10 status → process snapshot 4 months ago
contracts.test.ts feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold 3 months ago
contracts.ts fix: address architectural audit findings + sync ecosystem docs 4 months ago
intent.ts Tighten uix audit contracts 5 months ago
types.ts uix: relocate Layer + PartRef to src/uix/types.ts 5 months ago

README.md

UIX

Documento corto de posicionamiento arquitectónico para src/uix.

Visión de conjunto: para entender las cuatro capas (morfo, soma, sema, eidos) en una sola lectura — motivaciones y articulación incluidas — ir a active_architecture.md. Este README mantiene la introducción más narrativa.

Convenciones doctrinales del API (intent ↔ color, subset por componente, root visual con partes attached en eidos, sound prepare-time priming): viven en src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md. Autoritativo para todo wrapper / migración nueva.

UIX no intenta ser "otra librería de componentes". La apuesta es más ambiciosa y estructural: separar capas que casi todos los frameworks actuales mantienen mezcladas.

En la mayoría de sistemas de UI, estas cosas viven pegadas:

  • contrato público del DOM
  • comportamiento headless
  • accesibilidad
  • semántica del evento
  • capa visual
  • motores modales (sound, haptic, motion)
  • integración con servicios de app

UIX intenta partir ese bloque en piezas con fronteras fuertes.


1. La idea central

UIX modela la interfaz como varias capas cooperando, no como un único componente gigante que hace todo a la vez.

App
├─ servicios transversales (active-app)
│  └─ adom, langs, format, ...
└─ componentes
   └─ capa estructural / semántica / comportamental / visual

La intuición:

  • la estructura pública del componente no es lo mismo que su comportamiento
  • la semántica de un evento no es lo mismo que su materialización
  • el DOM activo no es lo mismo que utilidades DOM puras
  • la app no debería acoplar motores modales entre sí

UIX pone nombres y contratos explícitos a esas separaciones.


2. Las capas de UIX

Morfo (src/uix/morfo/)

Contrato estructural cross-layer del componente. Define parts, data-*, ARIA, foco, teclado, eventos y, cuando el texto pertenece al contrato, translations del componente. No es prose ni runtime — es la forma canónica pública.

Ver: morfo/README.md

Sema / Events (src/uix/sema/)

Vocabulario semántico y orquestación de eventos perceptivos. sema queda como nombre historico de la carpeta; la superficie publica de ActiveUix usa events.

  • familias canónicas (8): contact, commit, signal, handle, emerge, shift, sustain, delegate
  • intents canónicos: neutral, affirm, fulfill, risk, threat, loss
  • uix.events.emit(signal) para que el provider publique ocurrencias
  • registry de canales (VisualChannel, SoundChannel, HapticChannel)
  • src/uix/sema/channels.ts como fuente unica de ids, signatures y overrides de canales
  • src/uix/sema/sounds.ts como repositorio nominal unico de sonidos sintéticos, .wav externos y recetas dinámicas
  • semántica secuencial estricta del VisualChannel: prepara data-event-* mediante un proyector DOM, mantiene durante el hold y limpia ANTES de resolver la Promise

Sema no decide qué ocurrió (eso lo decide el provider). Solo orquesta la ocurrencia y la despacha a los canales registrados.

Ver: sema/README.md

Soma (src/uix/soma/)

Capa headless de comportamiento: estado, contexto, a11y, keyboard / pointer / focus, emisión de eventos.

Soma no conoce la implementación concreta de los engines modales. Su trabajo es emitir hechos del componente, no materializarlos.

Ver: soma/README.md, soma/SOMA_ARCHITECTURE.md

Eidos (src/uix/eidos/)

Capa visual: tokens, themes, recipes CSS por componente, archetype rules, event reactions y wrappers Svelte sobre los providers headless de soma con las props visuales (variant, size, block, iconOnly, icon, checkMark).

El modulo general queda aplanado en ActiveEidos:

  • ActiveEidos — runtime activo/contexto visual creado por ActiveEidos.create(...). Gestiona configuracion visual, primitivas, roles canonicos, themes, validacion, generacion de CSS y persistencia. Con servicios de ActiveUix, recibe dom, langs, prefs y format; mode y density llegan por fuentes explicitas o defaults propios. Inyecta <style data-uix-eidos> solo cuando la app quiere CSS runtime en vez de CSS precompilado.

La app puede aportar un EidosConfig completo o un themeBase patch parcial desde ActiveEidos.create({ config/themeBase }), o delegar los valores de theme a CSS externo con themeSource: 'css'. getCssContract() expone la lista typed de custom properties y renderContractCss() la publica como CSS vacío para themes externos. Para editores o preferencias en vivo, ActiveEidos puede inyectar un mapa de variables runtime en un <style> propio validado contra ese contrato. Cuando la app quiera persistir una configuracion completa, guarda un EidosConfigDocument versionado (kind + version + options) y lo puede pasar de vuelta como ActiveEidos.create({ config: document }).

El size de Eidos es canonico y discreto: xxs..xxl/full. ActiveEidos genera tokens fisicos coordinados para xxs..xxl; full queda reservado para layout responsivo.

El contrato generado tambien incluye alpha color scales (--scale-blue-a1..a12 y --primitive-primary-a1..a12), borde (width/style + aliases), layout (containerWidth, contentWidth, aspectRatio), density scalars conectados a data-density, opacidad, z-index y sombras con escala fisica 1..6 más aliases semanticos por theme. Los aliases de recipe activos (--toast-*, --dialog-*, etc.) ya viven en EidosConfig.recipes; el artefacto estatico generado vive en src/uix/eidos/generated/base.css y src/uix/eidos/index.css lo importa como foundation para SSR/docs con ActiveEidos.create({ applyDom: false }). Los antiguos CSS de contracts/ y themes/base/ se retiraron del arbol activo; el contrato se obtiene desde ActiveEidos.getCssContract() / renderContractCss(). El directorio tokens/ ya no forma parte del runtime. ActiveEidos.listRecipes() y getRecipeTokens(component) exponen esos aliases para editores de theme sin leer CSS.

La migración de componentes Soma -> Eidos se retoma por tandas pequeñas. A fecha 2026-05-17, además de los pilotos previos, ya están migrados meter, progress, slider, pagination, rating-group, search-field y number-field, y breadcrumb; cada uno envuelve partes públicas de Soma y añade únicamente superficie visual.

Eidos lee del DOM lo que las otras capas escriben (parts, data-attrs, ARIA, event signals) — nunca importa internals de soma ni de sema.

Ver: eidos/README.md

active-uix (src/uix/active-uix/)

Composition root. Solo ActiveApp y ActiveUix pueden crear servicios compartidos:

  • createActiveUix(options) — standalone, instancia services y prefs internamente.
  • attachActiveUix(activeApp) — adjunta a un ActiveApp ya compuesto por la aplicación; valida los servicios requeridos y no crea sustitutos.

Las capas activas consumen ActiveUix vía getActiveUix() y exponen la superficie minima que necesitan sus componentes. Los componentes Soma leen Soma; los componentes Eidos leen ActiveEidos. Así no saben qué ruta de boot se usó ni dependen de la superficie completa de ActiveUix.

ActiveUix y prefs

ActiveUix no exige ni conoce arts/frontend; ese artefacto fue retirado. El patron sigue a ActiveApp: prefs es core y las capas consumidoras se sincronizan desde sus dimensiones, pero cada capa proyecta solo lo que posee.

  • prefs.language sincroniza el idioma activo de uix.langs.
  • prefs.locale alimenta uix.format.
  • prefs.direction es la unica fuente de direccion efectiva; deriva de language cuando no hay override.
  • prefs.motion, prefs.sound y prefs.haptic son preferencias transversales de percepcion/interaccion.
  • theme, mode y density pertenecen a Eidos: theme nombra la familia visual, mode resuelve light | dark y density ajusta ergonomia visual.

La escritura global va por el ActiveDom que se inyecte, no por mutaciones directas:

ActivePrefsDomProjection -> dir, data-motion, data-sound, data-haptic
ActiveEidos              -> data-theme, data-mode, data-density

ActiveUix no crea automaticamente ActivePrefsDomProjection. Ese proyector pertenece a arts/prefs y lo cablea el composition root cuando quiere esos attrs globales. Eidos se crea aparte mediante ActiveEidos cuando se necesita runtime visual.

Ejemplo de shell UIX standalone (como la ruta /uix de docs):

const uix = createActiveUix({ langs, prefs: { schema } });

setActiveUix(uix);
Soma.create();

const prefsProjection = createActivePrefsDomProjection({ prefs: uix.prefs, dom: uix.dom });
const eidos = ActiveEidos.create({
	theme: 'base',
	modeSource,
	applyDom: true
});

El toggle de modo claro/oscuro alimenta ActiveEidos.modeSource, no uix.prefs.theme. prefs.theme no forma parte del preset core de UIX.

En modo standalone, createActiveUix() instancia ActivePrefs con el preset estandar de UIX y crea los servicios configurados. En modo attach, attachActiveUix(app) reutiliza app.prefs, app.langs, app.dom, app.clipboard, app.format y el motor perceptivo cuando existen. UIX lo expone como uix.events; en attach mode el servicio de ActiveApp se llama events. langs y dom son requeridos para attach; si faltan, se lanza error temprano. clipboard, format y events son servicios opcionales: si una capa los pide y ActiveUix no los expone, fallan con error explicito en vez de crear sustitutos.

soma.prefs es la vista de preferencias que necesitan los providers headless, respaldada por uix.prefs, no por frontend.

adom (src/arts/adom/, alias $adom)

Runtime DOM activo de aplicación. No es semantic engine. Su única función es coordinar y sincronizar mutaciones DOM vía app.dom.apply(change) y app.dom.remove(target, names).

ADom recibe instrucciones ya resueltas. No las interpreta.

Ver: src/arts/adom/README.md

libs/dom (src/libs/dom/)

Utilidades DOM puras o casi puras (contains, getDocument, getWindow, foco, traversal, wrappers base de observers). No contiene runtime activo — ese papel es de adom.


2.bis Cómo se ejecuta un componente

La arquitectura cerrada (post-2026-04-25) define seis piezas con responsabilidades disjuntas. Ninguna invade a la siguiente.

Morfo            declara
SomaRuntime     transcribe
Provider         aporta sources, targets y handlers
Effects          sincronizan attrs derivados
EngineSemantic   orquesta prepare + dispatch a canales perceptivos
VisualChannel    prepara data-event* via SignalProjector + mantiene hold
SignalProjector  proyecta data-event* via uix.dom
ADom             aplica mutaciones DOM (commit estructural)

El reparto operativo

Morfo es DNA: un fichero por componente que declara parts, data-*, aria-*, role, keyboard, focus, events. No ejecuta nada.

SomaRuntime (en soma/) interpreta el morfo. Una instancia por componente recibe del provider las fuentes de estado, los targets DOM y los handlers de eventos. Expone partProps(part), attachPart(part, target), keydown(part, event), trigger(eventName).

Provider aporta lo que el morfo no puede inferir: getters reactivos para states y props, getters reactivos para parts (ids dinámicos), handlers síncronos para los events, glue de layers ortogonales (Presence, Dismissal, ScrollLock — no son morfo).

Effects (registrados por el runtime al montar) escuchan cambios en los sources y aplican los attrs derivados vía dom.apply.

EngineSemantic recibe la ocurrencia desde runtime.trigger. Genera el id, ejecuta los prepare de canales y despacha la señal a TODOS los canales registrados. El VisualChannel (built-in) proyecta data-event-* mediante SignalProjector, mantiene los attrs durante el hold configurado, los limpia y solo entonces resuelve la Promise (semántica secuencial estricta). Los canales no-visuales (sound, haptic) son fire-and-forget.

ADom solo aplica el commit estructural posterior. No interpreta. Sema y ADom son capas hermanas: el engine ya no depende de ADom.

La secuencia de runtime.trigger(eventName)

1. prewrite imperativo (transient markers como data-last-action)
2. await events.emit(event)
3. handler síncrono del provider muta state
4. effects derivan y aplican attrs estructurales (data-state, aria-*)

El handler muta state. Los effects ven el cambio y reescriben el DOM. ADom es el único escritor de attrs mutables.

Tres escenarios de Soma

// Cambio estructural sin señal
provider.commitState(change);
// internamente: dom.apply(change)

// Cambio estructural con señal
provider.commitState(change, event);
// internamente: await events.emit(event); dom.apply(change)

// Señal sin cambio estructural
provider.emitEvent(event);
// internamente: void events.emit(event)

Reglas operativas

  • Lo que dom.apply escribe, Svelte no lo renderiza. partProps solo emite identidad estática (id, marker, ref).
  • Los handlers de events son síncronos. Async va fuera del trigger.
  • Los guards (if (disabled) return) van en el call-site, no dentro del handler — si entran al handler, ya emitieron señal perceptiva.
  • Sema y ADom son capas hermanas: ninguna depende de la otra.
  • morfo.events.commits es descriptivo: documenta lo observable, no lo ejecuta. La cadena causal real es handler → state → effect.

3. Qué hace distinto a UIX

3.1 El contrato estructural es una capa propia

En la mayoría de librerías la estructura pública del componente está dispersa:

  • atributos en el provider
  • roles en el render
  • partes en CSS
  • selector names en docs
  • contratos en tests

UIX concentra eso en Morfo. Eso permite:

  • docs derivadas del contrato
  • validación cross-layer (linter de eidos)
  • menos drift entre headless y visual
  • tooling más fiable

3.2 La semántica no se mezcla con la ejecución

UIX separa el nombre de la ocurrencia de su materialización modal. Sonido, vibración, CSS y motores futuros usan el mismo vocabulario sin quedar pegados entre sí.

3.3 El comportamiento headless no carga con toda la modalidad

Soma no es un mega-engine que sabe de todo. No reproduce WAVs ni feedback háptico, no decide la física perceptiva de cada canal. Soma emite. Los canales ejecutan.

3.4 El DOM activo es infraestructura de app

UIX reconoce que hay hechos transversales del DOM que varios consumidores quieren escuchar, y por eso introduce adom como servicio de app.

La regla no es "todo acceso DOM pasa por ADom". La regla es mas precisa: escrituras gestionadas por UIX, listeners de document/window, consultas globales, observers (ResizeObserver, MutationObserver, IntersectionObserver) y acciones imperativas transversales pasan por ActiveDom. Esto incluye foco y scroll imperativo (focus, scrollTo, scrollIntoView, scrollWindowTo). Las lecturas locales de un elemento que el componente ya posee (contains, closest, getBoundingClientRect, scrollTop) se quedan en el componente.

3.5 La app compone servicios, no "super componentes"

active-app permite que la aplicación componga langs, dom, clipboard, format, events y que ActiveUix los entregue a scopes de capa (Soma, Eidos) en lugar de obligar a cada componente a conocer la raíz activa completa.


4. Lo que UIX no es

UIX no es:

  • una colección plana de componentes visuales
  • un simple wrapper opinionated sobre primitives existentes
  • un design system clásico donde visual, comportamiento y contratos viven juntos
  • un servicio monolitico de eventos que ejecuta todas las modalidades
  • un EventEmitter global disfrazado de arquitectura

La originalidad de UIX no está en inventar nombres exóticos, sino en separar problemas reales que otros sistemas suelen aceptar como un único bloque.


5. Reglas de dependencia

Morfo            → declara contratos
SomaRuntime     → interpreta morfo dentro de Soma
Provider         → aporta sources, targets, handlers
Effects          → sincronizan state → attrs
EngineSemantic   → registry + prepare/dispatch de señales a canales
VisualChannel    → prepara data-event* via SignalProjector + mantiene hold
SignalProjector  → proyecta data-event* via uix.dom
ADom             → aplica mutaciones DOM (commit estructural)
Eidos            → materializa visualmente leyendo DOM
App              → compone servicios

Reglas duras:

  • Morfo no conoce Soma ni código de runtime
  • SomaRuntime lee Morfo y depende de Sema
  • Provider no escribe attrs mutables al DOM directamente; los aporta como sources al runtime
  • EngineSemantic no muta el DOM directamente; solo orquesta hooks de canales
  • VisualChannel.prepare() delega la proyeccion DOM a un SignalProjector
  • DomSignalProjector escribe mediante el ActiveDom recibido desde ActiveUix
  • Sema y ADom son capas hermanas
  • ADom no conoce Morfo, ni Sema, ni Soma, ni Eidos; solo ejecuta mutaciones, listeners y acciones DOM transversales que recibe
  • ActiveEidos adapta ActiveUix para componentes visuales; los wrappers no importan getActiveUix() directamente
  • Eidos consume DOM y data-*, no internals de Soma ni Sema
  • Lo que dom.apply escribe, Svelte no lo renderiza

Regla 2-de-3 para extender Morfo

Una extensión a Morfo solo se justifica si al menos dos de las tres capas (soma, sema, eidos) la consumen. Si solo soma se beneficia, el patrón es virtual prop en provider — no extender el contrato.

Extensión Soma Sema Eidos En morfo
parts[].archetype ✅ emit ✅ verbs por rol ✅ selectores transversales ✅
events[].semantic ✅ payload ✅ vocabulario ✅ tinta ✅
events[].prewrite ✅ ejecuta ✅ secuencia ✅ tinta exit ✅
firstOf value source ✅ only — — ❌
prop-not-nullish cond ✅ only — — ❌
Field-context OR — virtual prop — — ❌

Vocabularios canónicos cross-layer

  • Archetypes: clasificación de partes cross-component. Definida en src/uix/morfo/types.ts:ARCHETYPE_VOCABULARY. Emitida como data-archetype="..." por runtime.partProps.
  • Verbs: action verbs canónicos para morfo.events[].name. Definida en src/uix/sema/verbs.ts:SEMA_VERBS. Convención para composite names: {verb}-{variant} (e.g. commit-save, dismiss-outside).

6. La diferencia en una frase

Morfo declara, SomaRuntime transcribe, Provider aporta, Effects sincronizan, Sema emite, ADom aplica, Eidos lee.

Siete piezas, siete responsabilidades, ninguna invade a la siguiente.


7. Orden de lectura sugerido

  1. docs/CANON.md — canon semántico: el vocabulario (familias, intents, verbs, composición, canales) anclado al libro + código. La fuente de verdad que el resto enlaza.
  2. morfo/README.md — declaración, archetypes, regla 2-de-3
  3. sema/README.md — emit contract, verbs canónicos, canales
  4. eidos/README.md — qué consume eidos del DOM + wrappers
  5. soma/SOMA_ARCHITECTURE.md — runtime que transcribe morfo
  6. soma/COMPONENT_GUIDE.md — guía operativa para crear/migrar componentes
  7. src/arts/adom/README.md — dom.apply
  8. src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md — convenciones doctrinales del API

Powered by TurnKey Linux.