docs(book): F7.1 — book-corpus plan + docs/architecture skeleton + active-uix pilot

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
dev 3 months ago
parent 16caa0eb53
commit c4fe43abc6

@ -69,7 +69,7 @@ invented vocabulary (morfo, archetype, hold, TSC, …) one line each.
| [`soma/README.md`](../src/uix/soma/README.md) · [`SOMA_ARCHITECTURE.md`](../src/uix/soma/SOMA_ARCHITECTURE.md) | Headless behavior — README onboards, ARCHITECTURE is the deep reference |
| [`sema/README.md`](../src/uix/sema/README.md) | Perceptual engine + channels (sound/haptic) + cascade |
| [`eidos/README.md`](../src/uix/eidos/README.md) | The visual layer |
| [`active-uix/README.md`](../src/uix/active-uix/README.md) | Composition root (boot modes) |
| [`architecture/active-uix.md`](./architecture/active-uix.md) | Composition root (boot modes) — first chapter migrated into the book tree ([`docs/process/PLAN-docs-book.md`](./process/PLAN-docs-book.md)) |
| [`arts/README.md`](../src/arts/README.md) | Runtime artifacts (`Engine*`/`Active*`) |
### E2 — Canon

@ -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`.

@ -26,7 +26,7 @@ New here? Start at [`docs/README.md`](./README.md).
| **arts** | Runtime artifacts: the `Engine*` / `Active*` services (auth, cache, http, format, langs, dom, motion, …). → [`arts/README`](../src/arts/README.md) |
| **libs** | Pure, zero-dependency helpers (`$libs/days`, `$libs/dom`, `$reactive`, …). |
| **svrs** | Server-authoritative engines (`$svrs/auth`, `$svrs/perm`, `$svrs/cache`). |
| **active-uix** | The composition root that wires the layers — `createActiveUix` (standalone) or `attachActiveUix` (attach to an app). → [`active-uix/README`](../src/uix/active-uix/README.md) |
| **active-uix** | The composition root that wires the layers — `createActiveUix` (standalone) or `attachActiveUix` (attach to an app). → [`architecture/active-uix`](./architecture/active-uix.md) |
| **ActiveDom / `$adom`** | The single reactive DOM service: the only sanctioned surface for managed DOM writes, listeners, queries, focus and scroll. |
## Morfo vocabulary

@ -46,6 +46,15 @@ permanente/efímero, drift, dos idiomas.
## PENDIENTE
### Fase 7 — Corpus-libro en `docs/` (ACTIVA, decisión de usuario 2026-07-02)
El usuario fijó la forma objetivo: **la referencia de capa SE MUEVE a `docs/`**
con orden de libro, en inglés (sustituye la "estructura híbrida" del kickoff);
los 189 READMEs de componente quedan fuera (E5 in-place). Plan completo por
tandas + árbol objetivo + patrón de migración (mover→traducir→stub→barrido→
docs:check): [`PLAN-docs-book.md`](./PLAN-docs-book.md). **F7.1 hecha**
(esqueleto + piloto `docs/architecture/active-uix.md` con stub en la ruta
vieja). Siguiente: F7.2 (architecture/, ~5.4k líneas es→en).
### Fase 6 — Reconciliación — **HECHA (2026-07-02)**
Las auditorías `fable_audit.md` + `fable-eidos-audit.md` (2026-07-01/02) verificaron
~14 conflictos doc↔doc y doc↔código + referencia mezclada con bitácora + espejos sin

@ -0,0 +1,116 @@
# PLAN — Corpus-libro en `docs/` (fase 7 del workstream docs)
> **Kickoff para sesión nueva**: *"Lee docs/process/PLAN-docs-book.md y continúa
> la tanda que toque."* Decisión de usuario (2026-07-02): el corpus deja de ser
> un mapa sobre docs dispersos — la referencia de capa SE MUEVE a `docs/` con
> orden de libro, **en inglés**. Los 189 READMEs de componente quedan FUERA
> (E5 in-place, enlazados). Sustituye la decisión "estructura híbrida" del
> kickoff original (CONTINUE-docs-corpus §Decisiones acordadas).
## Objetivo
Un corpus único, estructurado y sin ambigüedad bajo `docs/`: base directa del
libro y de la futura web de docs. Junto al código quedan **stubs finos**
(puntero + mini-resumen) para que ninguna cita por ruta se rompa y el que
navega el código encuentre la puerta.
## Reglas de trabajo (heredadas + nuevas)
- PROHIBIDO lanzar agentes/workflows. Responder en castellano; docs nuevos en inglés.
- NUNCA tocar `words/`, `palabras/`, `chronos/`, el libro
(`docs/Disenando_lo_que_ocurre_v2_3.md`), ni `eidos/MOTION_SERVICE_RFC.md` +
`chronos/SPEC-*` (foráneos/concurrentes — NO se mueven).
- Editar con Edit/Write (PowerShell solo `WriteAllLines` para reescrituras).
- Git: `git reset -q` → add solo lo propio → commit **por tanda**.
- `npm run docs:check` verde (0 errores) al cierre de CADA tanda.
- **Patrón de migración por doc** (validado por el piloto F7.1):
1. Traducir a inglés adaptando (reglas de `docs/authoring.md`: frontmatter,
timeless, link-don't-copy) — misma sustancia, no reescritura creativa.
2. `Write` en la ruta nueva del árbol objetivo.
3. La ruta vieja queda como **stub** (`status: moved`, puntero + 2-4 líneas
de orientación; si el doc era citado por `§N` desde código, el stub lleva
el mapa §N→sección nueva).
4. Barrer los links del corpus que apuntaban a la ruta vieja → ruta nueva
(el stub garantiza que los no barridos sigan resolviendo).
5. `docs:check` + commit.
## Árbol objetivo
```
docs/
README.md ← entrada: pasa de mapa a TOC del libro (se
actualiza en cada tanda)
getting-started.md · glossary.md · authoring.md · comparison.md
testing-and-tooling.md · building-a-component.md · CANON.md · decisions.md
← ya viven aquí; se quedan
architecture/
overview.md ← src/uix/README.md (la tesis)
active-architecture.md ← src/uix/active_architecture.md
morfo.md ← src/uix/morfo/README.md
soma.md ← src/uix/soma/README.md
soma-architecture.md ← src/uix/soma/SOMA_ARCHITECTURE.md
sema.md ← src/uix/sema/README.md
eidos.md ← src/uix/eidos/README.md
active-uix.md ← src/uix/active-uix/README.md (piloto F7.1)
canon/
tsc.md ← src/uix/eidos/TSC.md
recipe-contract.md ← src/uix/eidos/RECIPE_CONTRACT.md
theming/
reference.md ← src/uix/eidos/THEMING.md
guide.md ← THEMING_GUIDE.md · notes.md ← THEMING_NOTES.md
changelog.md ← THEMING_CHANGELOG.md
motion.md ← eidos-motion.md · motion-guide.md ← MOTION_GUIDE.md
channels.md ← CHANNELS_SYNTHESIS.md
rfcs/
rfc-color-model.md ← COLOR_MODEL_RFC.md (el rename legacy→
rfc-color-engine.md ← COLOR_ENGINE_RFC.md rfc-* por fin es
rfc-depth.md · rfc-shape.md · rfc-structure.md seguro: el stub en
rfc-scaling.md · rfc-typography.md la ruta vieja conserva
las ~30 citas provenance)
guides/
component-guide.md ← src/uix/soma/COMPONENT_GUIDE.md
completion-checklist.md ← src/uix/COMPONENT_COMPLETION_CHECKLIST.md
(actualizar la ruta en scripts/docs-check.ts I5)
demo-authoring.md ← web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md
component-audit.md ← web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md
decisions/
book-deviations.md ← src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md
guia-semantica-historica.md← src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md
(histórica; se mueve tal cual, en castellano —
es semilla, no referencia)
process/ ← queda (efímero)
```
**Fuera del libro (E5 in-place, enlazados desde el TOC)**: READMEs de
componente (189), `src/arts/*/README.md` (29), docs de libs/svrs,
`eidos/components/README.md` (patrón — candidato a guides/ en F7.5, decidir),
audits/fósiles ya gestionados.
## Tandas
| Tanda | Contenido | Volumen | Estado |
|---|---|---|---|
| **F7.1** | Plan + esqueleto + piloto `active-uix.md` (valida el patrón) | ~100 L | **HECHA 2026-07-02** |
| F7.2 | `architecture/` — overview, active-architecture, morfo, soma×2, sema, eidos | ~5.4k L (ES→EN el grueso) | pendiente |
| F7.3 | `theming/` + `canon/` — THEMING familia, TSC, RECIPE_CONTRACT, motion | ~3.1k L | pendiente |
| F7.4 | `rfcs/` — 7 RFCs (rename incluido; MOTION_SERVICE_RFC NO — foráneo) | ~3-4k L | pendiente |
| F7.5 | `guides/` — COMPONENT_GUIDE, checklist (tocar docs-check I5), demo guides | ~2.2k L | pendiente |
| F7.6 | `decisions/` — LIBRO_VARIACIONES, GUIA histórica; barrido final de links + TOC completo en README | ~1.5k L | pendiente |
| F7.7 | **CLAUDE.md fino** — hand-offs → process/, referencias → rutas del libro (archivo sensible: presentar diff antes de commitear) | — | pendiente |
Notas por tanda:
- La **traducción es→en va incluida** en cada movimiento (F7.2 es la cara: THEMING
ya quedó saneada en la fase 6; SOMA_ARCHITECTURE/sema/eidos READMEs son el grueso).
- Tras cada tanda, actualizar `docs/README.md` (el TOC) — es el índice del libro.
- `docs:check` ya vigila links y campos fantasma; considerar añadir invariante
"stub no crece" si aparece drift stub↔destino.
## Riesgos conocidos
- Citas `§N` desde código (THEMING §5/§25/§35, TSC, DESIGN_*): el stub lleva
mapa §N cuando el doc migre. Las citas por ruta sobreviven por el stub.
- `scripts/docs-check.ts` (I5) y cualquier tooling que lea rutas de docs se
actualizan EN LA MISMA tanda que mueve su doc.
- Traducir ≠ reescribir: sustancia idéntica; lo que huela a stale se marca con
`<!-- TODO(reconcile): ... -->` y se anota en el CONTINUE, no se "arregla"
silenciosamente en la traducción.

@ -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).

@ -45,8 +45,8 @@
Qué requiere cada módulo, qué es opcional, cómo degrada y cuándo falla. Las
reglas de ownership y degradación se enuncian, atemporales, en
[`active-uix/README.md`](./active-uix/README.md) §"Reglas de ownership y
degradación".
[`docs/architecture/active-uix.md`](../../docs/architecture/active-uix.md)
§"Ownership and degradation rules".
> Fuente ejecutable: `src/uix/contracts.ts`. Test de frontera:
> `src/uix/contracts.test.ts`.

Loading…
Cancel
Save

Powered by TurnKey Linux.