--- 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**: [`architecture/active-architecture.md`](./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` — and the prefs schema ALWAYS carries the four visual axes: `uixVisualPrefsDimensions()` is merged under whatever `prefs.schema` the app passes, so the app may redefine `mode` / `theme` / `density` / `scaling` but never omit them; - 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; - **does not merge the visual axes.** The prefs engine already exists and belongs to the app, so nothing can be composed into it after the fact: an attaching app spreads `uixVisualPrefsDimensions()` (from `$active-uix`) into its own prefs schema. Otherwise eidos warns once per absent slot (`eidos.prefs`) and falls back to its own default — `mode: 'light'` on a dark-mode machine included; - **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. **`disabledDom` is deliberately MIXED** (AUX-2, 2026-07-11): write-shaped calls (`apply`, `listen`, …) no-op silently, but reads that MUST return a value — `measure`, `raf`, `getDocument` / `getWindow` — **throw `ActiveUixDomDisabledError`**; a component that measures under `dom:false` dies loudly instead of computing from a phantom layout. 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({ applyDom: true }); ``` `prefsProjection` owns `dir`, `data-motion`, `data-sound` and `data-haptic`. `ActiveEidos` owns `data-theme`, `data-mode` and `data-density`. Light/dark mode is a prefs dimension like the rest (2026-09-14): the toggle writes `uix.prefs.setIntent('mode', 'dark')` and eidos, which reads the slot, re-applies the attributes.