docs(book): F7.2 (2/6) — the thesis translated to docs/architecture/overview.md

src/uix/README.md (485 L, Spanish) translated to English as the book's
opening chapter. Two already-decided reconciliations applied in passing:
the 'authoritative GUIA' pointer now reflects its historical status
(phase-6 decision) and the frozen 2026-05-17 migration status became the
timeless statement (every component ships an eidos wrapper; inventory =
tree + component:audit). Thin stub at the old path; docs/README entry,
strata table and reading order repointed. docs:check 0 errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
menubar-v4-safe
dev 3 months ago
parent 4b48f2cc74
commit e83b9289ed

@ -27,7 +27,7 @@ materializes the visuals — all by reading the DOM attributes the morfo promise
> **Morfo declares · Soma transcribes · Sema projects · Eidos paints.**
The full thesis is in [`src/uix/README.md`](../src/uix/README.md); the deep
The full thesis is in [`architecture/overview.md`](./architecture/overview.md); the deep
architecture in [`src/uix/active_architecture.md`](../src/uix/active_architecture.md).
## The strata
@ -36,7 +36,7 @@ The corpus is organized in layers of permanence, not by folder:
| Stratum | What it is | Where |
| --- | --- | --- |
| **E0 — orientation** | this file; the narrative entry; the glossary | `docs/README.md`, `src/uix/README.md`, `docs/glossary.md` |
| **E0 — orientation** | this file; the narrative entry; the glossary | `docs/README.md`, `architecture/overview.md`, `docs/glossary.md` |
| **E1 — architecture** | how the layers fit | `active_architecture.md` + per-layer READMEs |
| **E2 — canon** | the fixed vocabulary & contracts | `CANON.md`, `eidos/TSC.md`, `GUIA_IMPLEMENTACION_SEMAUIX.md` |
| **E3 — decisions / RFC** | *why* it is built this way | `decisions.md` + the RFCs & decision logs |
@ -50,7 +50,7 @@ concern — are in [`docs/authoring.md`](./authoring.md).
## Reading order for a fresh start
1. **[`src/uix/README.md`](../src/uix/README.md)** — the thesis: the four layers, what makes it different, what it is not.
1. **[`architecture/overview.md`](./architecture/overview.md)** — the thesis: the four layers, what makes it different, what it is not.
2. **[`src/uix/active_architecture.md`](../src/uix/active_architecture.md)** — the deep architecture: the transcription chain, the DOM-primitive ownership table, the hard rules.
3. **[`docs/CANON.md`](./CANON.md)** — the semantic vocabulary (8 families, intents, verbs, channels). **Single source of truth — every other doc links here instead of re-stating it.**
4. Then the **layer reference** for whatever you are touching (table below).
@ -113,7 +113,7 @@ and the server-authoritative engines in `src/svrs/`.
| Goal | Go to |
| --- | --- |
| Run it and make a first change | [`docs/getting-started.md`](./getting-started.md) |
| Understand the framework | `src/uix/README.md` → `active_architecture.md` |
| Understand the framework | [`architecture/overview.md`](./architecture/overview.md) → `active_architecture.md` |
| Know why UIX, not Radix / Mantine | [`docs/comparison.md`](./comparison.md) |
| Know what a family / intent / verb means | `docs/CANON.md` |
| **Build a new component** | [`docs/building-a-component.md`](./building-a-component.md) — the one door: the 9-phase route across all layers, with the guard for each phase and the known superseded-doc traps |

