---
title: UIX — Active Architecture
type: reference
audience: human + agent
authority: E1 architecture — the deep whole-system view: motivations, the four layers, the transcription chain, the hard rules
status: current
source: migrated from src/uix/active_architecture.md (2026-07-02, docs-book F7.2)
---
# UIX — Active Architecture
> The living document of UIX's active architecture: motivations, the four
> layers, how they articulate, what problem they solve, what they
> deliberately leave out. This doc is the whole-system view; the per-layer
> chapters are the operational reference. Dated status snapshots live in
> [`docs/process/`](../process/) (see §10).
---
## 0. Minimum contracts per module
What each module requires, what is optional, how it degrades and when it
fails. The ownership and degradation rules are stated, timelessly, in
[`architecture/active-uix.md` ](./active-uix.md ) §"Ownership and degradation
rules".
> Executable source: `src/uix/contracts.ts`. Boundary test:
> `src/uix/contracts.test.ts`.
```text
Module Requires Optional When missing Error
active-uix langs,prefs,dom* clipboard,format,events,portal standalone disabledDom missing langs/dom in attach
morfo none translations registers no translations no
soma dom events,langs,format,clipboard disabledDom from uix invalid morfo/event/part; absent optional service
sema projector/dom* sound,haptic,visual:false none SemaConfigError without dom/projector
eidos dom* langs,format,prefs,mode/density sources applyDom:false missing dom with applyDom active
adom ActiveDom surface target/window/breakpoints disabledDom only explicit ADom errors without a real DOM
* `dom` means an `ActiveDom` surface, not necessarily a real DOM. It may be
`disabledDom` only in standalone when the integrator asks for `dom:false` .
In attach it must come from `ActiveApp` .
* Outside `ActiveUix` , an `EngineSemantic` with the visual channel active
must receive `dom` or `projector` ; `visual:false` is the explicit
degradation.
```
Future changes must derive from this table, not from constructors invented
in lower layers.
Applied correction: `ActiveUix` neither imports nor instantiates
`Soma` /`Eidos`. `portal` remains a generic UIX setting; `Soma` consumes it as
the default for `portalTo` , and `ActiveEidos.create(...)` creates the visual
scope when the app needs Eidos.
---
## 0.1 Canonical naming
The architecture may keep the historical folder names (`morfo`, `soma` ,
`sema` , `eidos` ), but the public surface must use a consistent grammar.
General rule: one name represents one concept; if a term is a historical
alias, it must be marked as such with a retirement path.
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| Concept | Canonical name | Avoid / retire |
| -------------------------------- | -------------------------------------------- | --------------------------------------------------- |
| Runtime translation service | `langs` | `lang` as a service |
| Active language | `prefs.language` | `locale` for the translation language |
| Locale / regional formats | `prefs.locale` | `language` for formats |
| Declarative text catalogs | `translations` | `langs` inside `morfo` ; global per-component tables |
| UIX preferences | `prefs` | `settings` , `presentation` as new names |
| UIX perceptual events | `events` | `semantic` as a public service |
| In-flight occurrence | `signal` | using it for the whole layer |
| A morfo event's semantic payload | `semantic` | mixing it with the runtime service |
| Declarative TS contract | `morfo` | `contract` as a duplicated TS API |
| Exported CSS/data contract | `contract` | `morfo` for external CSS |
| Runtime CSS bridge | `ActiveEidos` | a mandatory visual runtime for components |
| Independent pure engine | `EngineX` only if it lives outside `ActiveX` | decorative engines |
| Eidos visual root | `DrawerProps` , `DialogProps` | `DrawerProviderProps` in the visual API |
Decisions already applied:
- `ActiveUix.events` is the canonical name of the perceptual engine. In
attach mode it reads `app.events` ; `defineUixServices(...)` declares the
service under the same name.
- There is no public `ActiveUix.semantic` service. `semantic` survives only
as the payload name in `morfo.events[].semantic` .
- `morfo.texts` is the declarative field for component-owned idlangrefs —
the `morfo.translations` nomenclature was renamed to `texts` during the
2026-05 migration (see `langs/components/*.ts` for the per-component
catalogs). `langs` remains the runtime service.
- `prefs` is the only name for preferences. `ActiveUix` exposes the raw
`ActivePrefs` ; Soma/Eidos consume bounded views. No `settings` is
introduced.
- `ActiveUix.motion` (`EngineMotion`, `arts/motion` ) is the animation engine,
consumed by Soma (`soma.motion`) and Eidos (`eidos.motion`). It lives in
`arts/` , not in Eidos, so Soma can animate (spring) without a soma→eidos
dependency. In attach it reads `app.motion` .
Retirement order:
1. Keep `assertContract` as a data-contract validator, not as a parallel
registry. `registerContract` remains for tooling/direct tests; Soma
registers contracts via `registerMorfo()` .
2. Only afterwards clean up prop names in visual components.
---
## 1. The thesis in one sentence
> UIX treats a component as **four layers with explicit contracts**, not as a
> monolithic block mixing structure, behavior, semantics and presentation.
The four layers are **Morfo · Soma · Sema · Eidos** . Each does one sharp job
and communicates with the others only through the DOM and a shared
declarative contract. None invades the next.
---
## 2. The problem it solves
In most UI frameworks a component accumulates:
- the DOM's **public contract** (attributes, parts, ARIA)
- the **headless behavior** (state, keyboard, focus, events)
- the event's **semantics** (what "opening a dialog" means beyond an
attribute change)
- the **visual layer** (CSS, animations, theming)
- the **modal engines** (sound, haptic and CSS reactions via DOM events)
- the **app-service integration** (i18n, dates, theme, etc.)
All of that lives mixed together. Renaming a `part` touches six places with
no automatic verification. An event's semantics are buried in hardcoded
strings only the component knows. CSS couples to incidental DOM structure.
Sound engines rewrite per-component mappings. When you want to change a
cross-cutting decision — "all triggers must share a common hover dim" — you
must enumerate the 30 components that have a trigger.
UIX breaks that block into four layers with disjoint responsibilities and a
common communication channel: **the DOM with attributes declared by the
cross-layer contract**.
---
## 3. The four layers
### Morfo — the cross-layer contract
`Morfo` declares the component's genetics: its parts, the `data-*` it emits,
the ARIA it contributes, the roles, the states, the semantic events it may
fire, and the keys it dispatches. One declaration per component, in
TypeScript, validated by sium.
Morfo **executes nothing** . It is DNA, not protein.
```ts
// src/uix/morfo/components/dialog.ts (excerpt)
export const dialogMorfo = {
name: 'Dialog',
kebab: 'dialog',
scope: ['soma', 'sema'],
events: [{
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
name: 'emerge-close-cancel',
semantic: {
family: 'emerge',
verb: 'close',
target: v.partRef('content'),
sequence: 'pre'
},
prewrite: [{ part: v.partRef('content'),
attr: 'data-last-action', value: 'cancelled' }],
commits: { part: v.partRef('content'),
attr: 'data-state', value: 'closed' }
}],
parts: [
{ name: 'Trigger', kebab: 'trigger', archetype: 'trigger', role: 'button', ... },
{ name: 'Content', kebab: 'content', archetype: 'content', role: 'dialog', ... },
// ...
]
} as const satisfies Morfo
```
Morfo is **the single cross-layer articulation point** . Any data the other
layers need to share with each other passes through here. It is the most
important structural rule: if two layers need to know the same thing, that
"same thing" lives in morfo.
### Soma — the headless behavior
`Soma` consumes morfo and transcribes it into executable behavior. It reads
`morfo.events` , `morfo.keyboard` , `morfo.parts[].data` and `aria` , and
materializes them: dispatches keys, applies attributes to the DOM, manages
state, integrates with context (Field, Form, Soma).
Soma **decides no visuals** . It knows no colors. No transitions. No sounds.
Only states, events, focus, keyboard and how to materialize all of that in
the DOM.
Soma's central piece is `SomaRuntime` : a morfo interpreter that receives the
reactive sources from the provider (states, props, parts, events, actions)
and takes care of:
- emitting the static attrs (`partProps`)
- applying the state-derived attrs via `dom.apply` (effects)
- dispatching keys via `keydown(part, event)`
- executing events via `trigger(eventName)` with the full perceptual chain
The provider contributes **what the morfo cannot infer** : reactive getters
over internal state, concrete handlers, and the glue for orthogonal layers
(Presence, Dismissal, ScrollLock).
### Sema — vocabulary + perceptual channels
`Sema` defines the framework's canonical vocabulary and orchestrates the
**dispatch of perceptual signals** to a set of modular channels.
Canonical vocabulary (`SEMA_MAP` in `src/uix/sema/sema-map.ts` ):
- **8 families** — `contact` , `commit` , `signal` , `handle` , `emerge` ,
`shift` , `sustain` , `delegate` . Each declares a `hold` , a base for the real
channels (`sound`, `haptic` ) and the set of active channels.
- **6 intents** — `neutral` , `affirm` , `fulfill` , `risk` , `threat` , `loss` .
Each intent declares per-channel `deltas` applied over the family base when
the family is valenced.
- **Action verbs** (`SEMA_VERBS` in `src/uix/sema/verbs.ts` ) — `present` ,
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`dismiss` , `commit` , `cancel` , `announce` , `warn` , … — the canonical verbs
for `morfo.events[].semantic.verb` , and the tail of `morfo.events[].name` .
Sema **does not decide which event happened** — the provider decides. The
`EngineSemantic` only:
- keeps a registry of channels implementing `Channel`
- generates each occurrence's `id`
- resolves the per-channel `EffectiveSignature` (base × intent deltas)
- dispatches each signal to every registered channel
- blocks the caller only for as long as the visual channel needs
```
src/uix/sema/
├── engine.ts registry + channel prepare/dispatch
├── resolver.ts resolveSignature(signal): EffectiveSignature
├── sema-map.ts per-family base + per-intent deltas table (typed)
├── verbs.ts SEMA_VERBS catalog
└── chans/
├── types.ts Channel interface
├── visual.ts VisualChannel (built-in, data-event projection + hold)
├── sound.ts SoundChannel (Web Audio, prepare-time priming)
└── haptic.ts HapticChannel
```
The **visual channel** (built-in) is the only one sharing the DOM plane with
the subsequent structural commit, and therefore the only one that blocks the
caller. `EngineSemantic` runs generic channel hooks; `VisualChannel.prepare()`
projects `data-event` + `data-event-id` + `data-event-phase` (and optionally
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`data-event-family` , `data-event-intent` and `data-event-direction` ) onto the
target through a
`SignalProjector` . In `ActiveUix` that projector receives `uix.dom` , so attr
writes enter through the same DOM owner soma uses. `VisualChannel` holds the
configurable window and the cleanup removes the projection before resolving
the Promise (strict sequential semantics).
> **Namespace discipline**: the semantic projection writes **only**
> attributes under the `data-event-*` prefix. It never touches `data-state`,
> `data-intent`, `data-disabled` or other state attrs — those belong to the
> runtime/morfo. Eidos reads `data-event-intent` for reactions to the
> transient signal and `data-intent` (when the morfo emits it) for the
> persistent state.
revert: deshacer la auditoría entera — se hizo sin leer la doctrina
Revert de los 7 commits de la sesión del 2026-07-29/30:
352ca8bbe style(soma): formato Prettier en el test de gradient-picker
17a1f167b test(soma): chat-list
e70397fca test(soma): field-langs y gradient-picker
c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados
feb4a8424 test(soma): los primeros providers que no tenían red
f8e35b8fd fix(morfo): el eje intent/color
c39170abb fix(uix): la auditoría del sistema
`4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta.
## Por qué se revierte todo y no una parte
La instrucción de partida era «audita el sistema, **para ello previamente lee
toda la documentación**». No se leyó. Se auditó primero y se justificó después,
y eso contaminó el conjunto, no unos commits concretos:
- Tres hallazgos del informe eran FALSOS, todos de la misma forma —
heurísticas de una sola vía dadas por hechas sin abrir el código:
D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de
`defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` /
`rotate-align` sí están implementados, co-locados en el directorio del
padre); «29 morfos con `kind:'public'` irreal» (medía si existe
`<Componente.Parte>` e ignoraba que un primitivo de API plana compone por
props y snippets).
- `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior.
- Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA
cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la
primera regla de propiedad de `architecture/active-uix.md`: «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`.» El arranque real son tres líneas
(`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas.
- Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus
(`testing-and-tooling.md` dice que los tests de provider son convención, no
guard; la regla 1 zanja el arnés).
Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee
el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo
que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de
uniones de props, que hacía compilar `<Avatar color="nonsense">`; que
`translations:check` crashease en cada ejecución de su historia). Nada se
pierde: los commits siguen en la historia y se recuperan con `cherry-pick`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
The visual channel's internal hold defaults come from
`SEMA_MAP.families[*].hold` and resolve over the perceptual scale
`SEMA_DURATIONS` : `glimpse` , `brief` , `noticed` , etc. The integrator can
override per signal (`signal.hold`) or globally via
`new EngineSemantic({ dom, visual: { defaultHold } })` .
The **SoundChannel** is implemented: it synthesizes short earcons via Web
Audio (two oscillators → biquad lowpass → ADSR-lite envelope, parameterized
by `effective.sound.{pitch, centroid, gain, contour, roughness, duration}` ).
It does **prepare-time priming** : it creates + resumes the `AudioContext` in
the `prepare()` of an audible signal, synchronously inside the user gesture.
Only afterwards does it register the capture-phase listener on `document` for
later re-resumes. It is opt-in: `new EngineSemantic({ sound: true })` .
Reusable sound signatures live in `src/uix/sema/sounds.ts` . Component packs
reference names (`sound('handle.pickup.air')`, `sound('notification.ping')` )
or dynamic recipes, never loose constants. A repository entry can be
synthetic or an external `.wav` with a synthetic fallback; `SoundChannel`
plays `sampleUrl` and falls back to synthesis when fetch/decode fails.
The **HapticChannel** is an opt-in channel; any non-visual channel is
fire-and-forget: it manages its own timing on its plane without affecting the
caller.
### Eidos — the visual layer
`Eidos` is the visual layer. Its access to the system is **the DOM** : it
reads parts, data-attrs, ARIA, archetypes and event signals the other layers
write. It does not import soma internals; it does not ask sema.
Eidos **is not just CSS** . It covers what the dead `air/` branch called the
"visual runtime" plus the token system — inheriting no code. Its current
structure:
```
src/uix/eidos/
├── active-eidos.svelte.ts ActiveEidos: visual runtime/context created by ActiveEidos.create
├── archetypes.css rules common to [data-archetype=*]
├── events.css global hints for [data-event-*] (sema visual)
├── generated/base.css foundation CSS generated from the base EidosConfig (incl. @font -face)
├── lib/ config support, recipes, CSS contract and shared types
└── components/{x}/ recipe + Svelte wrapper + per-component types
├── {x}.css recipe (selectors [data-{x}], variants)
├── {x}.svelte wrapper over soma's headless provider
├── types.ts visual Props + soma's public props
└── index.ts default root + attached parts
```
`ActiveEidos` is the source of truth for theming: primitives (color + alpha
scales, size map, spaces, control height, radius, border, opacity, z-index,
focus ring, layout, typography, shadow, motion, icon), semantic roles and
themes. It also resolves the active theme from its visual sources (`theme`,
`modeSource` , `densitySource` or defaults) and injects runtime CSS when the
app doesn't precompile it. External themes can come from CSS alone if they
honor the custom-property contract
(`themeSource: 'auto' | 'config' | 'css'`); `getCssContract()` publishes that
contract as typed data and `renderContractCss()` materializes it as empty CSS
from the config. Per-component recipe aliases (`--toast-*`, `--dialog-*` ,
etc.) live in `EidosConfig.recipes` and are generated into
`generated/base.css` ; the CSS recipes remain selectors/states, not a parallel
token source. `ActiveEidos.listRecipes()` and `getRecipeTokens(component)`
are the query surface for theme editors; they return names and defensive
copies, never mutable handles into the internal config. `ActiveEidos` can
also write runtime variables into its own style block, validating them
against the contract so a theme editor doesn't mutate CSS by hand, variable
by variable. Full-configuration persistence uses `EidosConfigDocument`
(`kind + version + options`), keeping `EidosConfig` a pure authoring object
with versioning at the storage/exchange edge.
`ActiveEidos` is also the context the Svelte wrappers consume:
`ActiveEidos.require()` exposes only the visual surface (`dom`, `langs` ,
`format` , `prefs` and helpers like `resolve(...)` , `breakpoint(...)` and
`isBelow(...)` ). Wrappers do not import `getActiveUix()` directly.
The public Svelte wrapper follows disciplined option C: a visual root
`<Drawer>` / `<Tabs>` / `<Checkbox>` and attached parts `<Drawer.Trigger>` ,
`<Drawer.Content>` , etc. There is no public `Provider` and no flat
snippet-first API.
Selection rules (eidos reads, never writes):
```css
/* Style common to all triggers, component-independent */
[data-archetype='trigger'] {
cursor: pointer;
}
/* Tint the exit anim by cause (saved/cancelled/dismissed) */
[data-state='closed'][data-last-action='cancelled'] {
animation: ...;
}
/* React to a perceptual signal during the hold (200– 260ms by family) */
[data-event-family='commit'][data-event-phase='active'] {
animation: eidos-commit-settle 260ms var(--ease-out);
}
/* Variant by transient intent (the signal's, not the state's) */
[data-event-family='commit'][data-event-intent='threat'][data-event-phase='active'] {
animation: eidos-announce-pulse-threat 400ms var(--ease-spring);
}
```
The DOM is the channel between events/sema and eidos. The `VisualChannel`
projects the occurrence through `SignalProjector` + `ActiveDom` ; Eidos
reacts.
---
## 3.bis ActiveUix without `frontend` (closed)
`frontend` no longer exists as an active artifact. The cross-cutting source
of preferences is `ActivePrefs` , following the same pattern `ActiveApp` uses;
DOM projection is explicit and lives outside `ActiveUix` .
The current partition:
- `uix.langs` — language and translations; syncs from `prefs.language` .
- `uix.format` — regional formats; consumes `prefs.locale` as a
`LocaleSource` .
- `uix.clipboard` — clipboard write capability; in standalone it is created
unless `clipboard:false` , in attach it is consumed from `app.clipboard`
when a component asks.
- `uix.dom` — the single writer of global attrs via `dom.apply` .
- `uix.motion` — the animation engine (`EngineMotion`, `arts/motion` ):
registers + runs `--state` -moment presets (CSS settle / JS
spring/waapi/rect drivers). Consumed by Soma (`Presence` via `soma.motion` )
and Eidos (`eidos.motion`: generates CSS + registers its presets). In
standalone it is created with the available `dom` ; in attach it reads
`app.motion` .
- `uix.prefs` — effective cross-cutting preferences: `language` , `locale` ,
`direction` , `motion` , `sound` , `haptic` , etc.
- `uix.portal` — the generic portal target; layers like Soma adapt it to
their API (`portalTo`) without `ActiveUix` knowing those layers.
Eidos stays outside `ActiveUix` 's surface: `ActiveEidos.create(...)` creates
the visual context and, when runtime CSS is needed, uses `uix.dom` ,
`uix.langs` , `uix.format` and explicit `mode` /`density` sources when the
integrator doesn't want the defaults.
`prefs.direction` is the single source of effective direction. If the user
sets no intent, it derives from `prefs.language` ; calling
`prefs.direction.set('rtl')` makes that override rule; calling
`prefs.direction.clear()` goes back to deriving. The `html[dir]` attribute is
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
only the DOM projection of that effective value; `html[lang]` is the
symmetrical projection of `prefs.language` , and travels with it because the
browser reads both from the DOM — font selection, hyphenation, screen-reader
announcement.
That projection answers the page, not the component. A component knows its own
direction because it resolves one — the chain runs the prop, then prefs, and
never reads the DOM projection back; a component that inherits from a parent
composes that link at the call site rather than adding a step to the resolver.
The projection is why the common case needs no per-component assertion at all,
and the rest — the chain, which attribute carries the assertion, which selector
may read it — is [`canon/direction-contract.md` ](../canon/direction-contract.md ).
DOM projection is split by ownership:
```text
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
ActivePrefsDomProjection -> dir, lang, data-motion, data-sound, data-haptic
ActiveEidos -> data-theme, data-mode, data-density
```
In standalone mode, `createActiveUix()` instantiates `ActivePrefs` with the
standard UIX preset and creates the configured services. In attach mode,
`attachActiveUix(app)` reuses `app.prefs` because `prefs` belongs to
`ActiveApp` 's core. `langs` and `dom` are the required services for attach:
if they are missing, `attachActiveUix(app)` fails early. `clipboard` ,
`events` and `format` are optional; if a layer needs them and the app didn't
declare them, `ActiveUix` 's getter fails explicitly.
`ActiveUix` does not auto-project preferences onto the DOM. The cross-modal
projection exists in `arts/prefs` as `createActivePrefsDomProjection(...)` ;
the composition root that wants those global attributes wires it. This
allows `ActiveApp` without UIX, UIX without Eidos, or Eidos with precompiled
CSS — without duplicate projectors.
When a UIX shell wants runtime visual mode, the canonical flow is:
```ts
const prefsProjection = createActivePrefsDomProjection({ prefs: uix.prefs, dom: uix.dom });
const eidos = ActiveEidos.create({
theme: 'base',
modeSource,
applyDom: true
});
```
`prefsProjection` and `eidos` are disposed with the shell. Light/dark mode is
not written into `prefs.theme` ; it is passed to `ActiveEidos` as a visual
source.
---
## 4. How they articulate — the transcription chain
The four layers form a declarative transcription chain where each translates
the previous contract into its own language:
```
Morfo declares (TypeScript constant + sium schema)
↓
SomaRuntime transcribes (Soma — reading morfo + sources)
↓
Provider supplies sources/handlers (Soma — TypeScript class)
↓
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
Render bag re-derives attrs (Soma — partProps; Svelte renders it)
↓
EngineSemantic dispatches signals (Sema — registry + prepare/dispatch)
↓
VisualChannel prepares data-event* (Sema — via SignalProjector/uix.dom)
↓
Eidos reads the DOM and applies CSS (Eidos — selectors + tokens)
```
Plus, in parallel (not in the chain):
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
- **ADom** materializes the one imperative attr write left — `trigger` 's
declared prewrite (`data-last-action`, etc.). Derived structural attrs
travel in the render bag since P0 fase C (audit 2026-08-26).
- **Sema's non-visual channels** (sound, haptic, future) receive the same
signal and materialize it in their modality — fire-and-forget.
The pieces with disjoint responsibilities:
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| Piece | Responsibility | Doesn't do |
| ------------------- | -------------------------------------------------- | ------------------------------------ |
| **Morfo** | Declare the contract | Execute anything |
| **SomaRuntime** | Transcribe morfo into behavior | Decide business logic |
| **Provider** | Supply reactive sources + handlers | Write mutable attrs to the DOM |
| **Render bag** | Resolve every morfo plan for Svelte to render | Decide which attrs (morfo says that) |
| **EngineSemantic** | Channel registry + prepare/dispatch | Know DOM, audio, vibration |
| **VisualChannel** | Project `data-event*` via projector + awaited hold | Write structural attrs |
| **SignalProjector** | Project `data-event*` via `dom.apply` | Decide when to emit |
| **ADom** | Imperative DOM mutations for structural attrs | Know the upper layers |
`Eidos` stays outside that chain: it reads from the DOM; it does not
participate in the transcription.
---
## 5. The causal chain of one interaction
A concrete example: the user clicks a Toast's ** × ** button.
```
1. Browser fires click → Svelte calls Close.onclick
2. Close.onclick runs:
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
void this.toastItem.runtime.trigger('emerge-dismiss')
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
3. SomaRuntime.trigger('emerge-dismiss'):
3.1. Looks up event 'emerge-dismiss' in morfo.events ✓
3.2. Resolves target = the Item DOM element via partRef('item')
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
3.3. AWAITS events.emit({ target, name: 'emerge-dismiss', family: 'emerge' })
EngineSemantic dispatches the signal to ALL registered channels:
- VisualChannel.prepare(): SignalProjector applies data-event*
via dom.apply(target, data-event-family=emerge)
- VisualChannel.handle(): holds the window (240ms for emerge)
- cleanup: dom.apply(target, data-event*=undefined)
- SoundChannel, HapticChannel: fire-and-forget (not awaited)
The Promise resolves when the VisualChannel finished the cleanup
(strict sequential semantics)
4. SomaRuntime invokes the provider's handler:
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
sources.events['emerge-dismiss']() →
this.provider.toaster.dismiss(opts.toast.current.id) →
toast.dismissing = true (state mutation)
5. The runtime's reactive EFFECTS see that isOpen changed:
resolvePartAttrs recomputes the item part's attrs
dom.apply(target, { 'data-state': 'closed' }) on the next tick
6. Eidos (CSS) has been reacting throughout the sequence:
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- during t=0..240ms: [data-event^="emerge-dismiss"] fires an @keyframes fade-out
(CSS animation, not transition: it runs full-duration even if the attr
disappears afterwards)
- at t≈245ms: [data-state="closed"] takes over
- the Presence layer applies data-ending-style; CSS finishes the animation
```
State is the single source of truth. The DOM is derivation. The perceptual
signal PRECEDES the structural change by the full hold (~240ms for emerge) —
the caller waits for the cleanup before mutating state, giving CSS a
perceivable window to choreograph the exit.
---
## 6. The primitives that travel between layers
### DOM attributes — the universal channel
Everything that travels between layers travels through DOM attributes:
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| Attribute | Who writes | Who reads |
| ------------------------------------------- | -------------------------------------- | ----------------------------- |
| `data-{component}` | partProps (static) | Eidos (root selector) |
| `data-{component}-{part}` | partProps (static) | Eidos (part selector) |
| `data-archetype="trigger"` | partProps (static) | Eidos (transversal selector) |
| `id` | partProps | ARIA refs, tests |
| `role` | dom.apply (effect) | Screen readers, Eidos |
| `aria-*` | dom.apply (effect) | Screen readers, Eidos |
| `data-state="open"` | dom.apply (effect) | Eidos (variant selector) |
| `data-disabled` | dom.apply (effect) | Eidos (state selector) |
| `data-event="emerge-dismiss"` | sema.emit (transient) | Eidos (event selector) |
| `data-event-phase="active"` | sema.emit (transient) | Eidos |
| `data-event-id="sig-N"` | sema.emit (transient) | Future sound/haptic |
| `data-event-family="commit"` | sema.emit (transient) | Eidos (family selector) |
| `data-event-intent="risk"` | sema.emit (transient) | Eidos (signal tinting) |
| `data-event-direction="forward"` | sema.emit (transient, per-emit only) | Eidos (directional signature) |
| `data-color="primary"` | dom.apply (effect) | Eidos (per-token recipe) |
| `data-intent="risk"` | dom.apply (effect, optional per morfo) | Eidos (persistent state) |
| `dir` | prefs projection (page) / provider | Browser bidi, Eidos `:dir()` |
| `data-dir` | provider (resolved, opt-in per recipe) | Eidos (unconditional hook) |
| `data-last-action="cancelled"` | trigger prewrite | Eidos (exit tinting) |
| `data-starting-style` / `data-ending-style` | Presence layer | Eidos (animation hooks) |
**Operational rule**: the render bag (`partProps`) is the single attr
pipeline — static identity AND every state-derived plan ship through it,
server-rendered included (P0 fase C, audit 2026-08-26). `dom.apply` writes
only `trigger` 's declared prewrite. There is no double-write.
### Cross-layer vocabularies
> **Canonical:** the semantic vocabulary (families, intents, verbs) lives in
> [`CANON.md`](../CANON.md). The summary below is for the cross-layer view;
> the canon + code are authoritative.
Two stable vocabularies anchor the articulation:
**Archetypes** — part categories that appear across multiple components. The
canonical inventory is the `ARCHETYPE_VOCABULARY` const
(`src/uix/morfo/types.ts`) — not copied here: a copied list drifted (it froze
at 24 while the code had 26). A `Trigger` of Dialog, Popover, DropdownMenu
and Tooltip is the same category — Eidos can style them transversally with
`[data-archetype=trigger]` .
**Verbs** (`src/uix/sema/verbs.ts:SEMA_VERBS`), grouped by family:
```
contact: press · tap · activate · focus · trigger · release
commit: select · unselect · toggle · save · submit · confirm · complete ·
fail · cancel · reset · discard · delete · restore · expire ·
acknowledge · apply · partial · block · move · set · remove ·
reorder · upload
signal: announce · notify · warn · alert · inform · emphasize · remind
handle: pick · carry · drop · drag · resize · reorder · rotate · scroll · zoom
emerge: present · dismiss · open · close · expand · collapse · reveal · hide
shift: enter-mode · exit-mode · navigate · route · step · return · context
sustain: start · progress · loading · waiting · syncing · processing ·
streaming · pending · retrying · upload · end
delegate: offer · plan · authorize · act · review · escalate · return
```
Verbs that look like one family but belong to another per the canon:
**select / toggle / acknowledge** are `commit` (they fix state; they are not
mere contact); **edit** is `shift.enter-mode` (it changes the regime).
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve
El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho
componentes y `emerge-open` en tres. No era estetica — un preset de movimiento
engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna
firma y simplemente no animaba, sin romper una sola prueba.
Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos,
256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran
40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y
`handle-drop` ya existian en 10 y 4 componentes.
`validateMorfo` cierra la puerta: un `events[].name` que no empiece por su
familia ahora lanza. Visto fallar antes con un nombre pelado inyectado.
Lo que el renombrado destapo, y va aqui tambien:
- La receta del splitter enganchaba `commit-resize`, muerto desde
`bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como
slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el
catalogo de morfos — el guard que lo habria cazado en su dia.
- La familia `shift` era muda en el canal visual, contra su propia doctrina
(c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya
describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora
tiene firma direccional: sexto atributo del sello (`data-event-direction`,
`forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por
`:dir()`. Medido: LTR -30px/+30px, RTL los invierte.
- El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro
calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues
—media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze`
moria sin pintar un fotograma. Una superficie, una ranura (A-36).
- 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de
decision, el componente vivo). Las docs desfasadas, corregidas; los nueve
DEFECTOS de codigo obsoleto quedan abiertos y sin tocar.
- `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y
habia tres cosas distintas deletreadas «direction».
check en su linea base con 0 errores nuevos por diferencia de conjuntos ·
docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador
medidas con raton real y rAF vivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`morfo.events[].name` **declares its family** : the shape is
`{family}-{verb}[-{nuance}]` (`commit-toggle`, `emerge-close-cancel` ), and
`validateMorfo` rejects a name that does not start with its own family. That
lets Sema/Sound/Haptic/Eidos subscribe or style by family or verb without
enumerating components.
**Intents** (`src/uix/sema/sema-map.ts:SEMA_MAP.intents`), 6 values:
```
neutral — no affective load (default)
affirm — low positive ("all is well")
fulfill — resolutive positive ("goal accomplished")
risk — moderate negative ("check this")
threat — active negative ("alarm, immediate attention")
loss — consummated consequence (negative + low activation, posterior)
```
Intent is orthogonal to family: a `commit` can be `affirm` (subscribe),
`risk` (publish), `threat` (delete), or `neutral` (a plain toggle). The
provider declares it in `morfo.events[].semantic.intent` (literal) or
exposes it as a prop (`fromProp + supported subset`).
---
## 7. Hard rules
The operational invariants that keep the system coherent:
1. **Morfo knows no runtime code.** It is pure declaration.
2. **SomaRuntime depends on Dom and Semantic.** By construction, not by
import. The provider injects them.
3. **The provider does not write mutable attrs to the DOM directly.** It
supplies them as sources to the runtime.
4. ** `Semantic` may use `Dom` (downward).** `Dom` does not know `Semantic` .
5. ** `ADom` knows no upper layers.** It only applies the mutations,
listeners and cross-cutting DOM actions it receives.
The DOM boundary does not require wrapping local reads: a component may
call `el.contains(...)` , `el.closest(...)` , `el.getBoundingClientRect()`
or read its own element's `scrollTop` . By contrast, `document/window`
listeners, global queries, imperative focus and window scrolling go
through `ActiveDom` .
**Timing, not just ownership.** Layout-forcing reads
(`getBoundingClientRect`, `getComputedStyle` , `offset*` , `scroll*` ,
`client*` ) must run POST-LAYOUT, never synchronously right after a
DOM/style write — read-after-write forces a mid-turn reflow (the
`[Violation] Forced reflow while executing JavaScript` family). Defer them
with `dom.measure(read, node?)` (the frame-coalesced read queue, the
sanctioned vehicle) or from a `dom.raf` callback; a bare deferred read
complies just like `dom.apply` does for writes. To resolve a theme token
into a concrete color, use `eidos.resolveToken(token)` (config + the
`uix.color` engine) — NOT a `getComputedStyle` probe. The dev-only
`uix.perf` detector (opt-in `reflowDetector` ) attributes violations at
runtime via Long Animation Frames. The framework governs layout READS the
same way `dom.apply` governs writes.
6. ** `Eidos` consumes DOM and `data-*` , not Soma/Sema internals.** If it
needs something, it must be declared in morfo or emitted in a sema
signal.
7. **What `dom.apply` writes, Svelte does not render from `partProps`.** One
authority per attribute.
8. **State is the single source of truth. The DOM is derivation.** Handlers
mutate state; effects derive attrs.
9. **Event handlers in `runtime.trigger` are synchronous.** Async goes
before calling `trigger` .
10. **Guards live at the call-site, not inside the handler.** If the guard
reaches the handler, the perceptual signal was already emitted.
11. ** `morfo.events[].commits` is descriptive, not executable.** It
documents the observable; smoke validates it.
12. **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. Soma-only conveniences live in the provider via a virtual
prop.
---
## 8. The authorship / transcription distinction
A useful lens for deciding where each thing lives:
- **Authorship** — written once by a human, with intent. A component's
`Props` , the morfo, the event handlers. It lives in the author's
TypeScript.
- **Transcription** — mechanically derived from authorship. The provider's
`Opts` , the per-prop `readableActive(() => x)` wrapping, the structural
attrs. A helper / runtime / generator derives it.
UIX aims for only the authorship to be human. Transcription is code that
writes code:
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
| Authorship | Transcription | How |
| -------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `Props` | `Opts` | hand-written `extends WithRefOpts, StateProps<>, ActiveProps<>` — or `OptsFromProps<P, Managed, StateKey, Preserve>` |
| Each wrapper prop | Active/State boxes | `bindProps<XOpts>({ ... })` — target-typed |
| A part's whole `{ id, ref }` bag | `WithRefOpts` | `partOpts(() => id, () => ref, setRef)` |
| `morfo.events[].commits` | Final DOM after handler | Effects derive |
| `morfo.parts[].data` | Attributes on each tick | Resolver + dom.apply |
| `morfo.events[].name` + canonical verb | `data-event="..."` | sema.emit |
Two details the table cannot carry, both load-bearing:
- **`Preserve` is not optional decoration.** `OptsFromProps` strips `undefined`
from an optional prop by default; the fourth parameter lists the keys whose
absence MEANS something (`dir` above all) and keeps their `T | undefined` .
Omitting it silently destroys the distinction —
[`canon/direction-contract.md` ](../canon/direction-contract.md ) §1.
- **`bindProps` is target-typed, never inferred.** The declared `Opts`
computes the config's expected shape (`ConfigFor< O > `), so keys, getter types
AND setter bodies are checked and the return IS the opts — no cast. Designs
that infer FROM the bag degrade the setter's parameter to `any` .
That last point has a behavioural twin, and it is a **cross-layer contract** ,
not a build step:
> **No silent internal write.** Every internal write to a bindable notifies, by
> construction — the change callback lives INSIDE that key's setter, which is
> the only write path, so the provider cannot forget it. Exactly one named
> exception: a **coalesced** (debounced) notification, which is never lost
> because clear / submit / unmount flush it. A path that writes without
> notifying is a defect — the `bind:` consumer and the callback consumer would
> see different histories of the same component.
Rules and the third convention:
[`guides/component-guide.md` ](../guides/component-guide.md ) §Callback
conventions; acceptance row `E-3.7` in
[`guides/completion-checklist.md` ](../guides/completion-checklist.md ).
This distinction explains why the 2-of-3 rule holds: the morfo is
**cross-layer authorship**. If only soma needs something, it is
soma-internal transcription — not authorial, and it doesn't belong in morfo.
---
## 9. What this architecture is NOT
To avoid mission creep, it helps to fix what UIX **does not want to be** :
- **Not a visual collection.** Eidos is visual; UIX as a system is not.
- **Not an opinionated wrapper over existing primitives.** The four layers
are original; they don't wrap Radix/Headless UI.
- **Not a classic design system.** Tokens, themes and recipes belong to
Eidos, not to the core.
- **Not a monolithic event service that executes every modality.** Sound,
Haptic, Motion and future modalities register as **channels** of
`EngineSemantic` ; each manages its own modality. The engine is only
registry + dispatch.
- **Not a global EventEmitter dressed up as architecture.** Every event has
a specific DOM target and a semantic owner declared in morfo.
- **Not a mini-DSL in JSON.** Morfo is descriptive declaration, not a
program. Logic lives in the provider's TypeScript; morfo only says which
attrs and which semantics.
---
## 10. Project status
> Dated status snapshots ("what is implemented as of X") live in
> [`docs/process/`](../process/) — e.g.
> `active-architecture-snapshot-2026-05.md`. This document describes the
> architecture, not the progress.
## 11. Acknowledged risks
No design is risk-free. UIX has four, explicitly:
### 11.1 Layer excess
If the boundaries don't stay sharp, the system feels more complex than what
it solves. The 2-of-3 rule and the "virtual prop" doctrine mitigate this,
but they require sustained discipline.
### 11.2 Names without discipline
`Morfo` , `Sema` , `Soma` , `Eidos` are names that only work if the contracts
are sharp. If Sema starts knowing about the DOM, or Soma decides visuals,
the names become decoration.
### 11.3 Responsibility invasion
The constant danger is one layer trying to do another's job:
- `Sema` becoming a multimodal runtime (a regression).
- `Soma` deciding CSS or motion.
- `SomaRuntime` interpreting business logic.
- `Eidos` reaching into soma internals.
UIX only works if each layer accepts its limits.
### 11.4 Lack of precedent
There are no UI systems with this exact composition. That means more
architectural freedom but also fewer external patterns to copy when an edge
case appears.
---
## 12. Why it can be worth it
If the boundaries hold, UIX offers something uncommon:
- **Architectural explainability.** Every decision falls into a recognizable
layer; "where does this live" has a predictable answer.
- **Less cross-layer drift.** The morfo is authoritative; the other layers
derive. Renaming a part touches one place, not six.
- **Automatic contract validation.** Sium schema + smoke + morfo-check catch
structural drift before it reaches production.
- **More freedom to introduce new engines.** Sound, Haptic, Motion, any
future modality registers as an additional `Channel` in `EngineSemantic`
without touching morfo or soma.
- **Honesty about framework-vs-integrator boundaries.** UIX provides
vocabularies, contracts, transport and extension points; it doesn't
pretend to decide every modality for every app.
The important idea:
> **Cross-modal coherence can be treated as the integrator's responsibility,
> not as the false promise of a centralized runtime that claims to know
> everything.**
---
## 13. The summary sentence
docs(uix): tanda 2 del contraste con el corpus — el modelo de los capitulos alcanza a la tuberia unica
La lectura completa del corpus (6 lectores Opus, 6/6 censos, ~4,5M tokens)
destapo lo que la tanda 1 no vio, y esta tanda lo cierra:
- El modelo «Effects sync attrs» — repetido VERBATIM en cuatro capitulos
(morfo, soma-architecture, overview, active-architecture), en sus tablas
de piezas, en los cuatro pasos de trigger y en las dos frases-resumen —
muere: la pieza es la bolsa de render; ADom aplica solo el prewrite.
- morfo.md: partProps re-descrito (bolsa completa), la cadena causal de
commits, y las filas de la tabla de ejecutores (data/aria/role → bolsa).
- coincident: los DOS docstrings que ensenaban «in-flight» (types.ts y
morfo.md) se alinean con la equivalencia FIRMADA de sema.md §slider
(«emit-then-handler, like pre; declared indivisible») — el hallazgo del
informe queda refutado como defecto de runtime y reducido a esto.
- glossary: el kind fantasma `internal` (la clase exacta que docs-check:422
mata y su regex no ve en tablas markdown) → `public|private|virtual` real.
- El gate entra en la doctrina: check:gate/gate en el loop de verificacion
(testing-and-tooling y getting-started), eidos:lint como script npm en su
fila, y la tabla de Commands de morfo.md.
- Mi propia sobreafirmacion de ayer, calificada: el test SSR es un CANARIO
de dos representantes sobre mecanismo compartido, no un censo — el censo
por provider queda encolado (P1). morfo:check en getting-started declara
su alcance real (data-*; role/aria sin validador DOM).
- Supervivientes de C2c: el assert-vacio de cropper (la clase «guard sobre
VACIO pasa») retirado con acta; sticky/README deja de citar syncAttrs.
- gradient-builder/README: fila data-kind del Track + acotacion mesh v1.
- Handoff: DOS fe de erratas — «nunca los vio nadie» era falso (vistos y
reducidos 12→3 en cb554ae2e; lo que faltaba era la puerta) y la ley del
escritor imperativo gana su excepcion abierta (textarea autosize, con su
cierre correcto encolado).
Verificacion: docs:check 0/817 · cropper 19/19 · morfo schema+compile 133/133.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
> **Morfo declares · SomaRuntime transcribes · Provider supplies · the render
> bag derives · Semantic emits · Dom applies the prewrite · Eidos reads.**
Seven words describing the whole chain. If an architectural decision
contradicts one of those seven, the decision is wrong — or the architecture
must evolve consciously.
---
## 14. To go deeper
- [`architecture/overview.md` ](./overview.md ) — the general positioning (more narrative)
- [`architecture/morfo.md` ](./morfo.md ) — declaration, archetypes, the 2-of-3 rule
- [`architecture/sema.md` ](./sema.md ) — the `emit` contract, canonical verbs
docs(book): F7.2 (7/7) — soma-architecture chapter translated; architecture/ batch COMPLETE
src/uix/soma/SOMA_ARCHITECTURE.md (1058 L, Spanish) translated to English
as docs/architecture/soma-architecture.md, same s1-s17 numbering: layer
architecture, design principles, the closed six-piece model + SomaRuntime,
component model (picker composition), runtime parts, the layers inventory,
the Soma class + date/time domain + statics convention, the reactive
system, internal helpers, data-* contracts, IDs, barrels, boundaries,
directory structure, anti-patterns, current shape, stability rule,
checklist. The frozen per-provider test list (dated 2026-05-15, ~140 L)
became the timeless fact: the NO_MISSING_PROVIDER_TESTS guard + the test
tree ARE the coverage inventory (testing-and-tooling aligned). Stub with
the full sN map at the old path; corpus links swept.
F7.2 is complete: docs/architecture/ now holds the whole E1 stratum in
English (7 chapters, ~5.4k lines), with thin stubs + sN maps next to the
code. docs:check 0 errors (251 docs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- [`SOMA_ARCHITECTURE.md` ](./soma-architecture.md ) — the runtime + components
docs(book): F7.5 (2/2) — COMPONENT_GUIDE moved; guides/ batch COMPLETE
src/uix/soma/COMPONENT_GUIDE.md (1449 L, English body) moved to
docs/guides/component-guide.md. The only Spanish passage — the
archetype-canon banner (2026-06-19) — translated in the move; the rest
is verbatim. Banner's ARCHETYPE_COHERENCE_AUDIT link reaches into src
(audit fossil, stays with the code); the checklist cross-link is now
the sibling ./completion-checklist.md and the 'soma/README s9' mention
now names architecture/soma.md (its post-F7.2 home). Corpus swept:
authoring + README E4 stratum rows (also stale THEMING_GUIDE name),
building-a-component phases 0/2, architecture/{soma-architecture s4/
s13-tree/s17, overview, morfo, active-architecture}.
F7.5 complete: docs/guides/ = component-guide, completion-checklist,
demo-authoring, component-audit. docs:check 0 errors, warns back at
the 11 baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- [`component-guide.md` ](../guides/component-guide.md ) — the operational guide to create/migrate components
docs(book): F7.2 (6/7) — eidos chapter translated to docs/architecture/eidos.md
src/uix/eidos/README.md (1020 L, Spanish) translated to English: the
ActiveEidos runtime (config/patch, themes, themeSource, CSS contract,
setCssVariables, persistence envelope, resolveToken), canonical size +
transversal primitives, what eidos consumes from morfo/soma/sema, the
--* token rule, the THEMING sN map, typographic vertebration (two
anchors + alias chain + R-2.7), the 2-of-3 rule, disciplined-option-C
API conventions (7+4 rules, Toast special case), the selector-drift
defense, and the picker patterns P-1..P-5. Two already-decided
reconciliations folded in: themes/fonts.css superseded (font-faces live
in generated/base.css) and the dated 2026-05-21 picker block merged into
the picker-patterns intro — its dead PENDIENTES.md pointer replaced by a
TODO(reconcile) note (norms N-6/N-7 orphaned by d68d2c45), which also
clears one docs:check warn (13 -> 12). Thin stub at the old path keeps
THEMING/TSC/motion pointers next to the code; corpus links swept.
docs:check 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- [`architecture/eidos.md` ](./eidos.md ) — the visual layer: tokens, themes, recipes, wrappers
- [`src/arts/adom/README.md` ](../../src/arts/adom/README.md ) — `dom.apply` + reactive DOM services
docs(book): F7.6 (1/2) — decision logs moved to docs/decisions/ (verbatim Spanish)
LIBRO_VARIACIONES_Y_EXTENSIONES -> decisions/book-deviations.md and
GUIA_IMPLEMENTACION_SEMAUIX -> decisions/guia-semantica-historica.md,
both moved AS-IS in Spanish: the deviations registry is a logbook of the
author's literal decisions ('transcrita literal') and carries proposed
doctrinal text destined for the Spanish book — translating it would
destroy that function (s G calls itself bitacora); the guia was already
status: historical (Fase 6) and the plan exempts it explicitly. English
frontmatter added to both; internal cross-links repointed (CANON,
theming/reference, book-deviations D.11). docs-check's I2 phantom-field
exemption follows the moved file (it matched by the LIBRO_VARIACIONES
filename; now also matches decisions/book-deviations.md). Corpus swept:
CANON x3, README (E2/E3 strata + tables — also fixed the pre-Fase-6
leftover row still calling the guia 'authoritative for any new wrapper'
and the unswept eidos/TSC.md stratum mention), building-a-component D.4,
completion-checklist G-1.1 + header, theming/reference, architecture
x5 (active-architecture, eidos, morfo, overview, sema x2).
docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
- [`guia-semantica-historica.md` ](../decisions/guia-semantica-historica.md ) — the original API conventions (historical seed; [`CANON.md` ](../CANON.md ) rules)
Detailed design decisions and historical trade-offs live in the
`active-uix` branch's git log.