parent
26166b9e3c
commit
0a014ed9e5
@ -1,362 +0,0 @@
|
||||
# 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-14
|
||||
|
||||
Esta auditoria queda parcialmente cerrada por fases en la rama `active-uix`:
|
||||
|
||||
- `ActiveUix` ya no expone ni crea `Eidos`; `ActiveEidos` se crea aparte.
|
||||
- `ActiveEidos` no crea `ActiveDom` propio y falla si necesita DOM real y no
|
||||
se le entrega una superficie valida.
|
||||
- `Provider` abstract class fue eliminada; queda solo el contrato de opciones.
|
||||
- `Morfo.langs` se renombro a `Morfo.translations`.
|
||||
- `VisualChannel` posee la proyeccion `data-event-*`; `EngineSemantic` solo
|
||||
orquesta prepare/handle/cleanup de canales.
|
||||
- `Sema` ya no transporta `motion`, `color` ni `presence` en el mapa canonico.
|
||||
- `SoundChannel` no registra listeners globales en el constructor.
|
||||
- `ActivePrefsDomProjection` escribe `dir`, `data-motion`, `data-sound` y
|
||||
`data-haptic`; `ActiveEidos` escribe `data-theme`, `data-mode` y
|
||||
`data-density`.
|
||||
- La proyeccion cross-modal no vive en `ActiveUix`: existe como
|
||||
`createActivePrefsDomProjection(...)` en `arts/prefs` y debe cablearla el
|
||||
composition root que quiera esos attrs globales.
|
||||
- La ruta `/uix` ya sigue el contrato final: no escribe `prefs.theme`; usa
|
||||
`createActivePrefsDomProjection(...)` para `dir/motion/sound/haptic` y
|
||||
`ActiveEidos.create({ theme:'base', modeSource, applyDom:true })` para
|
||||
`data-theme`, `data-mode` y `data-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)
|
||||
|
||||
```ts
|
||||
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)
|
||||
|
||||
```ts
|
||||
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)
|
||||
|
||||
```ts
|
||||
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`:
|
||||
|
||||
```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:
|
||||
|
||||
- **`SemaChannelSignatures`** solo debe tener: `sound`, `haptic`, y canales futuros (`a11y`, `voice`).
|
||||
- El `hold` visual debe vivir en `VisualChannel` como `hold: number | 'brief' | 'noticed'...`,
|
||||
resuelto desde `SEMA_MAP.families[*].hold` (ya existe), **no** desde `effective.motion.duration`.
|
||||
- `motion`, `color`, `presence` se eliminan del `EffectiveSignature`. Eidos reacciona a
|
||||
`data-event-family` + `data-event-intent` directamente 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:
|
||||
|
||||
```ts
|
||||
// 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**:
|
||||
|
||||
```ts
|
||||
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 `registerPart` fue eliminada. La superficie
|
||||
> canonica de registro queda en `SomaRuntime.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`:
|
||||
|
||||
```ts
|
||||
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-lint` sea 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. `contracts/` ya no se importa desde `index.css`;
|
||||
ni desde `tokens/index.css`; queda como archivo de consulta/tooling. Aun no se
|
||||
borran `tokens/` ni `themes/base/`, pero `themes/base/` tampoco se importa ya
|
||||
en runtime: sus aliases necesarios viven ahora en el CSS generado.
|
||||
`tokens/components/index.css` solo importa tokens de las recipes activas; el
|
||||
resto queda como material legacy. El siguiente paso es separar recipes estables
|
||||
de aliases retirables en `tokens/`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Preguntas para priorizar
|
||||
|
||||
1. **¿Prioridad inmediata?** ¿Cerrar primero la tabla de contratos minimos
|
||||
(`active_architecture.md` §102) antes de cualquier refactor?
|
||||
2. **¿Eliminacion de `Provider` legacy?** ¿Hay componentes que aun no se migraron al patron
|
||||
`SomaRuntime` directo? ¿Se puede deprecar `Provider` abstracto ya?
|
||||
3. **¿Sema visual cleanup?** ¿Aceptar que sea un cambio grande? Implica mover
|
||||
`motion/color/presence` fuera de `SEMA_MAP` y ajustar `VisualChannel`.
|
||||
4. **Eidos opcional:** resuelto. `ActiveUix` no expone el bridge visual;
|
||||
`ActiveEidos` se crea aparte.
|
||||
5. **¿Scopes `Soma`/`Eidos`?** ¿Eliminar ambas clases y dejar que los componentes lean
|
||||
`getActiveUix()` directamente, o mantener la separacion simbolica?
|
||||
|
||||
---
|
||||
|
||||
_Auditoria generada el 2026-05-12. Estado de referencia: rama `active-uix`._
|
||||
@ -1,67 +0,0 @@
|
||||
# UIX DOM Audit
|
||||
|
||||
Fecha: 2026-05-14. Rama: `active-uix`.
|
||||
|
||||
## Criterio
|
||||
|
||||
La regla arquitectonica queda:
|
||||
|
||||
> Las capas UIX no escriben atributos/estilos mutables directamente. Las
|
||||
> escrituras estructurales y visuales deben pasar por `ActiveDom`
|
||||
> (`dom.apply`, `dom.remove`, `dom.writeStyle`, `dom.removeStyle`,
|
||||
> `dom.writeNode`, `dom.writeText`, `dom.removeNode`) o por un proyector
|
||||
> construido con ese servicio.
|
||||
|
||||
Lecturas DOM (`querySelector`, `dataset`, `textContent` como lectura,
|
||||
`ResizeObserver.observe`) no violan esta regla por si solas.
|
||||
|
||||
## Superficies correctas
|
||||
|
||||
- `ActivePrefsDomProjection` proyecta prefs transversales (`dir`,
|
||||
`data-motion`, `data-sound`, `data-haptic`) con `dom.apply` cuando el
|
||||
composition root lo cablea.
|
||||
- `SomaRuntime` sincroniza attrs derivados de morfo con `dom.apply`.
|
||||
- `events`/`Sema` proyecta `data-event-*` solo desde
|
||||
`VisualChannel.prepare()` usando `DomSignalProjector`, que escribe con
|
||||
`dom.apply`; `EngineSemantic` no escribe attrs directamente.
|
||||
- `ActiveEidos` proyecta `data-theme`, `data-mode`, `data-density` e inserta
|
||||
CSS runtime con `dom.writeStyle` y `dom.removeStyle`.
|
||||
- La shell `/uix` usa `createActivePrefsDomProjection(...)` para attrs
|
||||
cross-modal y `ActiveEidos.create({ applyDom:true, modeSource })` para
|
||||
theme/mode/density. No escribe `prefs.theme`.
|
||||
- `TextSelection` ya no escribe `style.userSelect` directamente; ahora recibe
|
||||
`ActiveDom` y actualiza/restaura solo `user-select` y `-webkit-user-select`
|
||||
mediante `dom.apply`.
|
||||
- Announcers/live regions y descripciones ocultas de date/time ya no crean
|
||||
nodos ni escriben texto directamente; usan nodos gestionados de `ActiveDom`.
|
||||
|
||||
## Escrituras directas restantes
|
||||
|
||||
No quedan escrituras directas de producción detectadas por el barrido P1.
|
||||
Quedan lecturas DOM y fixtures de tests.
|
||||
|
||||
| Archivo | Qué escribe | Estado |
|
||||
| --------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------- |
|
||||
| `src/uix/soma/components/announce/global.svelte.ts` | Crea live regions globales y alterna texto. | Corregido via `ActiveDom.writeNode/writeText` |
|
||||
| `src/uix/soma/datetime/announcer.ts` | Crea live regions para date/time y escribe mensajes. | Corregido via `ActiveDom.writeNode/writeText` |
|
||||
| `src/uix/soma/datetime/helpers.ts` | Crea/elimina descripcion oculta `aria-describedby`. | Corregido via `ActiveDom.writeNode/removeNode` |
|
||||
|
||||
## Lecturas o efectos DOM no clasificadas como violacion
|
||||
|
||||
- `src/uix/soma/datetime/segments.ts` y `helpers.ts`: leen `dataset.segment`.
|
||||
- `src/uix/soma/layers/focus-scope.svelte.ts`: lee/focaliza `document.body`
|
||||
como parte del focus management. Es mutacion de foco, no escritura de attrs.
|
||||
- `src/uix/soma/components/virtual-list/virtual-list-provider.svelte.ts`:
|
||||
observa `document.documentElement` con `ResizeObserver`.
|
||||
- Tests bajo `src/uix/**`: manipulan `document.body` como fixture.
|
||||
|
||||
## Decision cerrada
|
||||
|
||||
Se eligio extender `ActiveDom` con nodos gestionados (`writeNode`,
|
||||
`writeText`, `removeNode`). No se crea una fachada nueva y no quedan islas DOM
|
||||
owned fuera del servicio.
|
||||
|
||||
Los eventos de cierre que apuntan a una parte desmontable deben pasar
|
||||
`fallbackTarget` cuando esa parte pueda no estar registrada. Popover centraliza
|
||||
ese patrón en `triggerClose(...)`: prefiere `content`, cae al botón/trigger que
|
||||
provocó el cierre y evita que `SomaRuntime` emita una señal visual sin target.
|
||||
Loading…
Reference in new issue