|
|
5 months ago | |
|---|---|---|
| .. | ||
| chans | 5 months ago | |
| components | 5 months ago | |
| projection | 5 months ago | |
| README.md | 5 months ago | |
| define-engine-semantic.ts | 5 months ago | |
| durations.test.ts | 5 months ago | |
| durations.ts | 5 months ago | |
| emit.test.ts | 5 months ago | |
| engine.test.ts | 5 months ago | |
| engine.ts | 5 months ago | |
| errors.ts | 5 months ago | |
| event.test.ts | 5 months ago | |
| event.ts | 5 months ago | |
| exports.ts | 5 months ago | |
| index.ts | 6 months ago | |
| refactorizacion_codex.md | 5 months ago | |
| resolver.test.ts | 5 months ago | |
| resolver.ts | 5 months ago | |
| sema-map.ts | 5 months ago | |
| signal.ts | 5 months ago | |
| stamp.ts | 5 months ago | |
| types.ts | 5 months ago | |
| validation.ts | 5 months ago | |
| verbs.test.ts | 5 months ago | |
| verbs.ts | 5 months ago | |
README.md
Sema
Sema define el dominio semántico canónico de UIX y orquesta la emisión de
señales perceptivas.
Qué es
- familias canónicas:
contact,commit,signal,handle,emerge,shift,sustain - intents canónicos:
neutral,affirm,fulfill,risk,threat,loss - política de intent por familia (
SEMA_FAMILY_POLICY): determina cuándo el intent es obligatorio ('expected'), opcional ('allowed'/'optional') - normalización entre shape estructurado y label canónico
- validación mínima del dominio
EngineSemanticcomo registry de canales + dispatch de ocurrencias
Sema no decide qué evento ocurrió. El provider lo decide. EngineSemantic
recibe la ocurrencia y la despacha a los canales perceptivos registrados.
Cada canal materializa la señal en su modalidad (DOM, audio, vibración).
Handoff 2026-05-13
Sema ya tiene contrato explicito de DOM: si el canal visual esta activo,
EngineSemantic debe recibir dom o projector. Si se construye desde
ActiveUix, recibe el dom de ActiveUix; si se usa directamente fuera de
UIX, el integrador debe pasar un writer explicito o usar visual:false.
Sema no cae a escrituras DOM directas por defecto.
Tambien queda por decidir si emit debe seguir siendo secuencial estricto de
forma global o si la secuenciacion pertenece al evento/morfo. No cambiar esto
sin documentar antes la tabla de contratos minimos en
../active_architecture.md.
Qué ya no es
Sema ya no es un runtime multimodal monolítico.
El EngineSemantic no contiene:
- política global de accesibilidad
- mapa perceptivo cross-canal
- decisiones sobre qué efecto modal aplicar
Eso vive en cada canal por separado:
EngineSemanticmantiene un registry de canales y despacha cada signal- el proyector DOM materializa la señal como
data-event*en el DOM SoundChannel,HapticChannel, futuros engines modales se registran como canales independientes que reciben la señal y deciden cómo materializarla en su plano
Registry de canales — abierto
El conjunto de canales NO está cerrado. El framework ships con firmas
canónicas para sound y haptic. El canal visual existe como meta-canal
de proyección/hold, pero no tiene slice propia en EffectiveSignature.
Apps pueden añadir canales con firma vía declaration merging:
// app bootstrap
declare module '$uix/sema' {
interface SemaChannelSignatures {
a11y: A11ySignature; // narrador / live-region
voice: VoiceSignature; // text-to-speech
}
}
Sólo el visual channel es obligatorio (con escape visual: false).
Sound y haptic son opt-in. Cualquier canal nuevo registra su Channel y
recibe el dispatch.
El contrato emit
semantic.emit(signal: SemanticSignal): Promise<void>
Una sola firma. Cubre los tres escenarios cuando se compone con dom.apply:
// Cambio estructural sin señal
dom.apply(change);
// Cambio estructural con señal
await semantic.emit(signal);
dom.apply(change);
// Señal sin cambio estructural
void semantic.emit(signal);
Semántica de la Promise — sequential strict
emit(signal) resuelve cuando el VisualChannel ha completado su
materialización entera:
- el
VisualChannel.prepare()proyectódata-event*al DOM - los atributos vivieron en el DOM durante el
holdconfigurado - los atributos ya fueron retirados (cleanup completo)
- la Promise resuelve
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, haptic) son fire-and-forget: arrancan en paralelo con el visual pero no afectan al timing de la Promise.
Ciclo de vida interno de emit
1. Engine genera id/session de la ocurrencia
2. Engine ejecuta `channel.prepare(...)` en los canales registrados
- `VisualChannel.prepare()` proyecta `data-event*` para la cascade
3. Engine resuelve la cascade y despacha la señal a TODOS los canales registrados
- canales no-visuales (sound, haptic) → fire-and-forget (no awaited)
- canal visual → awaited
4. Engine resuelve la Promise cuando el visual ha terminado
(semántica secuencial estricta: cleanup ANTES del resolve)
Canales como módulos
Sema está organizada en canales perceptivos simétricos:
src/uix/sema/
├── engine.ts registry + channel prepare/dispatch + cascade composition
├── resolver.ts resolveSignature(signal, opts): EffectiveSignature
├── stamp.ts stampEventAttrs / unstampEventAttrs (data-event-*)
├── sema-map.ts SEMA_MAP + SemaChannelSignatures registry + Sema
├── components/ per-component perceptual packs (CSEM)
│ ├── dialog.ts dialogSema — cascade rules + preloadSamples
│ ├── toast.ts (futuro)
│ └── …
└── chans/
├── types.ts interfaz Channel — handle(signal, effective)
├── visual.ts VisualChannel (data-event projection + hold)
├── sound.ts SoundChannel (Web Audio earcons + sample playback)
└── haptic.ts HapticChannel (Vibration API + categorical kinds)
El engine no muta atributos DOM directamente: ejecuta el hook genérico
channel.prepare(...). En el canal visual, ese hook delega la proyección a un
SignalProjector. Cuando lo construye ActiveUix, ese proyector escribe via
el ActiveDom de UIX. El engine resuelve cada signal en una
EffectiveSignature con hold, sound, haptic y futuros canales tipados,
y dispatcha (signal, effective) a cada canal. Cada canal lee su slice
(effective.sound para audio, effective.haptic para vibración, etc.) o
ignora el signature si no lo usa. Solo el canal visual bloquea al caller con
el hold perceptivo; los demás son fire-and-forget.
Resolver y sema-map — cascada de 6 capas
El engine, en cada emit:
- Ejecuta los
preparede canales. ElVisualChannelproyecta los tokens semánticosdata-event,data-event-family,data-event-intent,data-event-phase,data-event-idalsignal.target. Estos son los tokens que la cascade y la CSS de eidos leen. - Llama a
resolveSignature(signal, opts)que aplica la cascada de 6 capas (cada una sobreescribe la anterior):
1. FAMILY base — SEMA_MAP.families[signal.family].base
sound / haptic + activeChannels + hold
2. INTENT deltas — SEMA_MAP.intents[signal.intent] cuando exista;
numbers ADD por defecto — son modificadores)
3. soundPack URL — SEMA_MAP.soundPack[<family>-<intent>]
4. MORFO overrides — signal.overrides + signal.channels
(numbers REPLACE por defecto — son set values)
5. RUNTIME overrides — engineOpts.overrides.runtime (path-based globals;
baked into el map en el constructor; numbers REPLACE)
6. CASCADE rules — engineOpts.components (per-component packs prepended)
+ engineOpts.overrides.cascade (app-level appended;
gana en empate de specificity por declaration order).
Selectors CSS-like contra signal.target con los
data-event-* ya stampados; numbers REPLACE.
- Despacha a cada canal con la signature resuelta.
- Awaita el VisualChannel (que aporta el hold).
- Ejecuta el cleanup de los handles devueltos por
prepare.
Convención de overrides vs deltas — números:
- Capa 2 (intent.deltas):
pitch: -200significa "restar 200 al pitch base". Modificadores compositivos. - Capas 4, 5, 6 (overrides):
pitch: 720significa "set pitch a 720". Como CSS —gain: 0.4no añade, asigna. - Para sumar explícitamente desde una capa de override:
{ op: 'add', value: 100 }. - Para multiplicar:
{ op: 'multiply', factor: 1.2 }. - Para reemplazar primitivos no-numéricos:
{ op: 'replace', value: ... }.
Si signal.family falta o no está en el map, devuelve un
EffectiveSignature vacío — los canales hacen no-op.
Tokens semánticos — data-event-*
El VisualChannel.prepare() proyecta los siguientes attrs en signal.target
ANTES de resolver la cascade. Las rules con selectores sobre estos attrs
matchean nativamente vía target.matches() / target.closest():
| Attr | Valor | Origen |
|---|---|---|
data-event |
'close-after-fail' |
signal.name |
data-event-family |
'signal' |
signal.family |
data-event-intent |
'threat' |
signal.intent (cuando exista) |
data-event-phase |
'active' |
mientras dure el hold |
data-event-id |
'sig-42' |
id de ocurrencia |
Esos tokens son la superficie de contacto cross-channel: la cascade
de sema (sound, haptic y futuros canales) los lee con selectores CSS,
igual que el CSS de eidos los lee para tintar bordes / animar estados durante
el hold. Una sola superficie perceptiva, con dueños separados.
Cascade rules — forma plana CSS-like
interface SemaCascadeRule {
selector: string // CSS selector — matchea state attrs + tokens evento
priority?: number // override de specificity CSS (opcional)
channels?: readonly SemaChannelId[] // restringe / silencia canales activos
sound?: …
haptic?: …
}
Una rule = un selector + un block de deltas. Sin capa intermedia
overrides: { eventLabel: ... } — la identidad del evento se lee del
selector mediante los tokens [data-event*].
Cascade selectors — typed builder, no hand-written strings
Cascade rules in sema/components/*.ts MUST build their selector via
semaSelector(morfo, partKebab, matchers?) from $uix/morfo. The
helper closes the loop between morfo's part/event contract and the
selectors the cascade evaluates.
import { semaSelector } from '$uix/morfo';
import { dialogMorfo } from '$uix/morfo/components/dialog';
const onContent = (matchers?: Parameters<typeof semaSelector<typeof dialogMorfo>>[2]) =>
semaSelector(dialogMorfo, 'content', matchers);
cascade: [
// [data-dialog-content][data-event-family="commit"]
{ selector: onContent({ eventFamily: 'commit' }), haptic: { kind: 'tap' } },
// [data-dialog-content][data-event="close-after-fail"]
{ selector: onContent({ eventName: 'close-after-fail' }), sound: { sampleUrl: '/fail.wav' } },
// [data-dialog-content][data-event^="close-"][data-event-family="emerge"]
{
selector: onContent({ eventNamePrefix: 'close-', eventFamily: 'emerge' }),
sound: { contour: 'descending', pitch: { op: 'add', value: -150 } }
}
];
Compile-time guarantees:
partKebabis checked againstmorfo.parts[].kebab.eventNameis checked againstmorfo.events[].name.eventFamily/eventIntentare typed against the canonical sema unions.state/aria/pseudoaccept plain strings (the data-attr vocabulary is per-component and not yet typed-derived).
Renaming a part or event in morfo breaks the cascade at type-check time, not silently in production. Hand-written selector strings in cascade rules are a code smell — review them as drift.
Per-component packs — sema/components/{name}.ts
Cada componente trae su pack de defaults perceptivos en
src/uix/sema/components/{name}.ts, simétrico a soma y eidos:
import type { Sema } from '../sema-map';
export const dialogSema: Sema = {
name: 'dialog',
preloadSamples: ['/sounds/dialog/saved.wav', '/sounds/dialog/failed.wav'],
cascade: [
{
selector: '[data-dialog-content][data-event-intent="threat"]',
sound: { sampleUrl: '/sounds/dialog/failed.wav' },
haptic: { kind: 'error', pattern: [50, 80, 50, 80, 50] }
}
]
};
App importa los packs que use (tree-shakable):
import { dialogSema } from '$uix/sema/components/dialog';
defineEngineSemantic({
components: [dialogSema],
overrides: {
cascade: [
// App-level rules ganan sobre los packs en empate de specificity
{ selector: '#critical [data-dialog-content]', sound: { sampleUrl: '/x.wav' } }
]
}
});
preloadSamples se concatena entre todos los packs y se le pasa a
SoundChannel.preloadSamples() al crear el engine cuando la app lo pide
explicitamente — los WAVs pueden quedar decodeados antes del primer emit.
El listener global de unlock no se registra en el constructor del canal; se
instala solo cuando existe un AudioContext que desbloquear.
Canales y signatures
| Canal | Slice consumido | Comportamiento si no aplica |
|---|---|---|
visual |
effective.hold para hold |
Cae al family fallback table (SEMA_DURATIONS) o al defaultHold |
sound |
effective.sound (skip si 'sound' no en activeChannels) |
no-op |
haptic |
effective.haptic (Vibration API, respeta prefers-reduced-motion) |
no-op si no hay navigator.vibrate |
| (custom) | declaración merging del SemaChannelSignatures |
leído por el canal registrado |
Namespace de attrs del canal visual
El VisualChannel.prepare() proyecta solo atributos bajo el prefijo
data-event-*:
| Attr | Cuándo |
|---|---|
data-event |
siempre |
data-event-id |
siempre |
data-event-phase |
siempre ('active') |
data-event-family |
si signal.family |
data-event-intent |
si signal.intent |
Regla: el canal nunca toca state attrs (data-state, data-intent,
data-disabled, ...). El estado lo gestiona el runtime/morfo. Razón:
state es persistente y signal es transitorio; pisar el mismo nombre
fuerza al canal a borrar estado al limpiar (o a save/restore frágil si
el estado muta durante el hold).
Para CSS:
[data-event-intent='risk']→ reacciona al intent de la ocurrencia transitoria[data-intent='risk']→ reacciona al estado persistente del componente
Ambos pueden coexistir en el mismo elemento con semánticas distintas.
Hold — cadena de resolución del canal visual
El VisualChannel resuelve su hold (cuánto viven los data-event-*
en el DOM) en este orden:
signal.hold— override imperativo per llamada.effective.hold— viene del resolver desdeSEMA_MAP.families[*].hold.- Family fallback table (
SEMA_DURATIONS+ label per familia) — defensa si una familia externa no declarahold. defaultHoldglobal del canal (240ms por defecto).
Ver SEMA_MAP.families[*].hold para los valores concretos por familia.
// Override per signal
semantic.emit({ ..., hold: 1200 })
// Override default global del canal visual
const semantic = new EngineSemantic({ dom, visual: { defaultHold: 400 } })
// Desactivar visual (entornos sin DOM)
const semantic = new EngineSemantic({ visual: false })
// Activar el canal de sonido built-in (opt-in: tiene side effect audible)
const semantic = new EngineSemantic({ dom, sound: true })
// Con opciones de SoundChannel
const semantic = new EngineSemantic({
dom,
sound: { masterGain: 0.6 }
})
// Activar el canal háptico built-in (opt-in: feedback del dispositivo)
const semantic = new EngineSemantic({ dom, haptic: true })
// Con opciones de HapticChannel
const semantic = new EngineSemantic({
dom,
haptic: { masterIntensity: 0.7 }
})
// Registrar canales a medida (a11y, voice, futuros)
class A11yChannel implements Channel {
readonly id = 'a11y'
async handle(signal, effective) { /* live-region updates, etc. */ }
}
semantic.register(new A11yChannel())
Override layers — recipes
import { dialogSema } from '$uix/sema/components/dialog';
const semantic = new EngineSemantic({
dom,
sound: true,
haptic: true,
// Capa 6a — packs de componentes (defaults shipped con cada componente)
components: [dialogSema /* , toastSema, drawerSema, … */],
overrides: {
// Capa 5 — edits puntuales del SEMA_MAP, válidos a TODA la app
runtime: {
'families.commit.base.sound.pitch': 850,
'intents.threat.deltas.haptic.intensity': 0.4
},
// Capa 6b — rules CSS-like de la app, matched contra signal.target
// AFTER de los packs de componente
cascade: [
{
selector: '#delete-confirm-dialog [data-dialog-content][data-event-intent="threat"]',
sound: { sampleUrl: '/sounds/scary.wav', gain: 0.25 },
haptic: { kind: 'error', pattern: [50, 80, 50, 80, 50] }
},
{
selector: ':root[data-sound="reduce"] [data-event-phase="active"]',
priority: 100,
sound: { gain: 0.05 },
channels: ['sound']
}
]
}
});
// Capa 4 — per-event override declarado en la propia morfo del componente.
// Propaga vía SomaRuntime → SemanticSignal → resolver. La app puede
// seguir sobreescribiendo desde la cascade (capa 6).
// src/uix/morfo/components/dialog.ts
{
name: 'close-after-fail',
semantic: {
family: 'signal',
verb: 'alert',
target: v.partRef('content'),
sequence: 'pre',
intent: 'threat',
// Per-event silencing: sin sonido para evitar competir con el live-region
channels: ['haptic'],
// Per-event override: sample propio del Dialog
overrides: {
haptic: { kind: 'error', pattern: [60, 80, 60, 80, 60] }
}
}
}
Specificity entre rules
Como CSS:
- Specificity computada del selector — IDs × 100, atributos × 10, classes × 10, pseudo-classes × 10, elementos × 1.
- Rules con specificity ascendente se aplican en orden — la última aplicada gana en cada conflicto puntual.
- Empate → orden de declaración. Componentes packs aparecen ANTES
de
overrides.cascade, así app rules ganan en empate. priority?: number— override del valor computado para casos que necesitan ganar sin contar atributos (típicamente accesibilidadpriority: 100+).
Para variar por evento, intent, family, name, etc. — todo va en el
selector mediante los tokens [data-event-*]:
// Variación por intent del evento
{ selector: '[data-toast-root][data-event-intent="threat"]', haptic: { kind: 'error' } }
// Variación por family
{ selector: '[data-toast-root][data-event-family="signal"]', sound: { gain: 0.4 } }
// Variación por nombre exacto de evento
{ selector: '[data-toast-root][data-event="close-after-fail"]', sound: { sampleUrl: '/fail.wav' } }
// Variación por prefijo de evento
{ selector: '[data-dialog-content][data-event^="close-"]', sound: { contour: 'descending' } }
SoundChannel
Earcons cortos sintetizados vía Web Audio API a partir de
effective.sound (pitch / centroid / roughness / attack / decay /
duration / contour / gain). Detalles:
- Un único
AudioContextconGainNodemaster por engine. - Prepare-time priming, no side-effect en el constructor. El canal
crea + resume el
AudioContextenprepare()cuando la señal admitesound, de forma síncrona dentro del gesto de usuario. - Despues de crear el contexto, registra un listener de
click/touchstart/keydownendocument(capture phase) para re-resume tras suspends pasivos (tab switch, etc.). Si el canal nunca prepara una señal sonora, no instala listeners globales. - Síntesis: dos osciladores (sine + 5ª) → biquad lowpass (centroid)
→ envelope ADSR-lite. Si
roughness > 0.2, modulador AM rápido. contour(flat/ascending/descending/arc/bell) se aplica víaosc.detune.- Si la signature trae
sampleUrl, reproduce el sample (con caché deAudioBuffer) en lugar de sintetizar. - Cualquier fallo (no-AudioContext, decode failure) se absorbe — sema es ornamental.
El patrón prepare-time priming aplica en general a cualquier canal cuyo
backend tenga restricción de "primera vez ha de ocurrir en gesture":
audio, vibration, fullscreen, clipboard write. Documentado como
convención 12 en
src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md.
Política de errores
- Errores en canales NO-visuales se loguean pero no propagan. Sema es ornamental: un fallo de audio context o vibration API no debe abortar la operación del provider.
- Si el canal visual lanza, la Promise de
emitrechaza. El caller decide. - En fire-and-forget (
void semantic.emit(...)), una rejection del visual se propaga como unhandled promise — política consciente.
Política de intent por familia — SEMA_FAMILY_POLICY
La doctrina sobre cuándo el intent es obligatorio vive en una const en
src/uix/sema/types.ts. Tres niveles:
export const SEMA_FAMILY_POLICY = {
contact: { intentPolicy: 'allowed' }, // OPCIONAL — neutral por default
commit: { intentPolicy: 'expected' }, // REQUERIDO — toda consumación es evaluativa
signal: { intentPolicy: 'expected' }, // REQUERIDO — alarmas son inherentemente evaluativas
handle: { intentPolicy: 'allowed' }, // OPCIONAL — drag/scrub suelen ser neutrales
emerge: { intentPolicy: 'optional' }, // OPCIONAL — Dialog que abre para confirmar threat sí declara
shift: { intentPolicy: 'optional' },
sustain: { intentPolicy: 'optional' }
} as const;
Cada entry es un objeto — anticipa otros campos doctrinales por familia (default sequence, channels permitidos, hold preferences, gesture phases, …). Cuando se añadan se ubicarán dentro del mismo objeto sin reestructurar.
Cómo se aplica:
-
Compile time —
SemaEventyMorfoEventSemanticson discriminated unions derivadas de la const. Cambiar'optional'→'expected'encontact(por ejemplo) hace que cada morfo de toggle/switch/etc. tenga que declarar intent o falle el typecheck. -
Runtime —
validateSemaEventlanzaSemaInvariantErrorcuando un evento de family'expected'se construye sin intent (defensa contra morfos mal-formados o inputs externos).
Por qué esta política reemplazó al split valenced/transitional:
La doctrina canónica original asumía que sólo familias valenced (contact, commit, signal, handle) podían declarar intent. Las transitional (emerge, shift, sustain) eran intent-less por definición. La realidad UX lo contradijo: un Dialog que se abre para confirmar borrar algo destructivo carga threat en su misma aparición. La política distingue por NECESIDAD práctica de intent, no por categoría taxonómica.
La distinción valenced/transitional sigue existiendo como clasificación, pero ya no dicta las reglas de intent — eso lo hace la política.
Vocabulario canónico de verbs (SEMA_VERBS)
Cross-component action verbs grouped by family per
src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md
§1.3. morfo.events[].name debería alinear con este vocabulario para
que sema/sound/haptic puedan suscribir por verb y eidos pueda escribir
selectores transversales ([data-event^=dismiss]).
SEMA_VERBS = {
contact: ['press', 'tap', 'activate', 'focus', 'trigger', 'release'],
commit: [
'select',
'toggle',
'save',
'submit',
'confirm',
'cancel',
'complete',
'fail',
'delete',
'restore',
'reset',
'discard',
'expire',
'acknowledge',
'set',
'remove',
'reorder'
],
signal: ['announce', 'notify', 'warn', 'alert', 'emphasize', 'remind'],
handle: ['pick', 'carry', 'drop', 'drag', 'resize', 'reorder', 'rotate', 'scroll'],
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',
'end'
]
};
Verbs que parecen de una familia pero pertenecen a otra per la
canónica: select/toggle/acknowledge son commit (fijan estado);
edit se expresa como shift.enter-mode (cambia régimen, no hay verb
edit en handle).
Definido en verbs.ts:SEMA_VERBS.
Naming shapes
Un morfo.events[].name puede tomar dos formas canónicas:
// Forma 1: {verb}-{variant} — head es el verbo, tail explica el matiz.
'dismiss'; // bare verb
'dismiss-outside'; // verb + variant
'close-cancel'; // verb (close) + variant (cancel)
// Forma 2: {family}-{verb} — head es la familia, tail es el verb canónico.
'commit-toggle'; // family=commit, verb=toggle
'commit-save'; // family=commit, verb=save
validateEventName(name) reconoce ambas formas y devuelve { family, verb, variant, matchesCanonical }. Advisory — no rechaza morfos, solo
flagea drift para tooling y revisión.
import { validateEventName } from '$uix/sema';
validateEventName('commit-toggle');
// { name: 'commit-toggle', head: 'commit', matchesCanonical: true,
// variant: 'toggle', family: 'commit', verb: 'toggle' }
validateEventName('dismiss-outside');
// { name: 'dismiss-outside', head: 'dismiss', matchesCanonical: true,
// variant: 'outside', family: 'emerge', verb: 'dismiss' }
validateEventName('frob-glob');
// { name: 'frob-glob', head: 'frob', matchesCanonical: false,
// variant: 'glob', family: undefined, verb: undefined }
Relación con Morfo y Soma
Morfodeclara los eventos semánticos del componente enmorfo.eventsProviderdecide cuándo ocurren y llama asemantic.emit(...)SomaRuntimeorquesta la secuenciaprewrite -> emit -> handler -> effectsSemaaporta el vocabulario, la normalización y la validación del dominio, y publica las ocurrencias
Dependencias
EngineSemanticno escribe atributos directamente. Orquesta hooks de canales (prepare,handle,cleanup) sin conocer los attrs DOM.- Cada canal gestiona su propia modalidad:
VisualChannel.prepare()proyectadata-event-*y luego mantiene el hold perceptivo.DomSignalProjectores el escritor DOM usado por el canal visual; escribe mediante elActiveDomrecibido desdeActiveUix.- Futuros canales (sound, haptic) accederán a sus APIs respectivas
(
AudioContext,navigator.vibrate, etc.).
- En uso normal,
ActiveUixinyecta elActiveDomenEngineSemantic. El uso directo de Sema fuera deActiveUixdebe pasar undom/projectorexplicito o elegir una degradacion documentada.
Regla de arquitectura
Morfo autoriza la semántica del componente.
Sema define el vocabulario canónico y despacha señales a los canales.
Provider decide cuándo emitir.
Cada Channel materializa la señal en su modalidad.
Ver src/uix/README.md §2.bis para la vista cross-layer y
src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md
para las convenciones doctrinales del API (single-event para
operaciones instantáneas, intent ↔ visual token resolution, sound prepare-time
priming, etc.).