docs: align cross-layer docs with channel-based Sema

Cleanup of stale references to the pre-refactor Sema model across
architectural docs:

- EngineSemantic → SemanticEngine (registry + dispatch) + VisualChannel
  (built-in materializer); engine no longer "depends on Dom"
- "1 rAF + 1 hold frame (~13ms)" → perceptually-anchored hold (240ms
  emerge/commit/handle, 600ms alert/sustain, 120ms contact)
- Promise semantics: sequential strict — cleanup BEFORE resolve, so
  structural commit lands AFTER perceptual feedback completes
- Toast cadence trace updated: prewrite folded into VisualChannel.handle;
  CSS uses @keyframes (animation), not transition

Touches: src/uix/README.md, src/uix/active_architecture.md,
src/uix/sema/README.md, src/uix/eidos/README.md, src/uix/morfo/README.md,
src/uix/soma/SOMA_ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
morfo-runtime
dev 5 months ago
parent b4ceb1bb09
commit 3d9a834886

@ -79,12 +79,15 @@ Su trabajo:
- definir las familias canonicas (`contact | commit | alert | handle | emerge | sustain`)
- definir los intents canonicos (`neutral | affirm | fulfill | risk | threat`)
- exponer `EngineSemantic.emit(event)` para que el provider publique ocurrencias
- garantizar la secuencia perceptiva: escribir senal `data-event*`, esperar 1 rAF
para que CSS la observe, resolver, mantener y limpiar
- exponer `SemanticEngine.emit(signal)` para que el provider publique ocurrencias
- mantener un registry de canales perceptivos (`VisualChannel`, `SoundChannel`,
`VibraChannel`, ...) y despachar la senal a cada uno
- semantica secuencial estricta: el `VisualChannel` (built-in) materializa la
senal `data-event*` en el DOM, la mantiene durante el `hold` configurado y la
limpia ANTES de que la Promise resuelva
`Sema` no decide que ocurrio (eso lo decide el provider). Solo orquesta la
ocurrencia que recibe.
ocurrencia que recibe y la despacha a los canales registrados.
Ver: [src/uix/sema/README.md](/G:/dev/svelte/vicen/src/uix/sema/README.md)
@ -160,8 +163,9 @@ Morfo declara
MorfoRuntime transcribe
Provider aporta sources, targets y handlers
Effects sincronizan attrs derivados
EngineSemantic emite senales perceptivas
ADom aplica mutaciones DOM
SemanticEngine despacha senales a canales perceptivos
VisualChannel materializa la senal en el DOM (data-event*, hold, cleanup)
ADom aplica mutaciones DOM (commit estructural)
```
### El reparto operativo
@ -190,11 +194,15 @@ eventos. Expone:
`Effects` (registrados por el runtime al montar) escuchan cambios en los
sources y aplican los attrs derivados via `dom.apply`.
`EngineSemantic` recibe el evento desde `runtime.trigger`. Escribe la senal
`data-event*` via `dom.apply`, espera 1 rAF, resuelve, mantiene la senal el
hold configurado y limpia.
`SemanticEngine` recibe la ocurrencia desde `runtime.trigger`. Genera el id,
despacha la senal a TODOS los canales registrados. El `VisualChannel`
(built-in) escribe la senal `data-event*` en el DOM directamente, mantiene los
attrs durante el `hold` configurado, los limpia y solo entonces resuelve la
Promise (semantica secuencial estricta). Los canales no-visuales (sound,
vibra) son fire-and-forget.
`ADom` solo aplica. No interpreta.
`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)`
@ -433,8 +441,9 @@ Morfo -> declara contratos
MorfoRuntime -> interpreta morfo dentro de Soma
Provider -> aporta sources, targets, handlers
Effects -> sincronizan state -> attrs
EngineSemantic -> emite senales perceptivas (depende de Dom)
ADom -> aplica mutaciones DOM
SemanticEngine -> registry + dispatch de senales a canales
VisualChannel -> materializa la senal en el DOM (data-event*, hold, cleanup)
ADom -> aplica mutaciones DOM (commit estructural)
Eidos -> materializa visualmente leyendo DOM
App -> compone servicios
```
@ -442,10 +451,13 @@ App -> compone servicios
Reglas duras:
- `Morfo` no conoce `Soma`, ni codigo de runtime
- `MorfoRuntime` lee `Morfo` y depende de `Dom` y `Semantic`
- `MorfoRuntime` lee `Morfo` y depende de `Sema`
- `Provider` no escribe attrs mutables al DOM directamente; los aporta como
sources al runtime
- `EngineSemantic` puede usar `Dom` (hacia abajo); `Dom` no conoce `Semantic`
- `SemanticEngine` no conoce DOM. Solo registry + dispatch
- `VisualChannel` accede al DOM directamente (no via `ADom`); el commit
estructural posterior va por `ADom`
- `Sema` y `ADom` son capas hermanas: ninguna depende de la otra
- `ADom` no conoce `Morfo`, ni `Sema`, ni `Soma`, ni `Eidos`
- `Eidos` consume DOM y `data-*`, no internals de `Soma` ni `Sema`
- Lo que `dom.apply` escribe, Svelte no lo renderiza

