sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
/**
|
sema: rename SemanticEngine → EngineSemantic + .destroy() → .dispose()
Aligns sema's engine class with the new ecosystem's Engine* / Active*
naming convention (per src/arts/README.md):
- Engine* — public methods over private state. No reactive surface.
- Active* — Engine* extended with $state + onChange listeners.
Sema's SemanticEngine is purely imperative: a channel registry + an
async dispatch. No $state, no listeners, no loading/lastError. So by
convention it must be Engine*, not Active*.
Renames (mechanical):
SemanticEngine → EngineSemantic
SemanticEngineOpts → EngineSemanticOptions (also aligned to
EngineSiumOptions / EngineHttpOptions name)
.destroy() → .dispose() (uniform with the rest
of the ecosystem; only one call site, in
engine.test.ts)
Touched code: engine.ts, exports.ts, emit.test.ts, engine.test.ts +
README.md (kept consistent). Also sweep src/lib/ext/app/{defaults,
app.svelte}.ts which still mention the old name in fallback wiring.
Air's docs (AIR_*.md) and the legacy CONTINUITY note kept untouched
(dead branch).
This commit is purely a rename. The next commit (Phase 4 part 2) adds
the defineEngineSemantic factory and migrates soma + the 3 layouts to
createActiveApp.
Verification: sema 44/44, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
* EngineSemantic — registry de canales perceptivos + dispatch.
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
*
|
|
|
|
|
* El engine no conoce DOM, ni hold, ni atributos de estado. Su trabajo es:
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
*
|
|
|
|
|
* 1. Asignar un id estable a cada ocurrencia.
|
|
|
|
|
* 2. Ejecutar `channel.prepare(...)` antes de la cascade para que los
|
|
|
|
|
* canales publiquen su superficie temporal si la necesitan.
|
|
|
|
|
* 3. Resolver la `EffectiveSignature` con las 6 capas de cascade.
|
|
|
|
|
* 4. Despachar a cada canal registrado.
|
|
|
|
|
* 5. Esperar al canal visual y limpiar los handles de preparación.
|
|
|
|
|
*
|
|
|
|
|
* Override layers (ver `resolver.ts`):
|
|
|
|
|
* 1. family base — SEMA_MAP.families
|
|
|
|
|
* 2. intent deltas — SEMA_MAP.intents (any family with intent)
|
|
|
|
|
* 3. morfo per-event — signal.overrides + signal.channels
|
|
|
|
|
* 4. runtime path overrides — engineOpts.overrides.runtime
|
|
|
|
|
* (applied to map at construction)
|
|
|
|
|
* 5. cascade rules — engineOpts.components (per-component
|
|
|
|
|
* packs prepended) + engineOpts.overrides.cascade
|
|
|
|
|
* (app-level rules appended; win on tie)
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import type { Channel, ChannelPreparation } from './chans/types';
|
|
|
|
|
import type { DomApplier } from '$libs/dom';
|
|
|
|
|
import type { Logger } from '$libs/logger';
|
|
|
|
|
import { HapticChannel, type HapticChannelOptions } from './chans/haptic';
|
|
|
|
|
import { SoundChannel, type SoundChannelDom, type SoundChannelOptions } from './chans/sound';
|
|
|
|
|
import { VisualChannel, type VisualChannelOptions } from './chans/visual';
|
|
|
|
|
import { SemaDuplicateChannelError } from './errors';
|
|
|
|
|
import { applyMapOverrides, resolveSignature, type SemaCascadeRule } from './resolver';
|
|
|
|
|
import { SEMA_MAP, type SemaMap, type Sema } from './sema-map';
|
|
|
|
|
import type { DeltaValue } from './channels';
|
|
|
|
|
import type { SemanticSignal } from './signal';
|
|
|
|
|
import type { SignalProjector } from './projection';
|
|
|
|
|
|
|
|
|
|
export interface EngineSemanticOverrides {
|
|
|
|
|
/**
|
|
|
|
|
* Path-based mutations of the SEMA_MAP applied at construction. The
|
|
|
|
|
* canonical map is not mutated; only the overridden object path is cloned.
|
|
|
|
|
*/
|
|
|
|
|
runtime?: Record<string, DeltaValue>;
|
sema: revive resolver + signatures + real SoundChannel
Restores the resolver-driven architecture that was deleted in the
channel-modules refactor (commit a0a1485b). Each signal now flows
through a single resolution that produces an `EffectiveSignature`, and
each channel reads its own slice — so the visual hold, the sound earcon,
and (future) motion / color / presence channels all share a single
family-level perceptual budget defined in `SEMA_MAP`.
New surfaces:
- `src/uix/sema/sema-map.ts` — typed perceptual map (port of the legacy
`sema-map.json`) with per-family `base` (motion / sound / color /
presence) + `activeChannels` and per-intent `deltas` using the
number / `{op:'add'}` / `{op:'multiply'}` / `{op:'replace'}` /
recursive-merge semantics from before.
- `src/uix/sema/resolver.ts` — pure `resolveSignature(signal)` that
returns an `EffectiveSignature`. Empty signature when family is
missing so channels skip rather than throw. Intent deltas only apply
to valenced families. Optional `soundPack` per-event sample URLs.
- `src/uix/sema/chans/sound.ts` — real `SoundChannel` (~290 lines, ported
from a0a1485b^). Single AudioContext + master GainNode, auto-unlock
for Safari/iOS via `click` / `touchstart` / `keydown` with capture,
ADSR-lite synthesis (sine + 5th → biquad lowpass → envelope, AM
modulator when roughness > 0.2), contour via `osc.detune`, optional
sample playback with `AudioBuffer` cache. Errors absorbed silently —
sema is ornamental.
Architectural changes:
- `Channel.handle(signal, effective)` — every channel now receives the
resolved signature alongside the signal. Channels can ignore it.
- `EngineSemantic.emit` resolves once per emit, dispatches both args.
- `VisualChannel` hold resolution chain is now (highest priority first):
1. `signal.hold`
2. `effective.motion.duration` ← NEW: from sema-map
3. family fallback table (SEMA_DURATIONS)
4. engine `defaultHold`
Behavior change: families with motion in the map (alert: 220ms,
contact: 80ms, commit: 180ms, …) now use those values instead of the
previous label-based 600ms / 120ms / 240ms fallbacks. The fallback
table remains as a safety net for families without motion (sustain).
- `EngineSemanticOptions` adds `sound?: true | false | SoundChannelOptions
| Channel`. Sound is opt-in (audible side effects). Layouts wire
`defineEngineSemantic({ sound: true })`.
Tests:
- New `resolver.test.ts` (7 tests) — locks down family base, intent
deltas, multiplicative ops, transitional-family no-op, deep-clone.
- `visual.test.ts` — every `handle` call now passes a stub
`EffectiveSignature`. Two new tests: motion.duration takes the hold,
and signal.hold overrides effective.motion.duration.
- `engine.test.ts` — `makeChannel` mock signature updated.
- `emit.test.ts` — alert family default test now expects 220ms (from
sema-map) instead of 600ms.
Verified:
- vitest: 6 files / 53 tests green
- smoke: 68/68 soma routes 200 OK
- morfo-check: 66/66 morfos validate
5 months ago
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* App-level cascade rules (CSS-style). Concatenated AFTER all
|
|
|
|
|
* per-component packs so app rules win on tie (declaration order).
|
sema: revive resolver + signatures + real SoundChannel
Restores the resolver-driven architecture that was deleted in the
channel-modules refactor (commit a0a1485b). Each signal now flows
through a single resolution that produces an `EffectiveSignature`, and
each channel reads its own slice — so the visual hold, the sound earcon,
and (future) motion / color / presence channels all share a single
family-level perceptual budget defined in `SEMA_MAP`.
New surfaces:
- `src/uix/sema/sema-map.ts` — typed perceptual map (port of the legacy
`sema-map.json`) with per-family `base` (motion / sound / color /
presence) + `activeChannels` and per-intent `deltas` using the
number / `{op:'add'}` / `{op:'multiply'}` / `{op:'replace'}` /
recursive-merge semantics from before.
- `src/uix/sema/resolver.ts` — pure `resolveSignature(signal)` that
returns an `EffectiveSignature`. Empty signature when family is
missing so channels skip rather than throw. Intent deltas only apply
to valenced families. Optional `soundPack` per-event sample URLs.
- `src/uix/sema/chans/sound.ts` — real `SoundChannel` (~290 lines, ported
from a0a1485b^). Single AudioContext + master GainNode, auto-unlock
for Safari/iOS via `click` / `touchstart` / `keydown` with capture,
ADSR-lite synthesis (sine + 5th → biquad lowpass → envelope, AM
modulator when roughness > 0.2), contour via `osc.detune`, optional
sample playback with `AudioBuffer` cache. Errors absorbed silently —
sema is ornamental.
Architectural changes:
- `Channel.handle(signal, effective)` — every channel now receives the
resolved signature alongside the signal. Channels can ignore it.
- `EngineSemantic.emit` resolves once per emit, dispatches both args.
- `VisualChannel` hold resolution chain is now (highest priority first):
1. `signal.hold`
2. `effective.motion.duration` ← NEW: from sema-map
3. family fallback table (SEMA_DURATIONS)
4. engine `defaultHold`
Behavior change: families with motion in the map (alert: 220ms,
contact: 80ms, commit: 180ms, …) now use those values instead of the
previous label-based 600ms / 120ms / 240ms fallbacks. The fallback
table remains as a safety net for families without motion (sustain).
- `EngineSemanticOptions` adds `sound?: true | false | SoundChannelOptions
| Channel`. Sound is opt-in (audible side effects). Layouts wire
`defineEngineSemantic({ sound: true })`.
Tests:
- New `resolver.test.ts` (7 tests) — locks down family base, intent
deltas, multiplicative ops, transitional-family no-op, deep-clone.
- `visual.test.ts` — every `handle` call now passes a stub
`EffectiveSignature`. Two new tests: motion.duration takes the hold,
and signal.hold overrides effective.motion.duration.
- `engine.test.ts` — `makeChannel` mock signature updated.
- `emit.test.ts` — alert family default test now expects 220ms (from
sema-map) instead of 600ms.
Verified:
- vitest: 6 files / 53 tests green
- smoke: 68/68 soma routes 200 OK
- morfo-check: 66/66 morfos validate
5 months ago
|
|
|
*/
|
|
|
|
|
cascade?: readonly SemaCascadeRule[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface EngineSemanticOptions {
|
|
|
|
|
/** VisualChannel built-in. `false` to disable; object for opts; Channel to replace. */
|
|
|
|
|
visual?: false | VisualChannelOptions | Channel;
|
|
|
|
|
/** SoundChannel built-in. `true`/object/Channel to enable; `false`/undef to skip. */
|
|
|
|
|
sound?: true | false | SoundChannelOptions | Channel;
|
|
|
|
|
/** HapticChannel built-in. `true`/object/Channel to enable; `false`/undef to skip. */
|
|
|
|
|
haptic?: true | false | HapticChannelOptions | Channel;
|
|
|
|
|
|
|
|
|
|
/** Optional diagnostics logger. When omitted, channel failures stay silent. */
|
|
|
|
|
logger?: Logger;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Per-component packs of perceptual defaults. Each pack ships with
|
|
|
|
|
* the component (`src/uix/sema/components/{name}.ts`) and is
|
|
|
|
|
* concatenated into the cascade BEFORE `overrides.cascade`. Tree-
|
|
|
|
|
* shakable: only packs the app imports get included.
|
|
|
|
|
*/
|
|
|
|
|
components?: readonly Sema[];
|
|
|
|
|
|
|
|
|
|
/** Override layers 5 (runtime path) + 6b (app cascade). */
|
|
|
|
|
overrides?: EngineSemanticOverrides;
|
uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
- eidos: migrate drawer / popover / toast / accordion to wrapper subdirectory
pattern (provider + parts + tokens + recipe per component); avatar wrapper
added; toggle/switch/collapsible recipe polish.
- sema: per-component packs (accordion, drawer, popover, toast); engine /
resolver / event / sema-map updates; visual channel hold refinements.
- morfo: drawer event surface (variant, dragToDismiss, isDragging, minSize/
maxSize, onDragStart); popover/toast/accordion event refinements; schema +
types updates.
- soma: drawer provider rewrite (variant, dragToDismiss, isDragging binding,
drag callbacks); toast provider/toaster restructure with toast-main +
toast-status parts; popover content/arrow polish; gesture/floating layer
updates; manipulation layer scaffold; avatar removed (eidos owns it now).
- web/routes: legacy demos updated for new APIs; new /uix docs scaffold
(web/routes/uix/) with Radix-style shell, theme/lang/format/semantic prefs,
and standardized component template (Drawer page first — 6 tabs, live
controls, drag-hint arrows, scroll-safe body wrapper).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Projector forwarded to the built-in VisualChannel. The engine stores no
|
|
|
|
|
* projector itself; channels own their prepare/materialization details.
|
uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
- eidos: migrate drawer / popover / toast / accordion to wrapper subdirectory
pattern (provider + parts + tokens + recipe per component); avatar wrapper
added; toggle/switch/collapsible recipe polish.
- sema: per-component packs (accordion, drawer, popover, toast); engine /
resolver / event / sema-map updates; visual channel hold refinements.
- morfo: drawer event surface (variant, dragToDismiss, isDragging, minSize/
maxSize, onDragStart); popover/toast/accordion event refinements; schema +
types updates.
- soma: drawer provider rewrite (variant, dragToDismiss, isDragging binding,
drag callbacks); toast provider/toaster restructure with toast-main +
toast-status parts; popover content/arrow polish; gesture/floating layer
updates; manipulation layer scaffold; avatar removed (eidos owns it now).
- web/routes: legacy demos updated for new APIs; new /uix docs scaffold
(web/routes/uix/) with Radix-style shell, theme/lang/format/semantic prefs,
and standardized component template (Drawer page first — 6 tabs, live
controls, drag-hint arrows, scroll-safe body wrapper).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
*/
|
|
|
|
|
projector?: SignalProjector;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DOM writer forwarded to the built-in VisualChannel's default projector.
|
|
|
|
|
* In `active-uix` this is `uix.dom`, so sema projects event attrs through
|
|
|
|
|
* the same DOM owner as soma.
|
|
|
|
|
*/
|
|
|
|
|
dom?: DomApplier | (DomApplier & SoundChannelDom);
|
|
|
|
|
}
|
|
|
|
|
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
interface PersistentSignalEntry {
|
|
|
|
|
readonly preparations: readonly ChannelPreparation[];
|
|
|
|
|
readonly target: HTMLElement | undefined;
|
|
|
|
|
}
|
|
|
|
|
|
sema: rename SemanticEngine → EngineSemantic + .destroy() → .dispose()
Aligns sema's engine class with the new ecosystem's Engine* / Active*
naming convention (per src/arts/README.md):
- Engine* — public methods over private state. No reactive surface.
- Active* — Engine* extended with $state + onChange listeners.
Sema's SemanticEngine is purely imperative: a channel registry + an
async dispatch. No $state, no listeners, no loading/lastError. So by
convention it must be Engine*, not Active*.
Renames (mechanical):
SemanticEngine → EngineSemantic
SemanticEngineOpts → EngineSemanticOptions (also aligned to
EngineSiumOptions / EngineHttpOptions name)
.destroy() → .dispose() (uniform with the rest
of the ecosystem; only one call site, in
engine.test.ts)
Touched code: engine.ts, exports.ts, emit.test.ts, engine.test.ts +
README.md (kept consistent). Also sweep src/lib/ext/app/{defaults,
app.svelte}.ts which still mention the old name in fallback wiring.
Air's docs (AIR_*.md) and the legacy CONTINUITY note kept untouched
(dead branch).
This commit is purely a rename. The next commit (Phase 4 part 2) adds
the defineEngineSemantic factory and migrates soma + the 3 layouts to
createActiveApp.
Verification: sema 44/44, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
export class EngineSemantic {
|
|
|
|
|
private readonly channels = new Map<string, Channel>();
|
|
|
|
|
private nextSignalId = 0;
|
|
|
|
|
|
|
|
|
|
private readonly map: SemaMap;
|
|
|
|
|
private readonly cascade: readonly SemaCascadeRule[];
|
|
|
|
|
private readonly logger: Logger | undefined;
|
|
|
|
|
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
/**
|
|
|
|
|
* Active non-transient signals — projection handles kept alive past the
|
|
|
|
|
* hold for `untilAction` / `untilFix` / `stateBound`. Cleared via
|
|
|
|
|
* `clear(id)` or `clearTarget(target)`. Per book §6.1: persistence is
|
|
|
|
|
* caller-managed; the engine only holds the cleanup handle.
|
|
|
|
|
*/
|
|
|
|
|
private readonly active = new Map<string, PersistentSignalEntry>();
|
|
|
|
|
|
sema: rename SemanticEngine → EngineSemantic + .destroy() → .dispose()
Aligns sema's engine class with the new ecosystem's Engine* / Active*
naming convention (per src/arts/README.md):
- Engine* — public methods over private state. No reactive surface.
- Active* — Engine* extended with $state + onChange listeners.
Sema's SemanticEngine is purely imperative: a channel registry + an
async dispatch. No $state, no listeners, no loading/lastError. So by
convention it must be Engine*, not Active*.
Renames (mechanical):
SemanticEngine → EngineSemantic
SemanticEngineOpts → EngineSemanticOptions (also aligned to
EngineSiumOptions / EngineHttpOptions name)
.destroy() → .dispose() (uniform with the rest
of the ecosystem; only one call site, in
engine.test.ts)
Touched code: engine.ts, exports.ts, emit.test.ts, engine.test.ts +
README.md (kept consistent). Also sweep src/lib/ext/app/{defaults,
app.svelte}.ts which still mention the old name in fallback wiring.
Air's docs (AIR_*.md) and the legacy CONTINUITY note kept untouched
(dead branch).
This commit is purely a rename. The next commit (Phase 4 part 2) adds
the defineEngineSemantic factory and migrates soma + the 3 layouts to
createActiveApp.
Verification: sema 44/44, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
constructor(opts: EngineSemanticOptions = {}) {
|
|
|
|
|
this.logger = opts.logger;
|
|
|
|
|
|
|
|
|
|
// Layer 5 — runtime path overrides baked into the map at construction.
|
|
|
|
|
this.map = applyMapOverrides(SEMA_MAP, opts.overrides?.runtime);
|
|
|
|
|
|
|
|
|
|
// Layer 6 — flatten per-component packs (lower precedence) +
|
|
|
|
|
// app-level cascade (higher precedence on tie).
|
|
|
|
|
const packCascade: SemaCascadeRule[] = [];
|
|
|
|
|
const preloadUrls = new Set<string>();
|
|
|
|
|
for (const pack of opts.components ?? []) {
|
|
|
|
|
for (const rule of pack.cascade) {
|
|
|
|
|
packCascade.push(rule as SemaCascadeRule);
|
|
|
|
|
}
|
|
|
|
|
for (const url of pack.preloadSamples ?? []) {
|
|
|
|
|
preloadUrls.add(url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.cascade = [...packCascade, ...(opts.overrides?.cascade ?? [])];
|
|
|
|
|
|
|
|
|
|
// Channels.
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
if (opts.visual !== false) {
|
|
|
|
|
const visualChannel: Channel = isChannel(opts.visual)
|
|
|
|
|
? opts.visual
|
|
|
|
|
: new VisualChannel({
|
|
|
|
|
...(opts.visual ?? {}),
|
|
|
|
|
dom: opts.dom,
|
|
|
|
|
projector: opts.projector
|
|
|
|
|
});
|
|
|
|
|
this.register(visualChannel);
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
}
|
sema: revive resolver + signatures + real SoundChannel
Restores the resolver-driven architecture that was deleted in the
channel-modules refactor (commit a0a1485b). Each signal now flows
through a single resolution that produces an `EffectiveSignature`, and
each channel reads its own slice — so the visual hold, the sound earcon,
and (future) motion / color / presence channels all share a single
family-level perceptual budget defined in `SEMA_MAP`.
New surfaces:
- `src/uix/sema/sema-map.ts` — typed perceptual map (port of the legacy
`sema-map.json`) with per-family `base` (motion / sound / color /
presence) + `activeChannels` and per-intent `deltas` using the
number / `{op:'add'}` / `{op:'multiply'}` / `{op:'replace'}` /
recursive-merge semantics from before.
- `src/uix/sema/resolver.ts` — pure `resolveSignature(signal)` that
returns an `EffectiveSignature`. Empty signature when family is
missing so channels skip rather than throw. Intent deltas only apply
to valenced families. Optional `soundPack` per-event sample URLs.
- `src/uix/sema/chans/sound.ts` — real `SoundChannel` (~290 lines, ported
from a0a1485b^). Single AudioContext + master GainNode, auto-unlock
for Safari/iOS via `click` / `touchstart` / `keydown` with capture,
ADSR-lite synthesis (sine + 5th → biquad lowpass → envelope, AM
modulator when roughness > 0.2), contour via `osc.detune`, optional
sample playback with `AudioBuffer` cache. Errors absorbed silently —
sema is ornamental.
Architectural changes:
- `Channel.handle(signal, effective)` — every channel now receives the
resolved signature alongside the signal. Channels can ignore it.
- `EngineSemantic.emit` resolves once per emit, dispatches both args.
- `VisualChannel` hold resolution chain is now (highest priority first):
1. `signal.hold`
2. `effective.motion.duration` ← NEW: from sema-map
3. family fallback table (SEMA_DURATIONS)
4. engine `defaultHold`
Behavior change: families with motion in the map (alert: 220ms,
contact: 80ms, commit: 180ms, …) now use those values instead of the
previous label-based 600ms / 120ms / 240ms fallbacks. The fallback
table remains as a safety net for families without motion (sustain).
- `EngineSemanticOptions` adds `sound?: true | false | SoundChannelOptions
| Channel`. Sound is opt-in (audible side effects). Layouts wire
`defineEngineSemantic({ sound: true })`.
Tests:
- New `resolver.test.ts` (7 tests) — locks down family base, intent
deltas, multiplicative ops, transitional-family no-op, deep-clone.
- `visual.test.ts` — every `handle` call now passes a stub
`EffectiveSignature`. Two new tests: motion.duration takes the hold,
and signal.hold overrides effective.motion.duration.
- `engine.test.ts` — `makeChannel` mock signature updated.
- `emit.test.ts` — alert family default test now expects 220ms (from
sema-map) instead of 600ms.
Verified:
- vitest: 6 files / 53 tests green
- smoke: 68/68 soma routes 200 OK
- morfo-check: 66/66 morfos validate
5 months ago
|
|
|
|
|
|
|
|
if (opts.sound !== undefined && opts.sound !== false) {
|
|
|
|
|
let soundChannel: Channel;
|
|
|
|
|
if (isChannel(opts.sound)) {
|
|
|
|
|
soundChannel = opts.sound;
|
|
|
|
|
} else {
|
|
|
|
|
const soundOptions = opts.sound === true ? {} : opts.sound;
|
|
|
|
|
soundChannel = new SoundChannel({
|
|
|
|
|
...soundOptions,
|
|
|
|
|
dom: soundOptions.dom ?? (isSoundChannelDom(opts.dom) ? opts.dom : undefined),
|
|
|
|
|
logger: opts.logger
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
this.register(soundChannel);
|
|
|
|
|
|
|
|
|
|
// Pre-decode the WAVs declared by component packs so the first
|
|
|
|
|
// emit doesn't pay the fetch + decode latency.
|
|
|
|
|
if (preloadUrls.size > 0 && soundChannel instanceof SoundChannel) {
|
|
|
|
|
void soundChannel.preloadSamples([...preloadUrls]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opts.haptic !== undefined && opts.haptic !== false) {
|
|
|
|
|
const hapticChannel: Channel = isChannel(opts.haptic)
|
|
|
|
|
? opts.haptic
|
|
|
|
|
: new HapticChannel(opts.haptic === true ? {} : opts.haptic);
|
|
|
|
|
this.register(hapticChannel);
|
sema: revive resolver + signatures + real SoundChannel
Restores the resolver-driven architecture that was deleted in the
channel-modules refactor (commit a0a1485b). Each signal now flows
through a single resolution that produces an `EffectiveSignature`, and
each channel reads its own slice — so the visual hold, the sound earcon,
and (future) motion / color / presence channels all share a single
family-level perceptual budget defined in `SEMA_MAP`.
New surfaces:
- `src/uix/sema/sema-map.ts` — typed perceptual map (port of the legacy
`sema-map.json`) with per-family `base` (motion / sound / color /
presence) + `activeChannels` and per-intent `deltas` using the
number / `{op:'add'}` / `{op:'multiply'}` / `{op:'replace'}` /
recursive-merge semantics from before.
- `src/uix/sema/resolver.ts` — pure `resolveSignature(signal)` that
returns an `EffectiveSignature`. Empty signature when family is
missing so channels skip rather than throw. Intent deltas only apply
to valenced families. Optional `soundPack` per-event sample URLs.
- `src/uix/sema/chans/sound.ts` — real `SoundChannel` (~290 lines, ported
from a0a1485b^). Single AudioContext + master GainNode, auto-unlock
for Safari/iOS via `click` / `touchstart` / `keydown` with capture,
ADSR-lite synthesis (sine + 5th → biquad lowpass → envelope, AM
modulator when roughness > 0.2), contour via `osc.detune`, optional
sample playback with `AudioBuffer` cache. Errors absorbed silently —
sema is ornamental.
Architectural changes:
- `Channel.handle(signal, effective)` — every channel now receives the
resolved signature alongside the signal. Channels can ignore it.
- `EngineSemantic.emit` resolves once per emit, dispatches both args.
- `VisualChannel` hold resolution chain is now (highest priority first):
1. `signal.hold`
2. `effective.motion.duration` ← NEW: from sema-map
3. family fallback table (SEMA_DURATIONS)
4. engine `defaultHold`
Behavior change: families with motion in the map (alert: 220ms,
contact: 80ms, commit: 180ms, …) now use those values instead of the
previous label-based 600ms / 120ms / 240ms fallbacks. The fallback
table remains as a safety net for families without motion (sustain).
- `EngineSemanticOptions` adds `sound?: true | false | SoundChannelOptions
| Channel`. Sound is opt-in (audible side effects). Layouts wire
`defineEngineSemantic({ sound: true })`.
Tests:
- New `resolver.test.ts` (7 tests) — locks down family base, intent
deltas, multiplicative ops, transitional-family no-op, deep-clone.
- `visual.test.ts` — every `handle` call now passes a stub
`EffectiveSignature`. Two new tests: motion.duration takes the hold,
and signal.hold overrides effective.motion.duration.
- `engine.test.ts` — `makeChannel` mock signature updated.
- `emit.test.ts` — alert family default test now expects 220ms (from
sema-map) instead of 600ms.
Verified:
- vitest: 6 files / 53 tests green
- smoke: 68/68 soma routes 200 OK
- morfo-check: 66/66 morfos validate
5 months ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
register(channel: Channel): void {
|
|
|
|
|
if (this.channels.has(channel.id)) {
|
|
|
|
|
throw new SemaDuplicateChannelError(channel.id);
|
|
|
|
|
}
|
|
|
|
|
this.channels.set(channel.id, channel);
|
|
|
|
|
}
|
|
|
|
|
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
/**
|
|
|
|
|
* Despacha una señal a todos los canales registrados.
|
|
|
|
|
*
|
|
|
|
|
* Lifecycle:
|
|
|
|
|
* 1. Generate id and run channel prepare hooks.
|
|
|
|
|
* VisualChannel.prepare() projects `data-event-*` on target.
|
|
|
|
|
* 2. resolveSignature → cascade selectors match against the now-
|
|
|
|
|
* projected target via native `target.matches()`.
|
|
|
|
|
* 3. Fire-and-forget non-visual channels.
|
|
|
|
|
* 4. Await visual channel hold.
|
|
|
|
|
* 5. Cleanup prepare handles.
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
*
|
|
|
|
|
* Sequential strict: the caller's structural commit happens AFTER
|
|
|
|
|
* cleanup. State change is strictly ordered after the perceptual window.
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
*/
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
async emit(signal: SemanticSignal): Promise<string> {
|
|
|
|
|
const id = signal.id ?? `sig-${this.nextSignalId++}`;
|
|
|
|
|
const enriched: SemanticSignal = { ...signal, id };
|
|
|
|
|
|
|
|
|
|
// Explicit silence (capa 4 morfo override): `signal.channels: []`
|
uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
- eidos: migrate drawer / popover / toast / accordion to wrapper subdirectory
pattern (provider + parts + tokens + recipe per component); avatar wrapper
added; toggle/switch/collapsible recipe polish.
- sema: per-component packs (accordion, drawer, popover, toast); engine /
resolver / event / sema-map updates; visual channel hold refinements.
- morfo: drawer event surface (variant, dragToDismiss, isDragging, minSize/
maxSize, onDragStart); popover/toast/accordion event refinements; schema +
types updates.
- soma: drawer provider rewrite (variant, dragToDismiss, isDragging binding,
drag callbacks); toast provider/toaster restructure with toast-main +
toast-status parts; popover content/arrow polish; gesture/floating layer
updates; manipulation layer scaffold; avatar removed (eidos owns it now).
- web/routes: legacy demos updated for new APIs; new /uix docs scaffold
(web/routes/uix/) with Radix-style shell, theme/lang/format/semantic prefs,
and standardized component template (Drawer page first — 6 tabs, live
controls, drag-hint arrows, scroll-safe body wrapper).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
// skips EVERYTHING — no projection, no dispatch, no hold. The morfo
|
|
|
|
|
// declared this event has no perceptual surface. Other paths to an
|
|
|
|
|
// empty `effective.activeChannels` (e.g. signal with no family —
|
|
|
|
|
// degenerate case) still go through dispatch so individual channels
|
|
|
|
|
// can self-skip via their own activeChannels check.
|
|
|
|
|
if (signal.channels !== undefined && signal.channels.length === 0) {
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const preparations: ChannelPreparation[] = [];
|
|
|
|
|
for (const channel of this.channels.values()) {
|
|
|
|
|
const handle = channel.prepare?.(enriched);
|
|
|
|
|
if (handle) preparations.push(handle);
|
|
|
|
|
}
|
|
|
|
|
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const persistence = signal.persistence ?? 'transient';
|
|
|
|
|
const shouldAutoCleanup = persistence === 'transient';
|
|
|
|
|
let cleanedUp = false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const effective = resolveSignature(enriched, {
|
|
|
|
|
map: this.map,
|
|
|
|
|
cascade: this.cascade
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const visualChannel = this.channels.get('visual');
|
|
|
|
|
const otherChannels: Channel[] = [];
|
|
|
|
|
for (const channel of this.channels.values()) {
|
|
|
|
|
if (channel.id !== 'visual') otherChannels.push(channel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const channel of otherChannels) {
|
|
|
|
|
channel.handle(enriched, effective).catch((err) => {
|
|
|
|
|
this.logger?.error('sema', `channel "${channel.id}" failed`, {
|
|
|
|
|
error: err,
|
|
|
|
|
context: { channel: channel.id, signal: enriched.id }
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Visual owns the hold timing. It runs whenever there are ANY
|
|
|
|
|
// active channels — the empty-channels case was already handled
|
|
|
|
|
// by the early return above. Visual isn't in family activeChannels
|
|
|
|
|
// (those describe real runtime channels like sound / haptic) —
|
|
|
|
|
// it's a meta channel that gates the data-event-* hold for eidos
|
|
|
|
|
// CSS reactions.
|
|
|
|
|
if (visualChannel) {
|
|
|
|
|
await visualChannel.handle(enriched, effective);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
if (shouldAutoCleanup) {
|
|
|
|
|
for (const handle of [...preparations].reverse()) {
|
|
|
|
|
handle.cleanup();
|
|
|
|
|
}
|
|
|
|
|
cleanedUp = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Persistent signals: keep projection alive past the hold. Caller
|
|
|
|
|
// (typically a soma provider) clears via `clear(id)` or
|
|
|
|
|
// `clearTarget(target)` when the relevant condition is met. If the
|
|
|
|
|
// caller never clears, the projection lingers until dispose() — by
|
|
|
|
|
// design: persistence is caller-managed per book §6.1.
|
|
|
|
|
if (!cleanedUp) {
|
|
|
|
|
this.active.set(id, { preparations, target: signal.target });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear an active persistent signal by id. Returns `true` if the signal
|
|
|
|
|
* was found and cleared, `false` if it didn't exist (already cleared,
|
|
|
|
|
* was transient, or never emitted).
|
|
|
|
|
*
|
|
|
|
|
* Call this from the caller that originally emitted the signal when
|
|
|
|
|
* the underlying condition is met:
|
|
|
|
|
* - `untilAction` — user acknowledged the alert.
|
|
|
|
|
* - `untilFix` — the validation error was corrected.
|
|
|
|
|
* - `stateBound` — the state ended.
|
|
|
|
|
*/
|
|
|
|
|
clear(id: string): boolean {
|
|
|
|
|
const entry = this.active.get(id);
|
|
|
|
|
if (!entry) return false;
|
|
|
|
|
this.active.delete(id);
|
|
|
|
|
for (const handle of [...entry.preparations].reverse()) {
|
|
|
|
|
handle.cleanup();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clear all active persistent signals whose `target` matches the given
|
|
|
|
|
* element. Returns the count of signals cleared. Useful when a single
|
|
|
|
|
* gesture invalidates multiple persistent signals on the same surface
|
|
|
|
|
* (e.g. a form with three field warnings: one form-valid clears all).
|
|
|
|
|
*/
|
|
|
|
|
clearTarget(target: HTMLElement): number {
|
|
|
|
|
let count = 0;
|
|
|
|
|
for (const [id, entry] of this.active) {
|
|
|
|
|
if (entry.target === target) {
|
|
|
|
|
this.active.delete(id);
|
|
|
|
|
for (const handle of [...entry.preparations].reverse()) {
|
|
|
|
|
handle.cleanup();
|
|
|
|
|
}
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether a persistent signal id is currently active. Read-only helper
|
|
|
|
|
* for diagnostics and tests; production code should not branch on this.
|
|
|
|
|
*/
|
|
|
|
|
hasActive(id: string): boolean {
|
|
|
|
|
return this.active.has(id);
|
|
|
|
|
}
|
|
|
|
|
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
getChannel(id: string): Channel | undefined {
|
|
|
|
|
return this.channels.get(id);
|
|
|
|
|
}
|
|
|
|
|
|
sema: rename SemanticEngine → EngineSemantic + .destroy() → .dispose()
Aligns sema's engine class with the new ecosystem's Engine* / Active*
naming convention (per src/arts/README.md):
- Engine* — public methods over private state. No reactive surface.
- Active* — Engine* extended with $state + onChange listeners.
Sema's SemanticEngine is purely imperative: a channel registry + an
async dispatch. No $state, no listeners, no loading/lastError. So by
convention it must be Engine*, not Active*.
Renames (mechanical):
SemanticEngine → EngineSemantic
SemanticEngineOpts → EngineSemanticOptions (also aligned to
EngineSiumOptions / EngineHttpOptions name)
.destroy() → .dispose() (uniform with the rest
of the ecosystem; only one call site, in
engine.test.ts)
Touched code: engine.ts, exports.ts, emit.test.ts, engine.test.ts +
README.md (kept consistent). Also sweep src/lib/ext/app/{defaults,
app.svelte}.ts which still mention the old name in fallback wiring.
Air's docs (AIR_*.md) and the legacy CONTINUITY note kept untouched
(dead branch).
This commit is purely a rename. The next commit (Phase 4 part 2) adds
the defineEngineSemantic factory and migrates soma + the 3 layouts to
createActiveApp.
Verification: sema 44/44, smoke 68/68, morfo-check 66/66.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
dispose(): void {
|
feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
Three book canon items codified in code:
**Persistence (libro §6.1)** — separa `hold` (perceptual min) de
`persistence` (lifecycle real):
- New `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
- New `SEMA_HOLDS_BY_INTENT` canonical lookup table (`src/uix/sema/holds.ts`)
- `EngineSemantic.emit()` returns the resolved signal id; keeps projection
alive past hold for non-transient. Exposes `clear(id)`, `clearTarget(target)`,
`hasActive(id)`.
- `SomaRuntime.trigger()` returns `TriggerResult { id?, persistence? }`.
Exposes `clearSignal(id)`, `clearTarget(target)`, `partRef(part)`.
**a11ySemantic (libro §9.1)**:
- New `MorfoA11ySemantic` (requiresPersistentTrace, requiresLiveRegion,
requiresFocusMove, keyboardEquivalent, reducedMotionFallback) on MorfoEvent.
- `ActiveDom.prefersReducedMotion`: reactive tracker via media query
(`src/arts/adom/reduced-motion.svelte.ts`).
- `ActiveUix.announce(msg, priority?, timeout?)`: lazy-created live region
via `dom.writeNode`.
- `Soma.runtime()` auto-wires `sources.announce`.
- `SomaRuntime.trigger()` honors a11ySemantic after emit (live region, focus,
reduced-motion fallback including `channels: []` for 'state').
**Polymorphic events (libro §5.3)** — ADITIVO sobre shape concreto:
- `allowedFamilies?: readonly SemaFamily[]` opcional en MorfoEventSemantic.
- Provider override via `runtime.trigger(name, { semantic })`.
- Default family of the morfo is implicitly allowed.
- New `SomaRuntimePolymorphicError`.
- New `isPolymorphicSemantic` type guard.
Synthetic `prewriteFixtureMorfo` (src/uix/morfo/test-fixtures.ts) decouples
compile + runtime tests from the production morfo catalogue.
671 tests pass in sema + morfo + soma + adom scopes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
// Clean up any lingering persistent signal projections before tearing
|
|
|
|
|
// down channels, so DOM doesn't keep stale data-event-* attrs.
|
|
|
|
|
for (const entry of this.active.values()) {
|
|
|
|
|
for (const handle of [...entry.preparations].reverse()) {
|
|
|
|
|
handle.cleanup();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.active.clear();
|
|
|
|
|
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
for (const channel of this.channels.values()) {
|
|
|
|
|
channel.dispose?.();
|
|
|
|
|
}
|
|
|
|
|
this.channels.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
|
|
|
|
|
function isChannel(value: unknown): value is Channel {
|
|
|
|
|
return (
|
|
|
|
|
value !== null &&
|
|
|
|
|
typeof value === 'object' &&
|
|
|
|
|
typeof (value as Channel).id === 'string' &&
|
|
|
|
|
typeof (value as Channel).handle === 'function'
|
|
|
|
|
);
|
sema: refactor into channel modules — engine becomes registry + dispatch
Reorganización estructural según el spec acordado: cada canal perceptivo
vive como módulo simétrico dentro de `chans/`. El engine queda mínimo
(registry + dispatch); la lógica de escribir `data-event*` al DOM,
mantener hold y retirar pasa al `VisualChannel`. La API pública que ven
los providers (`semantic.emit(signal)`) se mantiene idéntica.
Estructura nueva
src/uix/sema/
├── engine.ts registry + dispatch (ya no conoce DOM ni hold)
├── signal.ts SemanticSignal + nuevo campo opcional `id?`
├── exports.ts barrel actualizado (chans + drop publish + drop perception)
└── chans/
├── types.ts interfaz Channel { id, handle, dispose? }
├── visual.ts VisualChannel — escribe data-event*, hold, cleanup
├── visual.test.ts 9 tests del canal aislado
├── sound.ts SoundChannel placeholder (V1 no-op)
└── vibra.ts VibraChannel placeholder (V1 no-op)
Cambios al engine
- Constructor: `new SemanticEngine(opts?)`. `opts.visual` controla el
built-in (false / VisualChannelOptions / Channel custom). `opts.dom`
desaparece — ya no es responsabilidad del engine.
- Métodos: `register(channel)`, `getChannel(id)`, `emit(signal)`,
`destroy()`. Nada más.
- emit despacha a TODOS los canales registrados:
- Canales no-visuales (sound, vibra, futuros): fire-and-forget. Errores
se loguean pero no propagan (Sema es ornamental).
- Canal visual: el único cuya Promise se awaitea — comparte plano DOM
con el commit estructural posterior.
- emit genera el `id` de la ocurrencia y lo pasa a todos los canales,
garantizando coherencia cross-canal para tooling futuro.
Cambios al VisualChannel (lógica heredada del emit() anterior)
- 5 atributos: `data-event` + `data-event-id` + `data-event-phase`
(siempre) + `data-event-family` y `data-intent` (opcionales si vienen
en el signal). Los opcionales son la proyección al DOM de la metadata
semántica que Eidos consume.
- Hold defaults internos por familia (no exportados):
emerge/commit/handle: 240ms · alert/sustain: 600ms · contact: 120ms
Justificación técnica (rangos típicos de CSS transitions), no escala
perceptiva universal. Override per signal vía `signal.hold` o global
vía `new SemanticEngine({ visual: { defaultHold } })`.
- Semántica secuencial estricta: cleanup ANTES del resolve.
Eliminado
- `src/uix/sema/perception.ts` — escalas perceptivas no son concepto
cross-canal. La tabla de defaults por familia se movió al VisualChannel
como detalle interno con justificación técnica.
- `engine.publish()` y todo el sistema legacy:
- `SemanticEventDecl`, `SemanticComponentContract`, `SemanticPublishContext`,
`PublishedSemanticEvent`, `SemanticEventFilter`
- `onEvent()` y subscribers
- `applyPrewrites()` interno
- dependencia opcional de `ActiveDom` por construcción
- Tests de `publish()` reemplazados por tests del nuevo registry/dispatch.
Bootstrap (App + defaults)
- `src/lib/ext/app/app.svelte.ts:52` — `new SemanticEngine({ dom: this.dom })`
→ `new SemanticEngine()`
- `src/lib/ext/app/defaults.ts:45` — mismo cambio
Tests
- emit.test.ts ajustado: sin createActiveDom, sin SEMA_PERCEPTION/HOLD_DEFAULTS,
test "throws without dom" eliminado, números literales (240, 600).
Añadido test "resolves immediately when visual: false". 7 tests.
- engine.test.ts reescrito: register / getChannel / emit dispatch a múltiples
canales / id propagation / id override / fire-and-forget para non-visual /
error en canal no aborta dispatch / visual: false desactiva built-in /
custom Channel reemplaza built-in / destroy. 13 tests.
- chans/visual.test.ts nuevo: 5 attrs vs 3 / signal.hold respetado /
family default fallback / global default fallback / constructor defaultHold /
cleanup / sequential strict (resolve tras cleanup) / id field. 9 tests.
- 39 tests sema verdes en total.
Documentación
- src/uix/sema/README.md — reescrito alrededor del modelo de canales
- src/uix/eidos/events.css — comentario actualizado, referencia a chans/visual.ts
- src/uix/active_architecture.md — sección Sema reescrita
Componentes NO requieren cambios — la API `semantic.emit(signal)` es
idéntica externamente. Toast / runtime.trigger / providers funcionan igual.
Verificación
- 121/121 tests focused suite (sema 39 + adom + lib/dom + morfo + soma/morfo)
- 66/66 morfo-check
- 68/68 smoke routes
- svelte-check: 155 errors (baseline, sin nuevos)
Pendientes en doc del spec marcados como "no-objetivos":
- Implementación real de SoundChannel / VibraChannel (placeholders V1)
- Arbitraje propio del VisualChannel (deuda heredada del emit anterior)
- Cancelación si el target se desconecta durante el hold
6 months ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isSoundChannelDom(value: unknown): value is SoundChannelDom {
|
|
|
|
|
return (
|
|
|
|
|
value !== null &&
|
|
|
|
|
typeof value === 'object' &&
|
|
|
|
|
typeof (value as SoundChannelDom).getDocument === 'function' &&
|
|
|
|
|
typeof (value as SoundChannelDom).listen === 'function'
|
|
|
|
|
);
|
|
|
|
|
}
|