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
parent
4b48f2cc74
commit
e83b9289ed
@ -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…
Reference in new issue