diff --git a/src/uix/active-uix/README.md b/src/uix/active-uix/README.md index 96c2b7573..125fcbb92 100644 --- a/src/uix/active-uix/README.md +++ b/src/uix/active-uix/README.md @@ -1,72 +1,79 @@ # ActiveUix -`active-uix` es la raiz de composicion de UIX. Su responsabilidad no es ser -otra capa de comportamiento, sino entregar a `morfo`, `soma`, `sema` y `eidos` -los servicios minimos que necesitan sin que los componentes conozcan -`ActiveApp` directamente. +`active-uix` es la raíz de composición de UIX. Su responsabilidad no es ser otra +capa de comportamiento, sino entregar a `morfo`, `soma`, `sema` y `eidos` los +servicios mínimos que necesitan, sin que los componentes conozcan `ActiveApp` +directamente. -## Boot paths actuales +> **Arquitectura de conjunto**: [`../active_architecture.md`](../active_architecture.md). +> **Contrato ejecutable**: [`contracts.ts`](../contracts.ts), validado en +> [`contracts.test.ts`](../contracts.test.ts). -`createActiveUix(options)` compone un runtime standalone: +## Dos modos de arranque + +`createActiveUix(options)` — **standalone**. Compone un runtime propio: - crea `logger`, `timers`, `bus` y `prefs`; -- el `bus` usa `createSvelteEngineBus({ logger, clock: timers.clock })`, - igual que `ActiveApp`, para que los listeners corran bajo `untrack`; -- crea `langs`, `dom` o `disabledDom`, `clipboard`, `format` y `events` - segun opciones; -- conserva `portal` como target generico de portales para que cada capa lo - adapte a su API sin acoplar `active-uix` a esa capa; -- registra traducciones comunes y `morfo.translations`. - -`attachActiveUix(app, options)` se adjunta a un `ActiveApp` externo: - -- reutiliza `app.prefs`, `app.langs`, `app.dom`, `app.clipboard` y - `app.format` si existen; -- exige `app.langs` y `app.dom`; si faltan, lanza error de configuracion; -- no vuelve a suscribir `langs` a `prefs.language`: esa conexion pertenece - a `defineActiveLangs` dentro de `ActiveApp`; -- registra traducciones comunes y `morfo.translations`; +- el `bus` usa `createSvelteEngineBus({ logger, clock: timers.clock })`, igual que + `ActiveApp`, para que los listeners corran bajo `untrack` y no creen + dependencias reactivas accidentales; +- crea `langs`, `dom` (o `disabledDom` si `dom:false`), `clipboard`, `format` y + `events` según opciones; +- conserva `portal` como target genérico de portales, para que cada capa lo + adapte a su API (`portalTo`) sin acoplar `active-uix` a esa capa; +- registra las traducciones comunes y las `morfo.translations`. + +`attachActiveUix(app, options)` — **attach** a un `ActiveApp` externo: + +- reutiliza `app.prefs`, `app.langs`, `app.dom`, `app.clipboard` y `app.format` + cuando existen; +- **exige** `app.langs` y `app.dom`; si faltan, lanza error de configuración; +- no vuelve a suscribir `langs` a `prefs.language` (esa conexión pertenece a + `defineActiveLangs` dentro de `ActiveApp`); +- registra las traducciones comunes y las `morfo.translations`; - no posee el lifecycle del `app`. -## Contratos actuales - -| Modulo | Minimo requerido | Opcional | Fallback si falta | Error si falta | -| ---------------------- | ------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | -| `ActiveUix` standalone | `langs` config | `clipboard:false`, `format`, `events`, `portal`, `dom:false` | crea `prefs`, core services y, si `dom:false`, `disabledDom` local | falta config `langs` | -| `ActiveUix` attach | `ActiveApp` core + services `langs`, `dom` | `app.clipboard`, `app.format`, app event engine, `portal` | ninguno para servicios requeridos | falta `langs` o `dom` en app; getter explicito falla si se pide un servicio opcional ausente | -| `SomaRuntime` | `dom` desde `ActiveUix` | event engine, `langs`, `format` | ninguno propio | morfo/event/part inexistente | -| `Sema` directo | `dom` o `projector` si `visual` esta activo | `sound`, `haptic`, `visual:false` | ninguno propio de servicios UIX | `SemaConfigError` sin `dom/projector` y con visual activo | -| `Eidos` | `dom` si `applyDom` | `langs`, `format`, `prefs`, mode/density sources | `applyDom:false` permite render/serializar sin DOM | falta `dom` con `applyDom` activo; componentes visuales fallan si leen un servicio no inyectado | -| `ADom` directo | target/window/document del caller | breakpoints/window | `disabledDom` solo si el caller lo pide explicitamente | errores propios de ADom si no hay DOM real | - -## Decisiones cerradas y riesgos abiertos - -1. `dom:false` solo degrada en standalone: `ActiveUix` expone un - `disabledDom` local y no deja a Sema/events caer a escrituras directas. -2. En attach mode no hay creacion compensatoria: si la app no tiene `dom`, - `attachActiveUix(app)` falla. -3. Standalone usa el mismo bus Svelte-aware que `ActiveApp`. -4. Attach mode no duplica la suscripcion `prefs.language -> langs`. -5. `langs` no es `locale`: `prefs.language` alimenta traducciones; - `prefs.locale` alimenta formatos. -6. `clipboard` es un servicio de capacidad, no DOM visual. Standalone lo - crea salvo `clipboard:false`; attach lo consume de `app.clipboard` si una - capa lo pide y falla con error explicito si no fue declarado. -7. `prefs.direction` es la preferencia efectiva de direccion; `html[dir]` es - solo su proyeccion DOM. -8. Los wrappers/componentes no deben ser el lugar donde se resuelvan estas - politicas. Primero se cierra el contrato de la raiz activa. -9. El antiguo artefacto `frontend` fue retirado. `ActiveUix` no proyecta - preferencias al DOM: `arts/prefs` proyecta lo transversal y `ActiveEidos` - proyecta lo visual. -10. `ActiveUix` no crea ni conoce `Soma` ni `Eidos`: `Soma.create(...)` crea - su scope y `Soma.runtime(...)`; `ActiveEidos.create(...)` crea el scope - visual si la app necesita CSS runtime. - -## Boot de una shell UIX - -Una shell que usa componentes visuales debe cablear tres piezas de forma -explicita: +## Contratos mínimos por módulo + +> Fuente ejecutable: [`contracts.ts`](../contracts.ts). + +| Módulo | Mínimo requerido | Opcional | Fallback si falta | Error si falta | +| --- | --- | --- | --- | --- | +| `ActiveUix` standalone | `langs` config | `clipboard:false`, `format`, `events`, `portal`, `dom:false` | crea `prefs`, core services y, si `dom:false`, `disabledDom` local | falta config `langs` | +| `ActiveUix` attach | `ActiveApp` core + servicios `langs`, `dom` | `app.clipboard`, `app.format`, motor de eventos, `portal` | ninguno para servicios requeridos | falta `langs` o `dom` en app; el getter de un servicio opcional ausente falla explícito | +| `SomaRuntime` | `dom` desde `ActiveUix` | motor de eventos, `langs`, `format` | ninguno propio | morfo/event/part inexistente | +| `Sema` directo | `dom` o `projector` si `visual` activo | `sound`, `haptic`, `visual:false` | ninguno propio de servicios UIX | `SemaConfigError` sin `dom/projector` con visual activo | +| `Eidos` | `dom` si `applyDom` | `langs`, `format`, `prefs`, fuentes mode/density | `applyDom:false` permite render/serializar sin DOM | falta `dom` con `applyDom` activo | +| `ADom` directo | target/window/document del caller | breakpoints/window | `disabledDom` solo si el caller lo pide | errores propios de ADom sin DOM real | + +## Reglas de ownership y degradación + +1. **Solo los composition roots crean servicios compartidos.** `morfo`, `soma`, + `sema`, `eidos` y los componentes nunca crean `dom`, `langs`, `prefs`, + `format`, `clipboard` ni equivalentes: los reciben de `ActiveUix`. +2. **`dom:false` solo degrada en standalone.** `ActiveUix` expone un + `disabledDom` local y se lo pasa también a `EngineSemantic`; Sema/events no + caen a escrituras DOM directas. En attach no hay creación compensatoria: si la + app no tiene `dom`, `attachActiveUix(app)` falla. +3. **`langs` no es `locale`.** `prefs.language` alimenta las traducciones; + `prefs.locale` alimenta los formatos. No se mezclan. +4. **`clipboard` es un servicio de capacidad, no DOM visual.** Standalone lo crea + salvo `clipboard:false`; attach lo consume de `app.clipboard` cuando una capa + lo pide, y falla con error explícito si no se declaró. +5. **`prefs.direction` es la preferencia efectiva de dirección**; `html[dir]` es + solo su proyección DOM. +6. **`ActiveUix` no auto-proyecta preferencias al DOM.** `arts/prefs` proyecta lo + transversal (`dir`, `data-motion`, `data-sound`, `data-haptic`) vía + `createActivePrefsDomProjection`; `ActiveEidos` proyecta lo visual + (`data-theme`, `data-mode`, `data-density`). El artefacto `frontend` fue + retirado y no debe reaparecer como fuente de `locale`. +7. **`ActiveUix` no crea ni conoce `Soma` ni `Eidos`.** `Soma.create(...)` crea su + propio scope y `Soma.runtime(...)`; `ActiveEidos.create(...)` crea el scope + visual cuando la app necesita CSS runtime. + +## Arrancar una shell UIX + +Una shell que usa componentes visuales cablea tres piezas de forma explícita: ```ts const uix = createActiveUix({ langs, prefs: { schema } }); @@ -88,14 +95,6 @@ const eidos = ActiveEidos.create({ `prefsProjection` es el dueño de `dir`, `data-motion`, `data-sound` y `data-haptic`. `ActiveEidos` es el dueño de `data-theme`, `data-mode` y -`data-density`. No escribir `uix.prefs.setIntent('theme', ...)`: `theme` no -existe en el preset core de UIX y provocara `prefs::unknown_dimension`. - -## Regla de trabajo vigente - -Tras cerrar P1, siguen vigentes estas restricciones: - -- no tocar `src/uix/eidos/components/*`; -- no crear nuevas fachadas internas; -- no introducir `Engine*`/`Active*` por simetria decorativa; -- cualquier cambio debe justificar que contrato minimo aclara. +`data-density`. El modo claro/oscuro se pasa a `ActiveEidos.modeSource`, no a +`prefs.theme` — `theme` no forma parte del preset core de UIX y escribirlo +provoca `prefs::unknown_dimension`.