User decision (2026-07-02): the corpus stops being a MAP over dispersed layer docs — the layer reference MOVES into docs/ with book order, in English, superseding the kickoff-era 'hybrid structure' decision. The 189 component READMEs stay in-place (E5, linked). - docs/process/PLAN-docs-book.md: the phase-7 plan — target tree (architecture/ canon/ theming/ rfcs/ guides/ decisions/), the per-doc migration pattern (translate -> Write new path -> stub at old path -> link sweep -> docs:check -> commit per batch), batches F7.1-F7.7 with volumes, and the known risks (sN citations get a map in the stub; the legacy RFC renames finally become safe because the stub keeps the old name alive for provenance citations). - Pilot migrated end-to-end: docs/architecture/active-uix.md (English, frontmatter, links repointed) with a thin stub at src/uix/active-uix/README.md; docs/README map, glossary and active_architecture s0 repointed. Validates the pattern for F7.2+. docs:check: 0 errors (244 docs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>menubar-v4-safe
parent
16caa0eb53
commit
c4fe43abc6
@ -0,0 +1,110 @@
|
||||
---
|
||||
title: ActiveUix — the composition root
|
||||
type: reference
|
||||
audience: human + agent
|
||||
authority: E1 architecture — how UIX boots, which services it owns, and how it degrades
|
||||
status: current
|
||||
source: migrated from src/uix/active-uix/README.md (2026-07-02, docs-book F7.1)
|
||||
---
|
||||
|
||||
# ActiveUix
|
||||
|
||||
`active-uix` is UIX's composition root. Its responsibility is not to be another
|
||||
behavior layer, but to hand `morfo`, `soma`, `sema` and `eidos` the minimal
|
||||
services they need — without components ever knowing `ActiveApp` directly.
|
||||
|
||||
> **Whole-system architecture**: [`src/uix/active_architecture.md`](../../src/uix/active_architecture.md).
|
||||
> **Executable contract**: [`src/uix/contracts.ts`](../../src/uix/contracts.ts), validated by
|
||||
> [`contracts.test.ts`](../../src/uix/contracts.test.ts).
|
||||
|
||||
## Two boot modes
|
||||
|
||||
`createActiveUix(options)` — **standalone**. Composes its own runtime:
|
||||
|
||||
- creates `logger`, `timers`, `bus` and `prefs`;
|
||||
- the `bus` uses `createSvelteEngineBus({ logger, clock: timers.clock })`, same
|
||||
as `ActiveApp`, so listeners run under `untrack` and never create accidental
|
||||
reactive dependencies;
|
||||
- creates `langs`, `dom` (or `disabledDom` when `dom:false`), `clipboard`,
|
||||
`format` and `events` according to the options;
|
||||
- keeps `portal` as a generic portal target, so each layer adapts it to its own
|
||||
API (`portalTo`) without coupling `active-uix` to that layer;
|
||||
- registers the common translations and the per-component catalogs from
|
||||
`src/uix/langs/components/*`.
|
||||
|
||||
`attachActiveUix(app, options)` — **attach** to an external `ActiveApp`:
|
||||
|
||||
- reuses `app.prefs`, `app.langs`, `app.dom`, `app.clipboard` and `app.format`
|
||||
when they exist;
|
||||
- **requires** `app.langs` and `app.dom`; if either is missing it throws a
|
||||
configuration error;
|
||||
- does not re-subscribe `langs` to `prefs.language` (that connection belongs to
|
||||
`defineActiveLangs` inside `ActiveApp`);
|
||||
- registers the common translations and the per-component catalogs;
|
||||
- does not own the `app`'s lifecycle.
|
||||
|
||||
## Minimum contracts per module
|
||||
|
||||
> Executable source: [`src/uix/contracts.ts`](../../src/uix/contracts.ts).
|
||||
|
||||
| Module | Minimum required | Optional | Fallback when missing | Error when missing |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `ActiveUix` standalone | `langs` config | `clipboard:false`, `format`, `events`, `portal`, `dom:false` | creates `prefs`, core services and, with `dom:false`, a local `disabledDom` | missing `langs` config |
|
||||
| `ActiveUix` attach | `ActiveApp` core + `langs`, `dom` services | `app.clipboard`, `app.format`, event engine, `portal` | none for required services | missing `langs` or `dom` on the app; the getter of an absent optional service fails explicitly |
|
||||
| `SomaRuntime` | `dom` from `ActiveUix` | event engine, `langs`, `format` | none of its own | nonexistent morfo/event/part |
|
||||
| `Sema` direct | `dom` or `projector` when `visual` is active | `sound`, `haptic`, `visual:false` | none of its own for UIX services | `SemaConfigError` without `dom/projector` while visual is active |
|
||||
| `Eidos` | `dom` when `applyDom` | `langs`, `format`, `prefs`, mode/density sources | `applyDom:false` allows render/serialize without DOM | missing `dom` with `applyDom` active |
|
||||
| `ADom` direct | caller's target/window/document | breakpoints/window | `disabledDom` only when the caller asks for it | ADom's own errors without a real DOM |
|
||||
|
||||
## Ownership and degradation rules
|
||||
|
||||
1. **Only composition roots create shared services.** `morfo`, `soma`, `sema`,
|
||||
`eidos` and components never create `dom`, `langs`, `prefs`, `format`,
|
||||
`clipboard` or equivalents: they receive them from `ActiveUix`.
|
||||
2. **`dom:false` only degrades in standalone.** `ActiveUix` exposes a local
|
||||
`disabledDom` and passes it to `EngineSemantic` too; Sema/events never fall
|
||||
back to direct DOM writes. In attach there is no compensating creation: if
|
||||
the app has no `dom`, `attachActiveUix(app)` fails.
|
||||
3. **`langs` is not `locale`.** `prefs.language` feeds translations;
|
||||
`prefs.locale` feeds formats. They never mix.
|
||||
4. **`clipboard` is a capability service, not visual DOM.** Standalone creates
|
||||
it unless `clipboard:false`; attach consumes it from `app.clipboard` when a
|
||||
layer asks for it, and fails with an explicit error if it was not declared.
|
||||
5. **`prefs.direction` is the effective direction preference**; `html[dir]` is
|
||||
only its DOM projection.
|
||||
6. **`ActiveUix` does not auto-project preferences onto the DOM.** `arts/prefs`
|
||||
projects the cross-modal attrs (`dir`, `data-motion`, `data-sound`,
|
||||
`data-haptic`) via `createActivePrefsDomProjection`; `ActiveEidos` projects
|
||||
the visual ones (`data-theme`, `data-mode`, `data-density`). The `frontend`
|
||||
artifact was retired and must not reappear as a `locale` source.
|
||||
7. **`ActiveUix` neither creates nor knows `Soma` or `Eidos`.** `Soma.create(...)`
|
||||
creates its own scope and `Soma.runtime(...)`; `ActiveEidos.create(...)`
|
||||
creates the visual scope when the app needs runtime CSS.
|
||||
|
||||
## Booting a UIX shell
|
||||
|
||||
A shell that uses visual components wires three pieces explicitly:
|
||||
|
||||
```ts
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
`prefsProjection` owns `dir`, `data-motion`, `data-sound` and `data-haptic`.
|
||||
`ActiveEidos` owns `data-theme`, `data-mode` and `data-density`. Light/dark
|
||||
mode goes to `ActiveEidos.modeSource`, not to `prefs.theme` — `theme` is not
|
||||
part of UIX's core prefs preset and writing it raises
|
||||
`prefs::unknown_dimension`.
|
||||
@ -1,101 +1,12 @@
|
||||
# ActiveUix
|
||||
|
||||
`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.
|
||||
`active-uix` is UIX's composition root: it hands `morfo` / `soma` / `sema` /
|
||||
`eidos` the minimal services they need, without components knowing `ActiveApp`.
|
||||
|
||||
> **Arquitectura de conjunto**: [`../active_architecture.md`](../active_architecture.md).
|
||||
> **Contrato ejecutable**: [`contracts.ts`](../contracts.ts), validado en
|
||||
> [`contracts.test.ts`](../contracts.test.ts).
|
||||
**The reference moved to the docs corpus:**
|
||||
[`docs/architecture/active-uix.md`](../../../docs/architecture/active-uix.md)
|
||||
— boot modes (`createActiveUix` / `attachActiveUix`), the minimum contracts per
|
||||
module, the ownership & degradation rules, and the shell wiring example.
|
||||
|
||||
## 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` 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 los catálogos por componente de
|
||||
`src/uix/langs/components/*`.
|
||||
|
||||
`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 los catálogos por componente;
|
||||
- no posee el lifecycle del `app`.
|
||||
|
||||
## 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 } });
|
||||
|
||||
setActiveUix(uix);
|
||||
Soma.create();
|
||||
|
||||
const prefsProjection = createActivePrefsDomProjection({
|
||||
prefs: uix.prefs,
|
||||
dom: uix.dom
|
||||
});
|
||||
|
||||
const eidos = ActiveEidos.create({
|
||||
theme: 'base',
|
||||
modeSource,
|
||||
applyDom: true
|
||||
});
|
||||
```
|
||||
|
||||
`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`. 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`.
|
||||
Quick pointers: executable contract in [`contracts.ts`](../contracts.ts),
|
||||
validated by [`contracts.test.ts`](../contracts.test.ts).
|
||||
|
||||
Loading…
Reference in new issue