@ -0,0 +1,503 @@
---
title: UIX — the thesis
type: reference
audience: human + agent
authority: E1 architecture — the narrative introduction to the four layers and their boundaries
status: current
source: migrated from src/uix/README.md (2026-07-02, docs-book F7.2)
---
# UIX
A short architectural positioning document for `src/uix`.
> **Whole-system view**: to understand the four layers (morfo, soma, sema,
> eidos) in a single read — motivations and articulation included — go to
> [`active-architecture`](../../src/uix/active_architecture.md). This document
> keeps the more narrative introduction.
>
> **Semantic vocabulary** (families, intents, verbs, channels): the single
> source of truth is [`CANON.md`](../CANON.md), anchored to the book and the
> code. (The original Spanish implementation guide survives as a historical
> seed at [`src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md`](../../src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md).)
UIX does not try to be "another component library". The bet is more ambitious
and structural: **separate layers that almost every current framework keeps
mixed together**.
In most UI systems these things live glued to each other:
- the component's public DOM contract
- headless behavior
- accessibility
- event semantics
- the visual layer
- modal engines (sound, haptic, motion)
- app-service integration
UIX splits that block into pieces with strong boundaries.
---
## 1. The central idea
UIX models the interface as several cooperating layers, not as one giant
component that does everything at once.
```text
App
├─ cross-cutting services (active-app)
│ └─ adom, langs, format, ...
└─ components
└─ structural / semantic / behavioral / visual layer
```
The intuition:
- a component's public structure is not the same as its behavior
- an event's semantics are not the same as its materialization
- the active DOM is not the same as pure DOM utilities
- the app should not couple modal engines to each other
UIX gives those separations explicit names and contracts.
---
## 2. The UIX layers
### `Morfo` (`src/uix/morfo/`)
The component's cross-layer structural contract. It declares `parts`,
`data-*`, ARIA, focus, keyboard, events and — when text belongs to the
contract — the component's `texts` slots (idlangrefs). It is neither prose nor
runtime: it is the canonical public shape.
See: [`architecture/morfo.md`](./morfo.md)
### `Sema` / Events (`src/uix/sema/`)
The semantic vocabulary and the orchestration of perceptual events. `sema`
remains the historical folder name; `ActiveUix`'s public surface calls the
service `events`.
- canonical families (8): `contact`, `commit`, `signal`, `handle`, `emerge`,
`shift`, `sustain`, `delegate`
- canonical intents: `neutral`, `affirm`, `fulfill`, `risk`, `threat`, `loss`
- `uix.events.emit(signal)` for providers to publish occurrences
- a channel registry (`VisualChannel`, `SoundChannel`, `HapticChannel`)
- `src/uix/sema/channels.ts` as the single source of channel ids, signatures
and overrides
- `src/uix/sema/sounds.ts` as the single nominal repository of synthetic
sounds, external `.wav`s and dynamic recipes
- the `VisualChannel`'s strict sequential semantics: it prepares
`data-event-*` through a DOM projector, holds them for the `hold` window,
and cleans up BEFORE resolving the Promise
Sema does not decide what happened (the provider decides that). It only
orchestrates the occurrence and dispatches it to the registered channels.
See: [`src/uix/sema/README.md`](../../src/uix/sema/README.md)
### `Soma` (`src/uix/soma/`)
The headless behavior layer: state, context, a11y, keyboard / pointer /
focus, event emission.
Soma does not know the concrete implementation of the modal engines. Its job
is to emit the component's facts, not to materialize them.
See: [`src/uix/soma/README.md`](../../src/uix/soma/README.md),
[`SOMA_ARCHITECTURE.md`](../../src/uix/soma/SOMA_ARCHITECTURE.md)
### `Eidos` (`src/uix/eidos/`)
The visual layer: tokens, themes, per-component CSS recipes, archetype rules,
event reactions and **Svelte wrappers** over soma's headless providers with
the visual props (variant, size, block, iconOnly, icon, checkMark).
The general module is flattened into `ActiveEidos`:
- `ActiveEidos` — the active runtime / visual context created by
`ActiveEidos.create(...)`. It manages visual configuration, primitives,
canonical roles, themes, validation, CSS generation and persistence. With
`ActiveUix` services it receives `dom`, `langs`, `prefs` and `format`;
`mode` and `density` arrive through explicit sources or its own defaults.
It injects `<style data-uix-eidos>` only when the app wants runtime CSS
instead of precompiled CSS.
The app can provide a full `EidosConfig` or a partial `themeBase` patch via
`ActiveEidos.create({ config/themeBase })`, or delegate theme values to
external CSS with `themeSource: 'css'`. `getCssContract()` exposes the typed
list of custom properties and `renderContractCss()` publishes it as empty CSS
for external themes. For editors or live preferences, `ActiveEidos` can
inject a runtime variable map into its own `<style>` validated against that
contract. When the app wants to persist a full configuration, it stores a
versioned `EidosConfigDocument` (`kind + version + options`) and can pass it
back as `ActiveEidos.create({ config: document })`.
Eidos's `size` is canonical and discrete: `xxs..xxl/full`. ActiveEidos
generates coordinated physical tokens for `xxs..xxl`; `full` stays reserved
for responsive layout.
The generated contract also includes alpha color scales
(`--scale-blue-a1..a12` and `--primitive-primary-a1..a12`), border
(`width/style` + aliases), layout (`containerWidth`, `contentWidth`,
`aspectRatio`), density scalars wired to `data-density`, opacity, z-index and
shadows with a physical `1..6` scale plus per-theme semantic aliases. The
active recipe aliases (`--toast-*`, `--dialog-*`, etc.) live in
`EidosConfig.recipes`; the generated static artifact lives in
`src/uix/eidos/generated/base.css` and `src/uix/eidos/index.css` imports it
as the foundation for SSR/docs with `ActiveEidos.create({ applyDom: false })`.
The old `contracts/` and `themes/base/` CSS were retired from the active
tree; the contract is obtained from `ActiveEidos.getCssContract()` /
`renderContractCss()`. The `tokens/` directory is no longer part of the
runtime. `ActiveEidos.listRecipes()` and `getRecipeTokens(component)` expose
those aliases for theme editors without reading CSS.
Every soma component ships an eidos wrapper (the Soma → Eidos migration
completed in 2026-05): each wraps soma's public parts and adds only visual
surface. The live inventory is the `eidos/components/` tree plus
`npm run component:audit`.
Eidos reads from the DOM what the other layers write (parts, data-attrs,
ARIA, event signals) — it never imports soma or sema internals.
See: [`src/uix/eidos/README.md`](../../src/uix/eidos/README.md)
### `active-uix` (`src/uix/active-uix/`)
The composition root. Only `ActiveApp` and `ActiveUix` may create shared
services:
- `createActiveUix(options)` — standalone; instantiates services and `prefs`
internally.
- `attachActiveUix(activeApp)` — attaches to an `ActiveApp` already composed
by the application; validates the required services and creates no
substitutes.
Active layers consume `ActiveUix` via `getActiveUix()` and expose the minimal
surface their components need. Soma components read `Soma`; eidos components
read `ActiveEidos`. That way they neither know which boot path was used nor
depend on the full `ActiveUix` surface.
Full chapter: [`architecture/active-uix.md`](./active-uix.md)
#### ActiveUix and prefs
`ActiveUix` neither requires nor knows `arts/frontend`; that artifact was
retired. The pattern follows `ActiveApp`: `prefs` is core and consumer layers
sync from its dimensions, but each layer projects only what it owns.
- `prefs.language` syncs the active language of `uix.langs`.
- `prefs.locale` feeds `uix.format`.
- `prefs.direction` is the single source of effective direction; it derives
from `language` when there is no override.
- `prefs.motion`, `prefs.sound` and `prefs.haptic` are cross-modal
perception/interaction preferences.
- `theme`, `mode` and `density` belong to Eidos: `theme` names the visual
family, `mode` resolves `light | dark`, and `density` tunes visual
ergonomics.
Global writes go through whichever `ActiveDom` is injected, never direct
mutations:
```text
ActivePrefsDomProjection -> dir, data-motion, data-sound, data-haptic
ActiveEidos -> data-theme, data-mode, data-density
```
`ActiveUix` does not auto-create `ActivePrefsDomProjection`. That projector
belongs to `arts/prefs` and the composition root wires it when it wants those
global attrs. Eidos is created separately through `ActiveEidos` when runtime
visuals are needed.
Example of a standalone UIX shell (like the docs' `/uix` route):
```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
});
```
The light/dark toggle feeds `ActiveEidos.modeSource`, not `uix.prefs.theme`.
`prefs.theme` is not part of UIX's core preset.
In standalone mode, `createActiveUix()` instantiates `ActivePrefs` with the
standard UIX preset and creates the configured services. In attach mode,
`attachActiveUix(app)` reuses `app.prefs`, `app.langs`, `app.dom`,
`app.clipboard`, `app.format` and the perceptual engine when they exist. UIX
exposes it as `uix.events`; in attach mode `ActiveApp`'s service is called
`events`. `langs` and `dom` are required for attach; if missing, an early
error is thrown. `clipboard`, `format` and `events` are optional services:
if a layer asks for them and `ActiveUix` does not expose them, they fail with
an explicit error instead of creating substitutes.
`soma.prefs` is the preferences view the headless providers need, backed by
`uix.prefs`, not by `frontend`.
### `adom` (`src/arts/adom/`, alias `$adom`)
The application's active DOM runtime. It is not a semantic engine. Its only
job is to **coordinate and synchronize DOM mutations** via
`app.dom.apply(change)` and `app.dom.remove(target, names)`.
ADom receives already-resolved instructions. It does not interpret them.
See: [`src/arts/adom/README.md`](../../src/arts/adom/README.md)
### `libs/dom` (`src/libs/dom/`)
Pure or nearly-pure DOM utilities (`contains`, `getDocument`, `getWindow`,
focus, traversal, base observer wrappers). It contains no active runtime —
that role belongs to `adom`.
---
## 2.bis How a component executes
The closed architecture (post-2026-04-25) defines six pieces with disjoint
responsibilities. None invades the next.
```
Morfo declares
SomaRuntime transcribes
Provider supplies sources, targets and handlers
Effects sync derived attrs
EngineSemantic orchestrates prepare + dispatch to perceptual channels
VisualChannel prepares data-event* via SignalProjector + holds
SignalProjector projects data-event* via uix.dom
ADom applies DOM mutations (the structural commit)
```
### The operational split
`Morfo` is DNA: one file per component declaring `parts`, `data-*`, `aria-*`,
`role`, `keyboard`, `focus`, `events`. It executes nothing.
`SomaRuntime` (in `soma/`) interprets the morfo. One instance per component
receives from the provider the state sources, the DOM targets and the event
handlers. It exposes `partProps(part)`, `attachPart(part, target)`,
`keydown(part, event)`, `trigger(eventName)`.
`Provider` supplies what the morfo cannot infer: reactive getters for
`states` and `props`, reactive getters for `parts` (dynamic ids), synchronous
handlers for the `events`, and the glue for orthogonal layers (Presence,
Dismissal, ScrollLock — those are not morfo).
`Effects` (registered by the runtime on mount) listen for source changes and
apply the derived attrs via `dom.apply`.
`EngineSemantic` receives the occurrence from `runtime.trigger`. It generates
the id, runs the channels' `prepare` hooks and dispatches the signal to ALL
registered channels. The built-in `VisualChannel` projects `data-event-*`
through `SignalProjector`, holds the attrs for the configured `hold`, cleans
them up and only then resolves the Promise (strict sequential semantics).
Non-visual channels (sound, haptic) are fire-and-forget.
`ADom` only applies the subsequent structural commit. It does not interpret.
Sema and ADom are sibling layers: the engine no longer depends on ADom.
### The `runtime.trigger(eventName)` sequence
```
1. imperative prewrite (transient markers like data-last-action)
2. await events.emit(event)
3. the provider's synchronous handler mutates state
4. effects derive and apply structural attrs (data-state, aria-*)
```
The handler mutates state. The effects see the change and rewrite the DOM.
ADom is the only writer of mutable attrs.
### Three Soma scenarios
```ts
// Structural change without a signal
provider.commitState(change);
// internally: dom.apply(change)
// Structural change with a signal
provider.commitState(change, event);
// internally: await events.emit(event); dom.apply(change)
// Signal without structural change
provider.emitEvent(event);
// internally: void events.emit(event)
```
### Operational rules
- What `dom.apply` writes, Svelte does not render. `partProps` only emits
static identity (id, marker, ref).
- `events` handlers are synchronous. Async goes outside the trigger.
- Guards (`if (disabled) return`) live at the call-site, not inside the
handler — if they reach the handler, a perceptual signal was already
emitted.
- `Sema` and `ADom` are sibling layers: neither depends on the other.
- `morfo.events.commits` is descriptive: it documents the observable, it does
not execute it. The real causal chain is handler → state → effect.
---
## 3. What makes UIX different
### 3.1 The structural contract is its own layer
In most libraries the component's public structure is scattered:
- attributes in the provider
- roles in the render
- parts in CSS
- selector names in docs
- contracts in tests
UIX concentrates that in `Morfo`. That enables:
- docs derived from the contract
- cross-layer validation (the eidos linter)
- less drift between headless and visual
- more reliable tooling
### 3.2 Semantics never mix with execution
UIX separates the **name of the occurrence** from its **modal
materialization**. Sound, vibration, CSS and future engines use the same
vocabulary without being glued to each other.
### 3.3 Headless behavior doesn't carry every modality
`Soma` is not a mega-engine that knows everything. It doesn't play WAVs or
haptic feedback, and it doesn't decide each channel's perceptual physics.
Soma emits. Channels execute.
### 3.4 The active DOM is app infrastructure
UIX recognizes there are cross-cutting DOM facts several consumers want to
listen to, which is why it introduces `adom` as an app service.
The rule is not "all DOM access goes through ADom". The rule is more precise:
UIX-managed writes, `document/window` listeners, global queries, observers
(`ResizeObserver`, `MutationObserver`, `IntersectionObserver`) and
cross-cutting imperative actions go through `ActiveDom`. That includes focus
and imperative scroll (`focus`, `scrollTo`, `scrollIntoView`,
`scrollWindowTo`). Local reads of an element the component already owns
(`contains`, `closest`, `getBoundingClientRect`, `scrollTop`) stay in the
component.
### 3.5 The app composes services, not "super components"
`active-app` lets the application compose `langs`, `dom`, `clipboard`,
`format`, `events`, and `ActiveUix` delivers them to layer scopes (`Soma`,
`Eidos`) instead of forcing every component to know the full active root.
---
## 4. What UIX is not
UIX is not:
- a flat collection of visual components
- a mere opinionated wrapper over existing primitives
- a classic design system where visuals, behavior and contracts live together
- a monolithic event service that executes every modality
- a global `EventEmitter` dressed up as architecture
UIX's originality is not in inventing exotic names, but in **separating real
problems** that other systems usually accept as a single block.
---
## 5. Dependency rules
```text
Morfo → declares contracts
SomaRuntime → interprets morfo inside Soma
Provider → supplies sources, targets, handlers
Effects → sync state → attrs
EngineSemantic → registry + prepare/dispatch of signals to channels
VisualChannel → prepares data-event* via SignalProjector + holds
SignalProjector → projects data-event* via uix.dom
ADom → applies DOM mutations (the structural commit)
Eidos → materializes visually by reading the DOM
App → composes services
```
Hard rules:
- `Morfo` knows neither `Soma` nor any runtime code
- `SomaRuntime` reads `Morfo` and depends on `Sema`
- `Provider` does not write mutable attrs to the DOM directly; it supplies
them as sources to the runtime
- `EngineSemantic` does not mutate the DOM directly; it only orchestrates
channel hooks
- `VisualChannel.prepare()` delegates DOM projection to a `SignalProjector`
- `DomSignalProjector` writes through the `ActiveDom` received from
`ActiveUix`
- `Sema` and `ADom` are sibling layers
- `ADom` knows neither `Morfo`, nor `Sema`, nor `Soma`, nor `Eidos`; it only
executes the mutations, listeners and cross-cutting DOM actions it receives
- `ActiveEidos` adapts `ActiveUix` for visual components; wrappers do not
import `getActiveUix()` directly
- `Eidos` consumes DOM and `data-*`, not `Soma`/`Sema` internals
- What `dom.apply` writes, Svelte does not render
### The 2-of-3 rule for extending Morfo
A Morfo extension is only justified when **at least two of the three layers**
(soma, sema, eidos) consume it. If only soma benefits, the pattern is a
virtual prop on the provider — do not extend the contract.
| Extension | Soma | Sema | Eidos | In morfo |
| ----------------------- | -------------- | ---------------- | ------------------------ | -------- |
| `parts[].archetype` | ✅ emit | ✅ verbs by role | ✅ transversal selectors | ✅ |
| `events[].semantic` | ✅ payload | ✅ vocabulary | ✅ tinting | ✅ |
| `events[].prewrite` | ✅ executes | ✅ sequence | ✅ exit tinting | ✅ |
| `firstOf` value source | ✅ only | — | — | ❌ |
| `prop-not-nullish` cond | ✅ only | — | — | ❌ |
| Field-context OR | — virtual prop | — | — | ❌ |
### Canonical cross-layer vocabularies
- **Archetypes**: the cross-component part classification. Defined in
`src/uix/morfo/types.ts:ARCHETYPE_VOCABULARY`. Emitted as
`data-archetype="..."` by `runtime.partProps`.
- **Verbs**: the canonical action verbs for `morfo.events[].name`. Defined in
`src/uix/sema/verbs.ts:SEMA_VERBS`. Convention for composite names:
`{verb}-{variant}` (e.g. `commit-save`, `dismiss-outside`).
---
## 6. The difference in one sentence
> Morfo declares, SomaRuntime transcribes, Provider supplies, Effects sync,
> Sema emits, ADom applies, Eidos reads.
Seven pieces, seven responsibilities, none invades the next.
---
## 7. Suggested reading order
0. [`CANON.md`](../CANON.md) — the **semantic canon**: the vocabulary
(families, intents, verbs, composition, channels) anchored to the book +
code. The source of truth everything else links.
1. [`architecture/morfo.md`](./morfo.md) — declaration, archetypes, the
2-of-3 rule
2. [`src/uix/sema/README.md`](../../src/uix/sema/README.md) — the `emit`
contract, canonical verbs, channels
3. [`src/uix/eidos/README.md`](../../src/uix/eidos/README.md) — what eidos
consumes from the DOM + wrappers
4. [`SOMA_ARCHITECTURE.md`](../../src/uix/soma/SOMA_ARCHITECTURE.md) — the
runtime that transcribes morfo
5. [`COMPONENT_GUIDE.md`](../../src/uix/soma/COMPONENT_GUIDE.md) — the
operational guide to create/migrate components
6. [`src/arts/adom/README.md`](../../src/arts/adom/README.md) — `dom.apply`
7. [`architecture/active-uix.md`](./active-uix.md) — the composition root

@ -1,485 +1,16 @@
# UIX
Documento corto de posicionamiento arquitectónico para `src/uix`.
> **Visión de conjunto**: para entender las cuatro capas (morfo, soma, sema,
> eidos) en una sola lectura — motivaciones y articulación incluidas — ir a
> [active_architecture.md](./active_architecture.md). Este README mantiene la
> introducción más narrativa.
>
> **Convenciones doctrinales del API** (intent ↔ color, subset por componente,
> root visual con partes attached en eidos, sound prepare-time priming): viven en
> [`src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md`](../docs/GUIA_IMPLEMENTACION_SEMAUIX.md).
> Autoritativo para todo wrapper / migración nueva.
UIX no intenta ser "otra librería de componentes". La apuesta es más ambiciosa
y estructural: **separar capas que casi todos los frameworks actuales
mantienen mezcladas**.
En la mayoría de sistemas de UI, estas cosas viven pegadas:
- contrato público del DOM
- comportamiento headless
- accesibilidad
- semántica del evento
- capa visual
- motores modales (sound, haptic, motion)
- integración con servicios de app
UIX intenta partir ese bloque en piezas con fronteras fuertes.
---
## 1. La idea central
UIX modela la interfaz como varias capas cooperando, no como un único componente
gigante que hace todo a la vez.
```text
App
├─ servicios transversales (active-app)
│ └─ adom, langs, format, ...
└─ componentes
└─ capa estructural / semántica / comportamental / visual
```
La intuición:
- la estructura pública del componente no es lo mismo que su comportamiento
- la semántica de un evento no es lo mismo que su materialización
- el DOM activo no es lo mismo que utilidades DOM puras
- la app no debería acoplar motores modales entre sí
UIX pone nombres y contratos explícitos a esas separaciones.
---
## 2. Las capas de UIX
### `Morfo` (`src/uix/morfo/`)
Contrato estructural cross-layer del componente. Define `parts`, `data-*`,
ARIA, foco, teclado, eventos y, cuando el texto pertenece al contrato, los
slots `texts` del componente (idlangrefs). No es prose ni runtime — es la
forma canónica pública.
Ver: [docs/architecture/morfo.md](../../docs/architecture/morfo.md)
### `Sema` / Events (`src/uix/sema/`)
Vocabulario semántico y orquestación de eventos perceptivos. `sema` queda como
nombre historico de la carpeta; la superficie publica de `ActiveUix` usa
`events`.
- familias canónicas (8): `contact`, `commit`, `signal`, `handle`, `emerge`,
`shift`, `sustain`, `delegate`
- intents canónicos: `neutral`, `affirm`, `fulfill`, `risk`, `threat`, `loss`
- `uix.events.emit(signal)` para que el provider publique ocurrencias
- registry de canales (`VisualChannel`, `SoundChannel`, `HapticChannel`)
- `src/uix/sema/channels.ts` como fuente unica de ids, signatures y overrides
de canales
- `src/uix/sema/sounds.ts` como repositorio nominal unico de sonidos
sintéticos, `.wav` externos y recetas dinámicas
- semántica secuencial estricta del `VisualChannel`: prepara `data-event-*`
mediante un proyector DOM, mantiene durante el `hold` y limpia ANTES de
resolver la Promise
Sema no decide qué ocurrió (eso lo decide el provider). Solo orquesta la
ocurrencia y la despacha a los canales registrados.
Ver: [sema/README.md](./sema/README.md)
### `Soma` (`src/uix/soma/`)
Capa headless de comportamiento: estado, contexto, a11y, keyboard / pointer /
focus, emisión de eventos.
Soma no conoce la implementación concreta de los engines modales. Su trabajo
es emitir hechos del componente, no materializarlos.
Ver: [soma/README.md](./soma/README.md), [soma/SOMA_ARCHITECTURE.md](./soma/SOMA_ARCHITECTURE.md)
### `Eidos` (`src/uix/eidos/`)
Capa visual: tokens, themes, recipes CSS por componente, archetype rules,
event reactions y **wrappers Svelte** sobre los providers headless de soma
con las props visuales (variant, size, block, iconOnly, icon, checkMark).
El modulo general queda aplanado en `ActiveEidos`:
- `ActiveEidos` — runtime activo/contexto visual creado por
`ActiveEidos.create(...)`. Gestiona configuracion visual, primitivas, roles
canonicos, themes, validacion, generacion de CSS y persistencia. Con
servicios de `ActiveUix`, recibe `dom`, `langs`, `prefs` y `format`; `mode`
y `density` llegan por fuentes explicitas o defaults propios. Inyecta
`<style data-uix-eidos>` solo cuando la app quiere CSS runtime en vez de CSS
precompilado.
La app puede aportar un `EidosConfig` completo o un `themeBase` patch
parcial desde `ActiveEidos.create({ config/themeBase })`, o delegar los valores
de theme a CSS externo con `themeSource: 'css'`. `getCssContract()` expone la
lista typed de custom properties y `renderContractCss()` la publica como CSS
vacío para themes externos. Para editores o preferencias en vivo,
`ActiveEidos` puede inyectar un mapa de variables runtime en un `<style>`
propio validado contra ese contrato. Cuando la app quiera persistir una
configuracion completa, guarda un `EidosConfigDocument` versionado
(`kind + version + options`) y lo puede pasar de vuelta como
`ActiveEidos.create({ config: document })`.
El `size` de Eidos es canonico y discreto: `xxs..xxl/full`. ActiveEidos genera
tokens fisicos coordinados para `xxs..xxl`; `full` queda reservado para
layout responsivo.
El contrato generado tambien incluye alpha color scales (`--scale-blue-a1..a12`
y `--primitive-primary-a1..a12`), borde (`width/style` + aliases), layout
(`containerWidth`, `contentWidth`, `aspectRatio`), density scalars conectados
a `data-density`, opacidad, z-index y sombras con escala fisica `1..6` más
aliases semanticos por theme. Los aliases de
recipe activos (`--toast-*`, `--dialog-*`, etc.) ya viven en
`EidosConfig.recipes`; el artefacto estatico generado vive en
`src/uix/eidos/generated/base.css` y
`src/uix/eidos/index.css` lo importa como foundation para SSR/docs con
`ActiveEidos.create({ applyDom: false })`. Los antiguos CSS de `contracts/`
y `themes/base/` se retiraron del arbol activo; el contrato se obtiene desde
`ActiveEidos.getCssContract()` / `renderContractCss()`. El directorio
`tokens/` ya no forma parte del runtime. `ActiveEidos.listRecipes()` y
`getRecipeTokens(component)` exponen esos aliases para editores de theme sin
leer CSS.
La migración de componentes Soma -> Eidos se retoma por tandas pequeñas. A fecha
2026-05-17, además de los pilotos previos, ya están migrados `meter`,
`progress`, `slider`, `pagination`, `rating-group`, `search-field` y
`number-field`, y `breadcrumb`; cada uno envuelve partes públicas de Soma y
añade únicamente superficie visual.
Eidos lee del DOM lo que las otras capas escriben (parts, data-attrs, ARIA,
event signals) — nunca importa internals de soma ni de sema.
Ver: [eidos/README.md](./eidos/README.md)
### `active-uix` (`src/uix/active-uix/`)
Composition root. Solo `ActiveApp` y `ActiveUix` pueden crear servicios
compartidos:
- `createActiveUix(options)` — standalone, instancia services y `prefs`
internamente.
- `attachActiveUix(activeApp)` — adjunta a un `ActiveApp` ya compuesto por
la aplicación; valida los servicios requeridos y no crea sustitutos.
Las capas activas consumen `ActiveUix` vía `getActiveUix()` y exponen la
superficie minima que necesitan sus componentes. Los componentes Soma leen
`Soma`; los componentes Eidos leen `ActiveEidos`. Así no saben qué ruta de boot
se usó ni dependen de la superficie completa de `ActiveUix`.
#### ActiveUix y prefs
`ActiveUix` no exige ni conoce `arts/frontend`; ese artefacto fue retirado.
El patron sigue a `ActiveApp`: `prefs` es core y las capas consumidoras se
sincronizan desde sus dimensiones, pero cada capa proyecta solo lo que posee.
- `prefs.language` sincroniza el idioma activo de `uix.langs`.
- `prefs.locale` alimenta `uix.format`.
- `prefs.direction` es la unica fuente de direccion efectiva; deriva de
`language` cuando no hay override.
- `prefs.motion`, `prefs.sound` y `prefs.haptic` son preferencias
transversales de percepcion/interaccion.
- `theme`, `mode` y `density` pertenecen a Eidos: `theme` nombra la familia
visual, `mode` resuelve `light | dark` y `density` ajusta ergonomia visual.
La escritura global va por el `ActiveDom` que se inyecte, no por mutaciones
directas:
```text
ActivePrefsDomProjection -> dir, data-motion, data-sound, data-haptic
ActiveEidos -> data-theme, data-mode, data-density
```
`ActiveUix` no crea automaticamente `ActivePrefsDomProjection`. Ese proyector
pertenece a `arts/prefs` y lo cablea el composition root cuando quiere esos
attrs globales. Eidos se crea aparte mediante `ActiveEidos` cuando se necesita
runtime visual.
Ejemplo de shell UIX standalone (como la ruta `/uix` de docs):
```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
});
```
El toggle de modo claro/oscuro alimenta `ActiveEidos.modeSource`, no
`uix.prefs.theme`. `prefs.theme` no forma parte del preset core de UIX.
En modo standalone, `createActiveUix()` instancia `ActivePrefs` con el preset
estandar de UIX y crea los servicios configurados. En modo attach,
`attachActiveUix(app)` reutiliza `app.prefs`, `app.langs`, `app.dom`,
`app.clipboard`, `app.format` y el motor perceptivo cuando existen. UIX lo
expone como `uix.events`; en attach mode el servicio de `ActiveApp` se llama
`events`. `langs` y `dom` son requeridos para attach; si faltan, se lanza
error temprano. `clipboard`, `format` y `events` son servicios opcionales:
si una capa los pide y `ActiveUix` no los expone, fallan con error explicito
en vez de crear sustitutos.
`soma.prefs` es la vista de preferencias que necesitan los providers
headless, respaldada por `uix.prefs`, no por `frontend`.
### `adom` (`src/arts/adom/`, alias `$adom`)
Runtime DOM activo de aplicación. No es semantic engine. Su única función es
**coordinar y sincronizar mutaciones DOM** vía `app.dom.apply(change)` y
`app.dom.remove(target, names)`.
ADom recibe instrucciones ya resueltas. No las interpreta.
Ver: [`src/arts/adom/README.md`](../arts/adom/README.md)
### `libs/dom` (`src/libs/dom/`)
Utilidades DOM puras o casi puras (`contains`, `getDocument`, `getWindow`,
foco, traversal, wrappers base de observers). No contiene runtime activo —
ese papel es de `adom`.
---
## 2.bis Cómo se ejecuta un componente
La arquitectura cerrada (post-2026-04-25) define seis piezas con
responsabilidades disjuntas. Ninguna invade a la siguiente.
```
Morfo declara
SomaRuntime transcribe
Provider aporta sources, targets y handlers
Effects sincronizan attrs derivados
EngineSemantic orquesta prepare + dispatch a canales perceptivos
VisualChannel prepara data-event* via SignalProjector + mantiene hold
SignalProjector proyecta data-event* via uix.dom
ADom aplica mutaciones DOM (commit estructural)
```
### El reparto operativo
`Morfo` es DNA: un fichero por componente que declara `parts`, `data-*`,
`aria-*`, `role`, `keyboard`, `focus`, `events`. No ejecuta nada.
`SomaRuntime` (en `soma/`) interpreta el morfo. Una instancia por componente
recibe del provider las fuentes de estado, los targets DOM y los handlers de
eventos. Expone `partProps(part)`, `attachPart(part, target)`, `keydown(part,
event)`, `trigger(eventName)`.
`Provider` aporta lo que el morfo no puede inferir: getters reactivos para
`states` y `props`, getters reactivos para `parts` (ids dinámicos), handlers
síncronos para los `events`, glue de layers ortogonales (Presence, Dismissal,
ScrollLock — no son morfo).
`Effects` (registrados por el runtime al montar) escuchan cambios en los
sources y aplican los attrs derivados vía `dom.apply`.
`EngineSemantic` recibe la ocurrencia desde `runtime.trigger`. Genera el id,
ejecuta los `prepare` de canales y despacha la señal a TODOS los canales
registrados. El `VisualChannel` (built-in) proyecta `data-event-*` mediante
`SignalProjector`, mantiene los attrs durante el `hold` configurado, los
limpia y solo entonces resuelve la Promise (semántica secuencial estricta).
Los canales no-visuales (sound, haptic) son fire-and-forget.
`ADom` solo aplica el commit estructural posterior. No interpreta. Sema y
ADom son capas hermanas: el engine ya no depende de ADom.
### La secuencia de `runtime.trigger(eventName)`
```
1. prewrite imperativo (transient markers como data-last-action)
2. await events.emit(event)
3. handler síncrono del provider muta state
4. effects derivan y aplican attrs estructurales (data-state, aria-*)
```
El handler muta state. Los effects ven el cambio y reescriben el DOM. ADom es
el único escritor de attrs mutables.
### Tres escenarios de Soma
```ts
// Cambio estructural sin señal
provider.commitState(change);
// internamente: dom.apply(change)
// Cambio estructural con señal
provider.commitState(change, event);
// internamente: await events.emit(event); dom.apply(change)
// Señal sin cambio estructural
provider.emitEvent(event);
// internamente: void events.emit(event)
```
### Reglas operativas
- Lo que `dom.apply` escribe, Svelte no lo renderiza. `partProps` solo emite
identidad estática (id, marker, ref).
- Los handlers de `events` son síncronos. Async va fuera del trigger.
- Los guards (`if (disabled) return`) van en el call-site, no dentro del
handler — si entran al handler, ya emitieron señal perceptiva.
- `Sema` y `ADom` son capas hermanas: ninguna depende de la otra.
- `morfo.events.commits` es descriptivo: documenta lo observable, no lo
ejecuta. La cadena causal real es handler → state → effect.
---
## 3. Qué hace distinto a UIX
### 3.1 El contrato estructural es una capa propia
En la mayoría de librerías la estructura pública del componente está dispersa:
- atributos en el provider
- roles en el render
- partes en CSS
- selector names en docs
- contratos en tests
UIX concentra eso en `Morfo`. Eso permite:
- docs derivadas del contrato
- validación cross-layer (linter de eidos)
- menos drift entre headless y visual
- tooling más fiable
### 3.2 La semántica no se mezcla con la ejecución
UIX separa el **nombre de la ocurrencia** de su **materialización modal**.
Sonido, vibración, CSS y motores futuros usan el mismo vocabulario sin
quedar pegados entre sí.
### 3.3 El comportamiento headless no carga con toda la modalidad
`Soma` no es un mega-engine que sabe de todo. No reproduce WAVs ni feedback háptico,
no decide la física perceptiva de cada canal. Soma emite. Los canales
ejecutan.
### 3.4 El DOM activo es infraestructura de app
UIX reconoce que hay hechos transversales del DOM que varios consumidores
quieren escuchar, y por eso introduce `adom` como servicio de app.
La regla no es "todo acceso DOM pasa por ADom". La regla es mas precisa:
escrituras gestionadas por UIX, listeners de `document/window`, consultas
globales, observers (`ResizeObserver`, `MutationObserver`,
`IntersectionObserver`) y acciones imperativas transversales pasan por
`ActiveDom`. Esto incluye foco y scroll imperativo (`focus`, `scrollTo`,
`scrollIntoView`, `scrollWindowTo`). Las lecturas locales de un elemento que
el componente ya posee (`contains`, `closest`, `getBoundingClientRect`,
`scrollTop`) se quedan en el componente.
### 3.5 La app compone servicios, no "super componentes"
`active-app` permite que la aplicación componga `langs`, `dom`,
`clipboard`, `format`, `events` y que `ActiveUix` los entregue a scopes de capa
(`Soma`, `Eidos`) en lugar de obligar a cada componente a conocer la raíz
activa completa.
---
## 4. Lo que UIX no es
UIX no es:
- una colección plana de componentes visuales
- un simple wrapper opinionated sobre primitives existentes
- un design system clásico donde visual, comportamiento y contratos viven juntos
- un servicio monolitico de eventos que ejecuta todas las modalidades
- un `EventEmitter` global disfrazado de arquitectura
La originalidad de UIX no está en inventar nombres exóticos, sino en
**separar problemas reales** que otros sistemas suelen aceptar como un
único bloque.
---
## 5. Reglas de dependencia
```text
Morfo → declara contratos
SomaRuntime → interpreta morfo dentro de Soma
Provider → aporta sources, targets, handlers
Effects → sincronizan state → attrs
EngineSemantic → registry + prepare/dispatch de señales a canales
VisualChannel → prepara data-event* via SignalProjector + mantiene hold
SignalProjector → proyecta data-event* via uix.dom
ADom → aplica mutaciones DOM (commit estructural)
Eidos → materializa visualmente leyendo DOM
App → compone servicios
```
Reglas duras:
- `Morfo` no conoce `Soma` ni código de runtime
- `SomaRuntime` lee `Morfo` y depende de `Sema`
- `Provider` no escribe attrs mutables al DOM directamente; los aporta como
sources al runtime
- `EngineSemantic` no muta el DOM directamente; solo orquesta hooks de canales
- `VisualChannel.prepare()` delega la proyeccion DOM a un `SignalProjector`
- `DomSignalProjector` escribe mediante el `ActiveDom` recibido desde
`ActiveUix`
- `Sema` y `ADom` son capas hermanas
- `ADom` no conoce `Morfo`, ni `Sema`, ni `Soma`, ni `Eidos`; solo ejecuta
mutaciones, listeners y acciones DOM transversales que recibe
- `ActiveEidos` adapta `ActiveUix` para componentes visuales; los wrappers no
importan `getActiveUix()` directamente
- `Eidos` consume DOM y `data-*`, no internals de `Soma` ni `Sema`
- Lo que `dom.apply` escribe, Svelte no lo renderiza
### Regla 2-de-3 para extender Morfo
Una extensión a Morfo solo se justifica si **al menos dos de las tres
capas** (soma, sema, eidos) la consumen. Si solo soma se beneficia, el
patrón es virtual prop en provider — no extender el contrato.
| Extensión | Soma | Sema | Eidos | En morfo |
| ----------------------- | -------------- | ---------------- | --------------------------- | -------- |
| `parts[].archetype` | ✅ emit | ✅ verbs por rol | ✅ selectores transversales | ✅ |
| `events[].semantic` | ✅ payload | ✅ vocabulario | ✅ tinta | ✅ |
| `events[].prewrite` | ✅ ejecuta | ✅ secuencia | ✅ tinta exit | ✅ |
| `firstOf` value source | ✅ only | — | — | ❌ |
| `prop-not-nullish` cond | ✅ only | — | — | ❌ |
| Field-context OR | — virtual prop | — | — | ❌ |
### Vocabularios canónicos cross-layer
- **Archetypes**: clasificación de partes cross-component. Definida en
`src/uix/morfo/types.ts:ARCHETYPE_VOCABULARY`. Emitida como
`data-archetype="..."` por `runtime.partProps`.
- **Verbs**: action verbs canónicos para `morfo.events[].name`. Definida
en `src/uix/sema/verbs.ts:SEMA_VERBS`. Convención para composite names:
`{verb}-{variant}` (e.g. `commit-save`, `dismiss-outside`).
---
## 6. La diferencia en una frase
> Morfo declara, SomaRuntime transcribe, Provider aporta, Effects sincronizan,
> Sema emite, ADom aplica, Eidos lee.
Siete piezas, siete responsabilidades, ninguna invade a la siguiente.
---
## 7. Orden de lectura sugerido
0. [`docs/CANON.md`](../../docs/CANON.md) — **canon semántico**: el vocabulario (familias, intents, verbs, composición, canales) anclado al libro + código. La fuente de verdad que el resto enlaza.
1. [docs/architecture/morfo.md](../../docs/architecture/morfo.md) — declaración, archetypes, regla 2-de-3
2. [sema/README.md](./sema/README.md) — `emit` contract, verbs canónicos, canales
3. [eidos/README.md](./eidos/README.md) — qué consume eidos del DOM + wrappers
4. [soma/SOMA_ARCHITECTURE.md](./soma/SOMA_ARCHITECTURE.md) — runtime que transcribe morfo
5. [soma/COMPONENT_GUIDE.md](./soma/COMPONENT_GUIDE.md) — guía operativa para crear/migrar componentes
6. [`src/arts/adom/README.md`](../arts/adom/README.md) — `dom.apply`
7. [`src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md`](../docs/GUIA_IMPLEMENTACION_SEMAUIX.md) — convenciones doctrinales del API
UIX separates the layers most frameworks keep glued together: **morfo**
declares the contract, **soma** executes behavior, **sema** projects the
perceptual signal, **eidos** paints — composed by **active-uix**.
**The thesis moved to the docs corpus:**
[`docs/architecture/overview.md`](../../docs/architecture/overview.md)
— the central idea, the layers one by one, how a component executes
(SomaRuntime · Provider · Effects · EngineSemantic · channels · ADom), what
makes UIX different, what it is NOT, the dependency rules and the 2-of-3
rule, and the suggested reading order.
Quick pointers: semantic canon in [`docs/CANON.md`](../../docs/CANON.md);
deep architecture in [`active_architecture.md`](./active_architecture.md);
layer entry points under `morfo/ · soma/ · sema/ · eidos/ · active-uix/`.

Loading…
Cancel
Save

Powered by TurnKey Linux.