@ -186,14 +186,20 @@ Provider aporta sources/handlers (Soma — TypeScript class)
↓
Effects sincronizan attrs (Soma — $effect + dom.apply)
↓
EngineSemantic emite señales (Sema — orquestación rAF + hold)
SemanticEngine despacha señales (Sema — registry + dispatch a canales)
↓
ADom aplica mutaciones DOM (adom — setAttribute/removeAttribute)
VisualChannel escribe data-event* (Sema — Channel built-in)
↓
Eidos lee el DOM y aplica CSS (Eidos — selectores + tokens)
```
Seis piezas en sucesión, ninguna invade a la siguiente:
Más, en paralelo (no en cadena):
- **ADom** materializa los `dom.apply`/`dom.remove` que Soma le pide para
los attrs estructurales derivados (data-state, aria-*, etc.).
- **Otros canales de Sema** (sound, vibra, futuros) reciben la misma señal
que VisualChannel y la materializan en su modalidad — fire-and-forget.
Las piezas con responsabilidades disjuntas:
| Pieza | Responsabilidad | No hace |
|---|---|---|
@ -201,8 +207,9 @@ Seis piezas en sucesión, ninguna invade a la siguiente:
| **MorfoRuntime** | Transcribir morfo a comportamiento | Decidir lógica de negocio |
| **Provider** | Aportar fuentes reactivas + handlers | Escribir attrs mutables al DOM |
| **Effects** | Aplicar attrs derivados via `dom.apply` | Decidir qué attrs (eso lo dice morfo) |
| **EngineSemantic** | Orquestar señales perceptivas (`emit`) | Decidir cuándo |
| **ADom** | Mutaciones DOM imperativas | Conocer las capas superiores |
| **SemanticEngine** | Registry de canales + dispatch | Conocer DOM, audio, vibración |
| **VisualChannel** | Escribir `data-event*` con hold | Decidir cuándo emitir |
| **ADom** | Mutaciones DOM imperativas para attrs estructurales | Conocer las capas superiores |
`Eidos` queda fuera de esa cadena: lee del DOM, no participa en la
transcripción.
@ -222,12 +229,15 @@ Ejemplo concreto: el usuario clickea el botón **×** de un Toast.
3. MorfoRuntime.trigger('dismiss'):
3.1. Busca event 'dismiss' en morfo.events ✓
3.2. Resuelve target = Item DOM element via partRef('item')
3.3. PREWRITE — applies dom.apply(target, { ...event signal })
→ DOM ahora tiene data-event="dismiss" data-event-id="sig-N"
data-event-phase="active" data-event-family="emerge"
3.4. AWAIT semantic.emit({ target, name: 'dismiss', family: 'emerge' })
Sema espera 1 rAF — CSS observa la señal y arranca una transición
Sema resuelve la Promise
3.3. AWAIT semantic.emit({ target, name: 'dismiss', family: 'emerge' })
SemanticEngine despacha la señal a TODOS los canales registrados:
- VisualChannel: setAttribute(target, 'data-event', 'dismiss') +
'data-event-id', 'data-event-phase=active', 'data-event-family=emerge';
mantiene los attrs durante el hold (240ms para emerge);
removeAttribute al terminar el hold
- SoundChannel, VibraChannel: fire-and-forget (no awaited)
La Promise resuelve cuando el VisualChannel ha terminado el cleanup
(semántica secuencial estricta)
4. MorfoRuntime invoca el handler del provider:
sources.events.dismiss() →
@ -238,18 +248,18 @@ Ejemplo concreto: el usuario clickea el botón **×** de un Toast.
resolvePartAttrs recomputa los attrs del item part
dom.apply(target, { 'data-state': 'closed' }) en el siguiente tick
6. Sema mantiene data-event* el hold perceptual (240ms para emerge),
luego limpia: dom.remove(target, ['data-event', 'data-event-id', ...])
7. Eidos (CSS) ha estado reaccionando todo el tiempo:
- durante t=0..240ms: [data-event="dismiss"] dispara animation fade-out
- en t=5ms: [data-state="closed"] continúa la coreografía
6. Eidos (CSS) ha reaccionado durante toda la secuencia:
- durante t=0..240ms: [data-event^="dismiss"] dispara animation @keyframes
fade-out (CSS animation, no transition: corre full-duration aunque el
attr desaparezca después)
- en t≈245ms: [data-state="closed"] toma el relevo
- Presence layer aplica data-ending-style; CSS termina la animación
```
State es la única fuente de verdad. El DOM es derivación. La señal
perceptiva precede al cambio estructural en una ventana de un frame —
suficiente para que CSS coreografíe ambos.
perceptiva PRECEDE al cambio estructural por el hold completo (~240ms para
emerge) — el caller espera el cleanup antes de mutar estado, dándole a CSS
una ventana perceptible para coreografiar la salida.
---
@ -396,8 +406,9 @@ Para evitar mission creep, conviene fijar lo que UIX **no quiere ser**:
- **No es un design system clásico.** Tokens, themes y recipes pertenecen
a Eidos, no al núcleo.
- **No es un semantic engine centralizado que ejecuta todas las
modalidades.** Sound, Vibra, Motion y futuras modalidades **suscriben**
a `EngineSemantic`; no son parte de él.
modalidades.** Sound, Vibra, Motion y futuras modalidades se registran
como **canales** del `SemanticEngine`; cada uno gestiona su propia
modalidad. El engine es solo registry + dispatch.
- **No es un EventEmitter global disfrazado de arquitectura.** Cada
evento tiene un target específico en el DOM y un dueño semántico
declarado en morfo.
@ -407,7 +418,7 @@ Para evitar mission creep, conviene fijar lo que UIX **no quiere ser**:
---
## 10. Estado actual (2026-04-26)
## 10. Estado actual (2026-04-27)
**Implementado y verificado**:
- `morfo` — 66 componentes con declaración completa, sium validator,
@ -415,8 +426,10 @@ Para evitar mission creep, conviene fijar lo que UIX **no quiere ser**:
- `soma/morfo/runtime` — `partProps`, `registerPart`, `keydown`,
`trigger`. Cinco componentes piloto migrados (Toggle, Switch,
Collapsible, Toast, Avatar). 22/22 unit tests verdes.
- `sema` — `EngineSemantic.emit(signal)` con lifecycle rAF + hold +
cleanup. SEMA_VERBS catalog. 10 unit tests verdes.
- `sema` — `SemanticEngine` (registry + dispatch) + `chans/` modulares
(VisualChannel built-in escribe `data-event*` con hold per-family;
SoundChannel/VibraChannel placeholders). SEMA_VERBS catalog. 39 unit
tests verdes.
- `adom` — `dom.apply(change)`, `dom.remove(target, names)`. Toda la
superficie reactiva (viewport, breakpoints, BodyScrollLock,
DOMContext, RovingFocusGroup) ya estable.
@ -486,8 +499,8 @@ Si las fronteras se mantienen, UIX ofrece algo poco común:
- **Validación automática del contrato**. Sium schema + smoke + morfo-check
detectan drift estructural antes de que llegue a producción.
- **Más libertad para introducir engines nuevos**. Sound, Vibra, Motion,
cualquier modalidad futura suscribe a `EngineSemantic` sin tocar morfo
ni soma.
cualquier modalidad futura se registra como un `Channel` adicional en
`SemanticEngine` sin tocar morfo ni soma.
- **Honestidad sobre fronteras de framework vs integrador**. UIX provee
vocabularios, contratos, transporte y puntos de extensión; no finge
decidir por todas las modalidades de todas las apps.

