Architectural correction. Morfo was importing Svelte runes, $adom, and
$uix/sema — all violations of "morfo is pure declarative DNA". The
runtime that interprets a CompiledMorfo lives where it executes: soma.
Move:
- src/uix/morfo/runtime.svelte.ts → src/uix/soma/runtime.svelte.ts
- src/uix/morfo/runtime.svelte.test.ts → src/uix/soma/runtime.svelte.test.ts
Rename (consistent with the move; "soma's runtime", per the doctrine
"morfo declares, soma executes"):
- class MorfoRuntime → SomaRuntime
- createMorfoRuntime() → createSomaRuntime()
- type MorfoRuntimeSources → SomaRuntimeSources
- error prefixes [morfo-runtime] → [soma-runtime]
Sema is now genuinely ornamental:
- ActiveUix.semantic returns EngineSemantic | undefined (no longer
throws when no engine is configured). Components in SSR / headless
tests / audio-disabled environments stay functional.
- SomaRuntime.trigger() skips the emit step AND the target-resolution
check when no engine is present — only requires a DOM target when
something will actually be emitted.
- Soma.semantic getter returns | undefined too (was non-optional).
Consumers updated:
- src/uix/active-uix/active-uix.svelte.ts — imports createSomaRuntime
from $soma; semantic getter returns | undefined
- src/uix/active-uix/types.ts — interface SomaRuntime, semantic | undefined
- 8 providers (toggle, switch, collapsible, dialog, drawer, popover,
toast, avatar) — type SomaRuntime, import path $soma
- src/uix/soma/index.ts — re-exports createSomaRuntime + types
- src/uix/morfo/index.ts — drops the runtime re-exports (morfo is pure)
- src/uix/sema/signal.ts — JSDoc reference updated
- src/uix/soma/core/soma.svelte.ts — semantic getter | undefined
Test added: SomaRuntime.trigger() succeeds without a semantic engine
(skips emit + target check, runs handler directly).
Docs aligned to the new shape:
- active_architecture.md — runtime locations, sema as ornamental
- soma/SOMA_ARCHITECTURE.md, soma/README.md — name + import paths
- morfo/README.md — file listing now flags morfo as PURE TS, points
at soma/runtime.svelte.ts for the interpreter
- sema/README.md — SomaRuntime references
- README.md — root architecture diagram
- CLAUDE.md — hand-off note
Tests 218/218 green. npm run check holds at the 39-error baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 (docs alignment with the canonical guide):
- active_architecture.md — Dialog example refactored to the new event
shape (target inside semantic, explicit verb + sequence). Verbs
table grouped by family with the post-canon vocabulary (select/
toggle moved to commit, edit removed from handle, new verbs added).
Section 10 (Estado actual) updated to 2026-05-08 with current
progress: doctrina cerrada, eidos en producción, SoundChannel
eager-init, switch migrated, etc. "Para profundizar" §14 points at
the canonical guide.
- morfo/README.md — Step 5.5 added covering the doctrinal event
shape with all fields (semantic.family, .verb, .target, .sequence,
.intent) and the two naming shapes ({verb}-{variant} and
{family}-{verb}).
- sema/README.md — verbs section restructured to Record-by-family
matching the runtime; documents the verbs that moved between
families per the canon. Naming shapes section updated with both
forms and validateEventName signature.
- CLAUDE.md — "Session hand-off" rewritten with the post-canon
state. Pointer at GUIA_IMPLEMENTACION_SEMAUIX.md as the canonical
source. Pending list refreshed: switch is the immediate next
component, persistence/a11ySemantic/polymorphism deferred until
real consumers appear.
Switch migration to the eidos wrapper pattern:
- morfo/components/switch.ts — adds events: [{ commit-toggle }] with
the doctrinal shape (verb=toggle, sequence=post, intent fromProp
binding to the 4 supported intents). New data-* (data-color with
the 6-token Toggle/Switch subset, data-readonly, data-invalid,
data-required) and corresponding aria-*. scope expanded to
['soma', 'sema'].
- soma/components/switch/types.ts — adds intent, color, readonly,
invalid, aria-labelledby props with full JSDoc. Subset constrained
per guide §3.1 (Toggle/Switch row).
- soma/components/switch/switch-provider.svelte.ts — runtime-direct,
Field integration via FieldProvider.get(), isDisabled/isReadonly/
isRequired/isInvalid OR-merge derivations, intent ↔ color
resolution (intent wins when evaluative), runtime.trigger(
'commit-toggle') in onclick.
- soma/components/switch/components/switch.svelte — wires the new
props through bindProps.
- eidos/components/switch/{switch.css, switch.svelte, types.ts,
index.ts, README.md} — full subdirectory pattern, mirroring
toggle. Recipe drops legacy success/warning/danger/info selectors;
declares the 6 doctrinal data-color values directly. Wrapper
auto-renders Switch.Thumb inside the track and exposes an optional
`thumb` snippet for in-thumb glyphs.
- eidos/components/switch.css (legacy flat) deleted; eidos/index.css
updated to import the subdirectory recipe.
- web/routes/switch/+page.svelte — interactive testbed with all soma
props as live controls + tabs (Live, Soma, Morfo, Sema, Eidos,
Notes). Mirrors toggle's structure.
npm run check holds at the 39-error baseline. Tests 217/217 green.
Pending:
- Migrate collapsible / dialog / drawer / popover / toast / avatar
to the eidos wrapper pattern.
- Persistence + holds-by-intent — defer until first signal.warn /
signal.alert consumer.
- a11ySemantic + polymorphic events — defer until concrete need.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new doctrinal guide at src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md
supersedes src/docs/sema-implementation-guide.md and locks the
canonical vocabulary, event shape and color tokens. This commit aligns
the codebase to that canon in three concrete moves.
Phase 1 — sema/verbs.ts vocabulary:
- Restructured from flat array to family-keyed Record per the guide.
- Moved select / toggle / acknowledge → commit (these fix state, not
contact). Removed edit from handle (it's shift.enter-mode now).
- Added new verbs per the canon: contact (tap, focus, trigger,
release), commit (complete, restore, expire, set, remove, reorder),
signal (remind), handle (rotate, scroll, reorder), shift (route,
step, return, context), sustain (start, loading, waiting, syncing,
processing, streaming, pending, retrying, end).
- validateEventName now recognises both naming shapes:
{verb}-{variant} (head=verb) and {family}-{verb} (head=family).
- New helper familyForVerb. Tests rewritten — 16/16 green.
Phase 2 — morfo event shape:
- target moved from event-level into event.semantic. Adds optional
semantic.verb (advisory, validated against SEMA_VERBS[family]) and
optional semantic.sequence ('pre' | 'coincident' | 'post').
- compile.ts and schema.ts updated. The 22 events across toggle,
toast, popover, drawer, dialog refactored to the new shape with
explicit verb + sequence.
- Polymorphic capability (allowedFamilies + defaultSemantic) and
a11ySemantic deferred — no current consumer needs them; will land
when the first concrete usage appears.
Phase 5 — color tokens (doctrinal 8):
- themes/base/light.css and dark.css rewritten to expose primary,
secondary, neutral, affirm, fulfill, risk, threat, loss. Renames
success→fulfill, warning→risk, danger→threat (same hex). New
primitives for secondary (slate-blue), affirm (teal-mint, low
activation), loss (deep violet-grave, posterior). info palette
deleted entirely — per guide, "info no es intent, es signal.announce
+ neutral".
- _static.css legacy compatibility aliases (--color-bg, --color-success,
--color-error, --color-accent, ...) deleted. Clean cut, no
transition. Consumers reference doctrinal palettes directly.
- 29 consumers (token files + recipes) migrated:
- All --*-color-info-* declarations dropped.
- Component palettes extended with secondary / affirm / loss.
- switch + checkbox recipes updated to use [data-color='affirm'|
'risk'|'threat'|'secondary'] (was 'success'|'warning'|'danger'|
'info').
- toast tokens + recipe renamed to affirm/fulfill/risk/threat/loss
(was success/error/warning/info).
- meter, progress, mark, badge, button tokens renamed.
- Contracts (contracts/contract.css + contracts/components/*)
updated.
- focus-ring-color-error in themes now points to --color-risk-border
(validation errors are signal.warn + risk per doctrina, not threat).
Phase 4 (partial):
- src/docs/sema-implementation-guide.md deleted (superseded by the
canonical guide).
Tests: 217/217 green across uix. npm run check holds at the 39-error
baseline (all pre-existing in unrelated test files; none introduced).
Pending in follow-up commits:
- Update active_architecture.md, sema/README.md, morfo/README.md,
CLAUDE.md to reflect the canon.
- Switch component migration to the eidos wrapper pattern.
- Persistence field (untilAction / untilFix / stateBound) and
holds-by-intent — defer until the first signal.warn / signal.alert
consumer appears.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deleted 60 obsolete documents that no longer reflect the active-uix
state:
- 47 per-component audit logs (*-audit.md, audit_N.md, AUDIT.md)
- 5 global soma audits (AUDIT_1, codex_audit, soma-audit-04-20/21,
audit-prompt template)
- 5 closed studies (soma/study, morfo/study, morfo/DESIGN, morfo/
PROVIDER_STUDY_2026-04-23, uix/CONTINUITY_2026-04-24)
- 2 superseded refactor proposals (refactor_claude, refactor_code)
- 1 stale bug log (form/BUG-onchange-onblur-hang — repro path
src/routes/test/sium/kitchen-sink no longer exists)
Updated the remaining operational references to drop dead links and
reflect the post-cleanup architecture:
- src/uix/README.md — rewritten links section, dropped pointers to
terra/air/CONTINUITY/refactor docs, updated adom/dom paths
- src/uix/active_architecture.md — header now dated 2026-05-08, lists
the major shifts since 2026-04-26 (provider inheritance dropped,
eidos in production, doctrina parte IV, sound eager-init); pendings
section reflects the eidos migration roadmap; "see also" purged of
deleted files
- src/uix/morfo/README.md — file tree updated (removed DESIGN/study,
added compile/runtime/PERMUTATION_RUNNER); see-also pruned
- src/uix/sema/README.md — SoundChannel section now describes the
eager-init pattern (constructor-time, not lazy); pointer to
sema-implementation-guide added
- src/uix/soma/README.md — drops terra/air mentions, declares the
actual dependency direction (eidos -> soma, not the reverse)
- src/uix/soma/SOMA_ARCHITECTURE.md — §15 rewritten as "Estado actual
y deuda histórica" reflecting Provider-inheritance-drop; demo path
updated to web/routes/
- src/uix/soma/COMPONENT_GUIDE.md — A23 dates section now points to
$libs/days (no external/dates façade); demo paths swapped from
/test/soma/* to web/routes/
- src/uix/morfo/PERMUTATION_RUNNER.md — paths swapped to web/routes/
with a relocation note
- CLAUDE.md — Refactor Documents section replaced with Reference
Documents pointing to per-layer READMEs
npm run check holds at baseline (39 pre-existing errors, none new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Two architectural fixes surfaced by the eidos demo:
1. The previous default hold (1 rAF + 1 hold frame, ~30ms) was a
technical timing decision — invisible to humans. Signals must be
anchored to perceptual scales so they can actually be perceived.
2. The previous lifecycle resolved emit AFTER 1 rAF (caller's structural
change ran in parallel with cleanup, ~30ms later). That's an implicit
parallelism between event and state. The author has no way to say
"wait for the announcement to be perceived before changing state".
Sequential strict semantics (NEW)
- emit writes the signal, awaits the full author-specified hold, cleans
up, then resolves. State change happens AFTER cleanup — strictly
sequential.
- `signal.hold` is in milliseconds anchored to `SEMA_PERCEPTION` scales:
subliminal 50ms below conscious threshold
glimpse 120ms minimum perceptible without effort
brief 240ms comfortable acknowledgement
noticed 600ms sustained signal
insistent 1200ms demands attention
persistent 3000ms until acknowledged
- Defaults per family in `SEMA_HOLD_DEFAULTS`:
emerge: brief sustain: noticed
contact: glimpse commit: brief
alert: noticed handle: brief
- Authors override per event via `SemanticSignal.hold` based on:
- their CSS animation duration (so it completes before cleanup)
- the perceptual scale they want
- critical attention requirements (use insistent/persistent)
Files
- src/uix/sema/perception.ts (new) — SEMA_PERCEPTION + SEMA_HOLD_DEFAULTS
+ SEMA_OBSERVE_FRAME_MS constants with rationale anchored on Bloch's
law, Material/Apple HIG motion guidelines, toast UX literature.
- src/uix/sema/signal.ts — `hold` JSDoc updated to explain ms semantics +
defaults + override pattern.
- src/uix/sema/engine.ts — emit() refactored from rAF chain to
setTimeout(holdMs); cleanup happens BEFORE resolve (sequential strict);
`nextFrame()` helper removed; `resolveHoldMs(signal)` added with the
3-tier fallback (signal.hold → family default → SEMA_PERCEPTION.brief).
- src/uix/sema/exports.ts — re-exports SEMA_PERCEPTION + SEMA_HOLD_DEFAULTS
+ SEMA_OBSERVE_FRAME_MS + SemaPerception type.
- src/uix/sema/emit.test.ts — rewritten around fakeTimers + setTimeout
(no more rAF queue); new test verifies cleanup runs BEFORE resolve.
7/7 tests green.
- src/uix/sema/README.md — new "Duración del signal" section with the
full perception scale + per-family defaults tables + override pattern.
- src/uix/eidos/events.css — comment updated to explain the new hold
windows; rule `[data-event^='dismiss']` etc. clarified.
- src/uix/active_architecture.md — replaced "~13ms" prose with explicit
perception-anchored scales; added explanation of "perceptual by design,
not framerate".
- src/uix/soma/components/toast/toast-provider.svelte.ts — new $effect
that fires `present` + `announce` events when item ref attaches
(one-shot guarded), so toasts emit signals on appear. The flash on
/test/eidos is now visible thanks to (a) the perceptual hold and (b)
the wired events.
Channel parallelism note
- Hold is the DOM channel duration only.
- Future sound / vibra / motion engines subscribe to sema events
independently; each owns its own timing relative to the event id.
- Sema does not coordinate cross-channel sync — that's an integrator
decision per app.
Verification
- 22/22 sema tests green.
- 22/22 morfo runtime tests green.
- 66/66 morfo-check + 68/68 smoke (verified before commit).
- Probe shows announce signal on toast appear lasting ~600ms (alert
family default) — visible in DevTools attribute panel.
- svelte-check unchanged at 155.
Pre-existing failure in src/uix/air/internal/canvas/layout.test.ts
(`bun:test` import) is in the dead-branch air layer and unrelated.
Synthesizes the four-layer architecture (morfo · soma · sema · eidos) into
a single readable doc. Where the per-layer READMEs are operational
references, this is the "north star" for understanding the why and how
the pieces articulate.
Structure (14 sections):
1. The thesis in one line — four layers with explicit contracts
2. The problem solved — what conventional frameworks mix together
3. The four layers — Morfo (DNA), Soma (behavior), Sema (semantic
channel), Eidos (visual)
4. How they articulate — the transcription chain (six pieces, one
responsibility each)
5. The causal chain of an interaction — concrete walkthrough of a Toast
dismiss with timing
6. The primitives that pass between layers — DOM attrs catalog +
archetypes + verbs vocabularies
7. Hard rules — the 12 invariants that keep the system coherent,
including the 2-of-3 rule
8. The autoría / transcripción distinction — the lens for deciding what
lives where
9. What this architecture is NOT — explicit non-goals
10. Current state (2026-04-26) — what's built, designed, pending
11. Acknowledged risks — over-layering, name discipline, responsibility
invasion, lack of precedent
12. Why it may be worth it — explainability, less drift, validation,
extensibility
13. The summary phrase — "Morfo declara · MorfoRuntime transcribe ·
Provider aporta · Effects sincronizan · Semantic emite · Dom aplica
· Eidos lee"
14. For deeper reading — links to per-layer docs
Also adds a top-of-page pointer in src/uix/README.md so the vision doc is
the obvious entry point for new readers.
No code changes — pure documentation.