13 KiB
Auditoria Arquitectonica UIX — Kimi (2026-05-12)
Alcance
Auditoria de las capas ActiveUix, Morfo, Soma, Sema y Eidos. NO se analizaron los componentes internos de Soma ni Eidos.
Estado Codex 2026-05-13
Esta auditoria queda parcialmente cerrada por fases en la rama active-uix:
ActiveUixya no expone ni creaEidos;ActiveEidosse crea aparte.ActiveEidosno creaActiveDompropio y falla si necesita DOM real y no se le entrega una superficie valida.Providerabstract class fue eliminada; queda solo el contrato de opciones.Morfo.langsse renombro aMorfo.translations.VisualChannelposee la proyecciondata-event-*;EngineSemanticsolo orquesta prepare/handle/cleanup de canales.Semaya no transportamotion,colornipresenceen el mapa canonico.SoundChannelno registra listeners globales en el constructor.ActivePrefsDomProjectionescribedir,data-motion,data-soundydata-haptic;ActiveEidosescribedata-theme,data-modeydata-density.- La auditoria DOM P1 queda documentada en
src/uix/dom_audit.md.
Pendiente vivo: terminar la generacion/retirada de CSS legacy de Eidos.
createAttrs queda como selector helper, registerContract ya no se usa desde
Soma, y arts/frontend queda como servicio legacy opt-in fuera del boot UIX.
1. Errores arquitectonicos criticos
1.1. Sema viola su propia frontera: conoce DOM y concerns visuales
Archivo: src/uix/sema/engine.ts (lineas 113, 202, 232)
Problema: EngineSemantic crea un DomSignalProjector por defecto y es el quien escribe
data-event-* en el DOM durante emit. La doctrina dice que Sema es "registry + dispatch"
y que VisualChannel materializa la senal, pero en el codigo el engine proyecta antes
de llamar a los canales. El VisualChannel solo hace sleep(hold); no controla la proyeccion.
Impacto: El canal visual no es autonomo. El engine esta acoplado a la idea de "escribir en el DOM", lo que rompe la regla Sema no conoce DOM.
Fix propuesto: Mover projector.project() y handle.cleanup() dentro de VisualChannel.handle().
El engine solo despacha la senal; el canal visual gestiona su propio plano DOM.
1.2. Acoplamiento historico de Eidos/Semantic en ActiveUix
Estado 2026-05-13: resuelto por retirada de ambos acoplamientos. ActiveUix
ya no expone eidos ni el alias semantic; el motor perceptivo se expone como
events?: EngineSemantic y ActiveEidos.create(...) crea el scope visual.
ActiveEidos solo es necesario para CSS runtime o authoring de configuracion.
Archivo: src/uix/active-uix/types.ts (linea 115), src/uix/active-uix/active-uix.svelte.ts (lineas 419-425)
Problema historico: En types.ts, semantic: EngineSemantic | undefined
(opcional), pero el bridge visual era obligatorio. Sin embargo,
createActiveUix({ eidos: false }) era valido, lo que hacia que leer el
bridge visual fallase en runtime. Esto contradecia la degradacion graceful que
se aplicaba al motor perceptivo.
Impacto historico: Un componente que intentaba leer el bridge visual en modo headless (sin CSS) crasheaba; uno que leia el motor perceptivo simplemente saltaba la senal.
Fix aplicado: retirar ambos acoplamientos de ActiveUix: events queda
como motor perceptivo opcional y ActiveEidos se crea aparte cuando la app lo
necesita.
1.3. ActiveEidos puede crear su propio ActiveDom sin permiso de ActiveUix
Archivo: src/uix/eidos/active-eidos.svelte.ts (lineas 258-263)
Problema: ActiveEidos.#getDom() llama createActiveDom() si no recibe dom.
Esto crea un segundo servicio DOM con sus propios listeners de resize/scroll,
rompiendo el principio de que ActiveUix es el unico dueno del DOM.
Impacto: En modo dom: false o attach sin dom, ActiveEidos silenciosamente instancia
DOM real en lugar de usar el disabledDom compartido.
Fix propuesto: ActiveEidos debe recibir siempre un dom explicito (incluso si es disabledDom).
Si falta, debe lanzar error o degradar a no-op, nunca crear uno propio.
1.4. Provider abstract class sigue vivo a pesar de que la doctrina dice "dropped"
Archivo: src/uix/soma/provider/provider.svelte.ts
Documento: src/uix/soma/SOMA_ARCHITECTURE.md §15
Problema: SOMA_ARCHITECTURE.md dice "Provider inheritance dropped", pero
provider.svelte.ts sigue exportando abstract class Provider<S>.
Esto genera dos patrones conviviendo: el legacy (herencia) y el nuevo (SomaRuntime directo).
Impacto: Confusion para nuevos desarrolladores. Doble mantenimiento.
Fix propuesto: Depreciar/Eliminar Provider. Todos los componentes deben migrar a
SomaRuntime + funciones helper puras.
1.5. Duplicacion de logica de prefs entre active-uix/prefs.ts y eidos/active-eidos.svelte.ts
Archivo: src/uix/active-uix/prefs.ts (lineas 55-69, 71-78)
Archivo: src/uix/eidos/active-eidos.svelte.ts (lineas 364-375)
Problema: readPrefsSlot, createPrefsPreferenceSource, readActiveUixPrefsSlot y
createLocaleSourceFromPrefs hacen esencialmente lo mismo: leer slots de ActivePrefs
de forma defensiva.
Impacto: DRY violation. Si cambia la forma de ActivePrefs, hay que tocar dos sitios.
Fix propuesto: Extraer un prefs-bridge.ts compartido en $libs/prefs o $active-uix.
1.6. Morfo incluia langs — mezcla de contrato estructural con datos de i18n
Archivo: src/uix/morfo/types.ts (linea 604), src/uix/morfo/registry.ts (lineas 33-47)
Actualizacion 2026-05-13: el campo publico se ha renombrado a
Morfo.translations y el registro a connectMorfoTranslations, para distinguir
catalogo declarativo de servicio runtime langs.
Problema original: Morfo.langs era un catalogo de traducciones embebido en el contrato.
Morfo deberia ser "DNA" puramente estructural. Las traducciones son volatiles y no
forman parte del contrato DOM.
Impacto: Cada vez que cambia una traduccion, el fingerprint del morfo cambia,
disparando re-registros innecesarios en connectMorfoTranslations.
Fix propuesto: Separar MorfoLangCatalog en un registry aparte. El morfo puede tener
una referencia (langCatalogId), pero no el arbol de strings.
2. Optimizaciones identificadas
2.1. readBindings en SomaRuntime genera garbage objects en cada tick
Archivo: src/uix/soma/runtime.svelte.ts (lineas 213-239)
function readBindings(reg, sources): MorfoBindings {
const states = {};
const props = {};
const parts = {};
// ... for-in + asignacion en cada $effect
}
En un $effect reactivo, esto corre en cada cambio de estado. Para 20 partes, son ~60 objetos
nuevos por frame.
Optimizacion: Usar un objeto de bindings mutable reutilizado (pool) o cambiar readBindings
a lectura lazy por plan, evaluando solo las fuentes que el evalAttrPlan necesita.
2.2. EngineSemantic clona SEMA_MAP en cada instancia
Archivo: src/uix/sema/engine.ts (linea 107)
this.map = applyMapOverrides(SEMA_MAP, opts.overrides?.runtime);
En tests con muchas instancias, deep-clone del mapa perceptivo completo es costoso.
Optimizacion: Hacer SEMA_MAP inmutable y aplicar overrides como un layer de proxy/lookup
en tiempo de resolucion, no en construccion.
2.3. SoundChannel registra listeners globales en el constructor incluso si nunca se usa
Archivo: src/uix/sema/chans/sound.ts (eager-init)
Eager-init de AudioContext es correcto para la politica de autoplay, pero registrar un
listener capture-phase en document al crear EngineSemantic es side-effect agresivo.
Optimizacion: Mover el registro del listener a la primera llamada a emit() que requiera
sonido, o usar un singleton lazy para SoundChannel.
2.4. stableStringify en registerMorfo para fingerprint de langs
Archivo: src/uix/morfo/registry.ts (lineas 97-109)
function fingerprintLangNode(node: LangNode): string {
return stableStringify(node); // JSON-like recursivo
}
Esto se ejecuta en cada registerMorfo. Para catalogos grandes, es O(n) en cada registro.
Optimizacion: Usar WeakMap + referencia de objeto si los catalogos son estaticos (lo son),
o cachear fingerprints por identidad de objeto.
3. Propuesta de mejor arquitectura
3.1. Contrato minimo tipado por capa
El documento active_architecture.md admite que falta la tabla de contratos.
Propongo definirla como tipos en src/uix/contracts.ts:
export interface UixServiceContract {
langs: ActiveLangs; // obligatorio
dom: ActiveDom; // obligatorio (puede ser disabledDom)
events?: EventEngineEmitter; // ornamental
eidos?: ActiveEidos; // ornamental
format?: ActiveFormat; // ornamental
prefs: ActivePrefs; // obligatorio
}
ActiveUix deberia implementar esta interfaz y createActiveUix deberia validar contra ella
en lugar de logica if/else dispersa.
3.2. Sema debe purgarse de dimensions visuales (motion, color, presence)
La doctrina de Phase 5 del codex refactor es correcta pero incompleta. Propongo:
SemaChannelSignaturessolo debe tener:sound,haptic, y canales futuros (a11y,voice).- El
holdvisual debe vivir enVisualChannelcomohold: number | 'brief' | 'noticed'..., resuelto desdeSEMA_MAP.families[*].hold(ya existe), no desdeeffective.motion.duration. motion,color,presencese eliminan delEffectiveSignature. Eidos reacciona adata-event-family+data-event-intentdirectamente en CSS.
Esto reduce drasticamente el acoplamiento Sema→Eidos.
3.3. Unificar scopes redundantes
Estado 2026-05-13: el adaptador Eidos ya fue eliminado. ActiveEidos
queda como scope visual real y no existe un engine visual separado. Soma
sigue como scope headless mientras se decide si debe conservarse o aplanarse.
La deuda historica era:
Soma y el antiguo adaptador visual repetian una superficie casi identica:
uix, dom, events, langs y helpers de capa. Esa duplicacion ya no debe
volver.
Propuesta viva: eliminar solo los adaptadores que no aporten contrato
propio. Los componentes no deben leer la superficie completa de ActiveUix;
deben consumir scopes o vistas estrechas:
// En un provider headless
const uix = getActiveUix();
const runtime = uix.runtime(morfo, sources);
// En un wrapper visual
const uix = getActiveUix();
const eidos = ActiveEidos.require();
Si se quiere evitar que los componentes toquen ActiveUix completo, usar interfaces de capa:
export interface SomaServices {
dom: ActiveDom;
events?: EventEngineEmitter;
langs: ActiveLangs;
}
export interface EidosServices {
dom: ActiveDom;
eidos?: ActiveEidos;
langs: ActiveLangs;
}
Esto elimina una clase entera de indireccion y la dependencia a runed/Context extra.
3.4. SomaRuntime desacoplado de Svelte
Nota 2026-05-13: la API publica
registerPartfue eliminada. La superficie canonica de registro queda enSomaRuntime.part(...); esta observacion queda como contexto historico de la auditoria.
registerPart usa $effect directamente. Para testear el runtime sin un entorno Svelte,
o para reutilizarlo en otro framework, propongo inyectar un Scheduler:
export interface EffectScheduler {
run(fn: () => void): () => void; // return cleanup
}
export function createSomaRuntime(
morfo: Morfo,
sources: SomaRuntimeSources,
scheduler: EffectScheduler = svelteScheduler
): SomaRuntime { ... }
Esto permite testear con nanostores, mobx, o incluso manual callbacks.
3.5. Eliminar CSS legacy (contracts/, tokens/, themes/base/)
Estos directorios son deuda de migracion. ActiveEidos ya puede generar todo el CSS estatico
y de theme desde la configuracion viva. Mantenerlos:
- Duplica fuentes de verdad (engine vs CSS manual).
- Hace que
eidos-lintsea necesario. - Incrementa bundle size.
Estado 2026-05-13: existe el primer artefacto generado,
src/uix/eidos/generated/base.css, producido con npm run generate:eidos-css
desde EidosConfig base. Aun no se borran contracts/, tokens/ ni
themes/base/ porque varias recipes y aliases historicos dependen de ellos.
El siguiente paso es separar recipes estables de aliases/contratos retirables.
4. Preguntas para priorizar
- ¿Prioridad inmediata? ¿Cerrar primero la tabla de contratos minimos
(
active_architecture.md§102) antes de cualquier refactor? - ¿Eliminacion de
Providerlegacy? ¿Hay componentes que aun no se migraron al patronSomaRuntimedirecto? ¿Se puede deprecarProviderabstracto ya? - ¿Sema visual cleanup? ¿Aceptar que sea un cambio grande? Implica mover
motion/color/presencefuera deSEMA_MAPy ajustarVisualChannel. - Eidos opcional: resuelto.
ActiveUixno expone el bridge visual;ActiveEidosse crea aparte. - ¿Scopes
Soma/Eidos? ¿Eliminar ambas clases y dejar que los componentes leangetActiveUix()directamente, o mantener la separacion simbolica?
Auditoria generada el 2026-05-12. Estado de referencia: rama active-uix.