@ -54,11 +54,18 @@ runtime) las cosas que necesita para generar selectores y reglas:
## Cómo Sema le sirve
Sema escribe `data-event*` al DOM cuando el provider llama `semantic.emit(...)`.
La señal vive ~13ms (1 rAF + 1 hold frame), luego se limpia. Durante ese
ventana, eidos puede:
- Aplicar transiciones que dependan de la señal: `[data-event=dismiss] { transition: ... }`.
Cuando el provider llama `semantic.emit(...)`, el `SemanticEngine` despacha la
señal al `VisualChannel`, que escribe `data-event*` al DOM. La señal vive el
`hold` configurado (240ms emerge/commit/handle, 600ms alert/sustain, 120ms
contact por defecto), luego se limpia. Esa ventana es perceptualmente
significativa — CSS tiene tiempo real para coreografiar feedback visible.
Durante esa ventana, eidos puede:
- Disparar animaciones que reaccionen a la señal:
`[data-event^=dismiss] { animation: eidos-dismiss-fade 320ms forwards }`.
Usar `animation: @keyframes` (no `transition`) para que el efecto corra full
duration aunque el attr se retire después.
- Tintar el commit con la causa (via `data-last-action`): `[data-last-action=cancelled]`.
- Diferenciar por intent: `[data-intent=risk] { ... }`.

@ -77,8 +77,9 @@ Morfo declares
MorfoRuntime transcribes
Provider supplies sources, targets, handlers
Effects sync attrs from state
EngineSemantic emits perceptual signals
ADom applies DOM mutations
SemanticEngine dispatches signals to perceptual channels
VisualChannel materializes the signal in the DOM (data-event*, hold, cleanup)
ADom applies DOM mutations (structural commit)
```
### What each morfo field maps to at runtime

@ -9,33 +9,34 @@ señales perceptivas.
- intents canónicos: `neutral`, `affirm`, `fulfill`, `risk`, `threat`
- normalización entre shape estructurado y label canónico
- validación mínima del dominio
- `EngineSemantic` como canalizador de ocurrencias
- `SemanticEngine` como registry de canales + dispatch de ocurrencias
`Sema` no decide qué evento ocurrió. El provider lo decide. `EngineSemantic`
recibe la ocurrencia y orquesta su materialización en el canal perceptivo
visual (DOM).
`Sema` no decide qué evento ocurrió. El provider lo decide. `SemanticEngine`
recibe la ocurrencia y la despacha a los canales perceptivos registrados.
Cada canal materializa la señal en su modalidad (DOM, audio, vibración).
## Qué ya no es
`Sema` ya no es un runtime multimodal.
`Sema` ya no es un runtime multimodal monolítico.
No contiene:
El `SemanticEngine` no contiene:
- resolver de canales
- sound/motion/color/presence engines
- mapa perceptivo por canal
- política global de accesibilidad por canal
- política global de accesibilidad
- mapa perceptivo cross-canal
- decisiones sobre qué efecto modal aplicar
Eso pertenece a capas futuras y separadas:
Eso vive en cada canal por separado:
- `EngineSemantic` publica ocurrencias semánticas
- `ActiveDom` materializa esas ocurrencias como `data-event*` en el DOM
- `SoundEngine`, `VibraEngine` y otros engines modales se suscribirán al engine
- `SemanticEngine` mantiene un registry de canales y despacha cada signal
- `VisualChannel` (built-in) materializa la señal como `data-event*` en el DOM
- `SoundChannel`, `VibraChannel`, futuros engines modales se registran como
canales independientes que reciben la señal y deciden cómo materializarla
en su plano
## El contrato `emit`
```ts
semantic.emit(event: SemanticEvent): Promise<void>
semantic.emit(signal: SemanticSignal): Promise<void>
```
Una sola firma. Cubre los tres escenarios cuando se compone con `dom.apply`:
@ -45,23 +46,29 @@ Una sola firma. Cubre los tres escenarios cuando se compone con `dom.apply`:
dom.apply(change)
// Cambio estructural con señal
await semantic.emit(event)
await semantic.emit(signal)
dom.apply(change)
// Señal sin cambio estructural
void semantic.emit(event)
void semantic.emit(signal)
```
### Semántica de la Promise
### Semántica de la Promise — sequential strict
`emit(event)` resuelve cuando:
`emit(signal)` resuelve cuando el `VisualChannel` ha completado su
materialización entera:
- la señal `data-event*` ya fue escrita al DOM
- ha pasado **un rAF** para que CSS pueda observarla y arrancar transitions
- todavía está visible en el DOM
1. la señal `data-event*` se escribió al DOM
2. los atributos vivieron en el DOM durante el `hold` configurado
3. los atributos ya fueron retirados (cleanup completo)
4. la Promise resuelve
No resuelve antes (no hay frame para que CSS reaccione) ni después de la
limpieza (la señal ya no estaría visible cuando el commit estructural entre).
Esto es **sequential strict**: el caller aplica el commit estructural
DESPUÉS de que la señal perceptiva haya terminado. No hay paralelismo entre
evento y cambio de estado.
Los canales no-visuales (sound, vibra) son fire-and-forget: arrancan en
paralelo con el visual pero no afectan al timing de la Promise.
### Ciclo de vida interno de `emit`
@ -191,19 +198,23 @@ validateEventName('frob-glob')
## Dependencias
- `Sema` puede usar `Dom` (`semantic.emit` llama a `dom.apply` para escribir
`data-event*`). Dependencia hacia abajo, legítima.
- `Dom` no conoce `Sema`.
- `Sema` recibe `dom` por construcción, no lo importa duro de `$uix/adom`.
- `SemanticEngine` no conoce DOM. Solo registry + dispatch.
- Cada canal gestiona su propia modalidad:
- `VisualChannel` accede al DOM directamente vía `setAttribute` /
`removeAttribute` sobre el `signal.target` (no via `ActiveDom`).
- Futuros canales (sound, vibra) accederán a sus APIs respectivas
(`AudioContext`, `navigator.vibrate`, etc.).
- `ActiveDom` ya **no** participa en el flujo de Sema. Sema y ADom son
capas hermanas sin dependencia entre sí.
## Regla de arquitectura
`Morfo` autoriza la semántica del componente.
`Sema` define el vocabulario canónico y orquesta la señal perceptiva.
`Sema` define el vocabulario canónico y despacha señales a los canales.
`Provider` decide cuándo emitir.
`Dom` aplica.
Cada `Channel` materializa la señal en su modalidad.
Ver [src/uix/README.md](../README.md) §2.bis para la vista cross-layer.

@ -153,8 +153,9 @@ Morfo declara
MorfoRuntime transcribe (vive en soma/)
Provider aporta sources, targets y handlers
Effects sincronizan attrs derivados
EngineSemantic emite senales perceptivas
ADom aplica mutaciones DOM
SemanticEngine despacha senales a canales perceptivos
VisualChannel materializa la senal en el DOM (data-event*, hold, cleanup)
ADom aplica mutaciones DOM (commit estructural)
```
### MorfoRuntime — la pieza nueva

Loading…
Cancel
Save

Powered by TurnKey Linux.