You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/sema/engine.ts

928 lines
37 KiB

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
/**
* 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
*
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 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.
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
* 3. Resolver la `EffectiveSignature` con las 5 capas de cascade.
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* 4. Despachar a cada canal registrado.
* 5. Esperar al canal visual y limpiar los handles de preparación.
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
*
* Override layers (ver `resolver.ts`):
* 1. family base — SEMA_MAP.families
docs(sema,theming): el orden real, como se autora cada canal, y el hueco del theming Tres cosas que el cambio del sonido dejo sin plasmar. 1. EL ORDEN CANONICO MENTIA. La numeracion 1·2·3·4·5a·5b esta citada como canonica en sema.md, resolver.ts y engine.ts, y despues del reorden era falsa para `sound`: un pack ya no autora en 5a, lo hace en 1.5. Corregido en los tres sitios a la vez, con el porque — todo lo que AUTORA un sonido corre ahi conservando su precedencia relativa; solo cambio su posicion respecto al intent. El resto de la regla (channels/haptic/hold) mantiene el «gana el ultimo» porque no son ejes evaluativos. 2. COMO SE AUTORA CADA CANAL (CANON §7). El reparto por dueño estaba escrito; lo que un componente ESCRIBE, no. Tabla nueva: el canal visual no se escribe (se declara el evento y eidos reacciona), `sound` es UN NOMBRE, `haptic` es un `kind`. La regla es la misma en las tres filas y ese es el punto: un componente dice QUE ocurre, nunca cuan fuerte, cuan brillante ni cuanto dura. El sonido era la excepcion hasta ayer. 3. EL ANALISIS DEL THEMING que el autor pidio (theming/channels.md §5b). Lo que el cambio NO toco: nada de eidos. El canal visual se proyecta estampando data-event-*, y esa ruta quedo intacta — ni una receta, ni un token, ni un selector. Eidos no sabe que existe el sonido y no le hizo falta. Lo que cambio fue DONDE se autora (un catalogo en vez de 71 ficheros) y CUANDO se aplica (antes del intent), ambos dentro de sema. Lo que si cambio, y es lo util: `applyTheme(seed)` retunea SEIS ejes visuales (color·type·depth·shape·space·gradient) y el sonido tiene CERO. Antes esa asimetria se justificaba sola — con 214 reglas autorando 33 firmas a mano no habia objeto que retunear. Hoy hay exactamente uno: 16 nombres en un const. Un eje `sound` en ThemeSeed seria la misma forma que los otros seis. Y las tres puertas de personalizacion, medidas, ninguna llega al catalogo: `overrides.runtime` recorre rutas de SEMA_MAP y el catalogo no esta en el mapa (ademas se traga las erratas, S-09); `overrides.cascade` es por selector, no un tema; `masterGain` se descarta en SILENCIO (S-05/S-32). Un producto puede silenciar y puede pisar una ocurrencia, pero NO puede re-voceear el sistema. Queda escrito en vez de ser folclore. ⚠️ chronos: `src/uix/sema/components/chronos.ts` entro en la migracion de `769e426c5` pese a ser de escritura excluida. Era forzoso — con el tipo estrechado, dejarlo sin migrar rompe la compilacion del arbol entero — y el cambio es mecanico (soundTuning(...) -> nombre), sin decision de diseño. Su SPEC.md NO se ha tocado y sigue citando `soundTuning('commit.soft')`. VERIFICADO: sema+morfo+sound 437/437 · check 75 · docs:check 0/618. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* 1.5 the SOUND — the catalogue name selected by the morfo
* event and the cascade (sound-names.ts),
* applied BEFORE the intent so the
* evaluative profile always survives it
* 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
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* (applied to map at construction)
docs(reconciliation): soma deps truth + checklist enforcement column + sema cascade renumber + archetype pointers Phase 1d of PLAN-docs-reconciliation. - soma/README s3 + SOMA_ARCHITECTURE s2/s6/s12 + popover/link-preview READMEs: positioning is the in-house engine (layers/floating + $ethereal); @floating-ui is devDep-only. Layers table gains Stacking / AxialDrag / ZoomPan / ImageProvider / ListSelection (popper/ is an empty leftover dir — not documented). clsx documented as it really is: NOT declared but still imported by props/props.ts via a svelte transitive (phantom dep — flagged for a decision, chip spawned). - COMPONENT_COMPLETION_CHECKLIST: new Enforcement column on every rule table (audit | tool:X | manual) so declared-but-unchecked rules are explicit; morfo rule IDs renamed M-* -> A-* to grep-match what component-audit.ts emits; stale rows fixed against the corrected script (D-1.2 v2 9-tab, D-3.1 single eidosSnippet, E-2.2 wrapper import); implemented-but-undeclared rules added (R-2.7 literal typography, A-3.4b verb/family pairing); A-3.3 family count decopied; sI now requires an honest Enforcement value for new rules. - Sema cascade numbering unified to 1 family - 2 intent - 3 morfo - 4 runtime - 5a packs - 5b app across sema/README, engine.ts, resolver.ts, resolver.test.ts and soma/runtime.svelte.ts (three divergent schemes coexisted; CLAUDE.md already uses the chosen one). - '24 archetypes' copied lists replaced by pointers to ARCHETYPE_VOCABULARY in active_architecture s6 + morfo/README. - TriggerOptions.semantic JSDoc: phantom defaultSemantic removed (implemented shape is additive allowedFamilies, D.11); 'warns via logger' claim corrected — the override is silently ignored on non-polymorphic events (no warn exists in code). npm run check: 61 errors = pre-existing baseline, 0 new. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
* 5a. pack cascade — engineOpts.components (per-component
* packs, prepended)
* 5b. app cascade — engineOpts.overrides.cascade
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* (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';
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
import type { DomApplier } from '$adom';
import type { Logger } from '$libs/logger';
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
import { HapticChannel, type HapticChannelDom, type HapticChannelOptions } from './chans/haptic';
import { SoundChannel, type SoundChannelDom, type SoundChannelOptions } from './chans/sound';
refactor(sound): extraer el motor Web Audio de sema al art `$sound` `sema/chans/sound.ts` tenía 407 líneas de las que ~255 (≈63%) eran maquinaria Web Audio —ciclo de vida del AudioContext, síntesis, samples, desbloqueo por gesto— mezclada con doctrina perceptiva. Deuda arrastrada, con tres síntomas medidos: el import era estático, así que toda app con sema metía el sintetizador en el bundle aunque el sonido estuviera apagado (que es el default); faltaba ciudadanía que `$scene` ya resuelve; y la costura de inyección (`audioContextFactory`) llevaba ahí sin usar desde el principio. Mismo movimiento que `$motion` hizo desde eidos: el art se lleva el RUNTIME, la capa conserva sus DATOS y su doctrina. - `$sound` / `EngineSound`: UN AudioContext por documento (los navegadores los limitan y el gesto de desbloqueo es por contexto), síntesis de earcon, samples con caché y fallback a síntesis, `autoSuspend` OPT-IN —suspender con la pestaña oculta es correcto para earcons y erróneo para contenido, así que es decisión de quien compone— y aviso cuando un segundo contexto va vivo. Puertos `SoundDom` / `SoundTimers` inyectados; no importa ningún otro art ni nada de `$uix/sema`, que es la prueba objetiva del corte. - `SoundChannel`: 407 → 136 líneas. Solo doctrina: el gate de `prepare`, la política de reducción y el reparto «el canal resuelve el NIVEL, el motor aplica la ganancia». Sema no gana ni un import: recibe el motor por puerto. - `uix.sound` en standalone y attach con `ownsSound` (idioma ya shipped: `ownsMotion` / `ownsScene`), fila `sound` en la tabla ejecutable `contracts.ts`, y `defineEngineSound()` para el camino de app. - Tests nuevos: `engine-sound.test.ts` (11), `sound-port.test.ts` (guard de deriva de tipos + la regla de propiedad) y `sound-e2e.test.ts`, que recorre `emit -> cascada -> canal -> art -> grafo real`: el camino que las 16 suites previas no cubrían porque paraban en canales falsos. Sin `diagnostics.ts` ni `errors.ts`, y es decisión: espejo de `$scene`, aquí todo fallo es degradación documentada, no error de programador. Verificación: 17 suites / 198 tests · `check` en la baseline exacta (73 errores, 0 propios) · `sound.test.ts` verde SIN tocar un solo assert, que era el criterio de que el movimiento fue value-preserving. Planes: `PLAN-sound-engine.md` (completo, con el registro de la revisión adversarial E-1..E-7), `PLAN-audio-player.md` (aparcado tras el análisis del reproductor, con sus correcciones en cabecera) y `CONTINUE-sound-engine.md` (handoff). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import type { EngineSound } from '$sound';
import { VisualChannel, type VisualChannelOptions } from './chans/visual';
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
import {
AnnounceChannel,
type AnnounceChannelDom,
type AnnounceChannelOptions
} from './chans/announce';
import { SemaDuplicateChannelError } from './errors';
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import { namedSampleUrls, type SoundName } from './sound-names';
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import type { NamedSound } from './sema-map';
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
import {
applyMapOverrides,
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
assertMapPaths,
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
resolveSignature,
type EffectiveSignature,
type SemaCascadeRule
} from './resolver';
import { SEMA_MAP, type SemaMap, type Sema } from './sema-map';
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
import type { DeltaValue, SemaPreferences } from './channels';
import type { SemanticSignal } from './signal';
import type { SignalProjector } from './projection';
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
import { semaDelay, type SemaTimerScheduler, type SemaTimerHandle } from './timers';
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
export interface EngineSemanticOverrides {
/**
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* 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
/**
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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[];
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
export interface EngineSemanticOptions {
/** VisualChannel built-in. `false` to disable; object for opts; Channel to replace. */
visual?: false | VisualChannelOptions | Channel;
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/** SoundChannel built-in. `true`/object/Channel to enable; `false`/undef to skip. */
sound?: true | false | SoundChannelOptions | Channel;
refactor(sound): extraer el motor Web Audio de sema al art `$sound` `sema/chans/sound.ts` tenía 407 líneas de las que ~255 (≈63%) eran maquinaria Web Audio —ciclo de vida del AudioContext, síntesis, samples, desbloqueo por gesto— mezclada con doctrina perceptiva. Deuda arrastrada, con tres síntomas medidos: el import era estático, así que toda app con sema metía el sintetizador en el bundle aunque el sonido estuviera apagado (que es el default); faltaba ciudadanía que `$scene` ya resuelve; y la costura de inyección (`audioContextFactory`) llevaba ahí sin usar desde el principio. Mismo movimiento que `$motion` hizo desde eidos: el art se lleva el RUNTIME, la capa conserva sus DATOS y su doctrina. - `$sound` / `EngineSound`: UN AudioContext por documento (los navegadores los limitan y el gesto de desbloqueo es por contexto), síntesis de earcon, samples con caché y fallback a síntesis, `autoSuspend` OPT-IN —suspender con la pestaña oculta es correcto para earcons y erróneo para contenido, así que es decisión de quien compone— y aviso cuando un segundo contexto va vivo. Puertos `SoundDom` / `SoundTimers` inyectados; no importa ningún otro art ni nada de `$uix/sema`, que es la prueba objetiva del corte. - `SoundChannel`: 407 → 136 líneas. Solo doctrina: el gate de `prepare`, la política de reducción y el reparto «el canal resuelve el NIVEL, el motor aplica la ganancia». Sema no gana ni un import: recibe el motor por puerto. - `uix.sound` en standalone y attach con `ownsSound` (idioma ya shipped: `ownsMotion` / `ownsScene`), fila `sound` en la tabla ejecutable `contracts.ts`, y `defineEngineSound()` para el camino de app. - Tests nuevos: `engine-sound.test.ts` (11), `sound-port.test.ts` (guard de deriva de tipos + la regla de propiedad) y `sound-e2e.test.ts`, que recorre `emit -> cascada -> canal -> art -> grafo real`: el camino que las 16 suites previas no cubrían porque paraban en canales falsos. Sin `diagnostics.ts` ni `errors.ts`, y es decisión: espejo de `$scene`, aquí todo fallo es degradación documentada, no error de programador. Verificación: 17 suites / 198 tests · `check` en la baseline exacta (73 errores, 0 propios) · `sound.test.ts` verde SIN tocar un solo assert, que era el criterio de que el movimiento fue value-preserving. Planes: `PLAN-sound-engine.md` (completo, con el registro de la revisión adversarial E-1..E-7), `PLAN-audio-player.md` (aparcado tras el análisis del reproductor, con sus correcciones en cabecera) y `CONTINUE-sound-engine.md` (handoff). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* The shared Web Audio runtime (`$sound`), injected by the composition root
* as `uix.sound`. Forwarded to the built-in `SoundChannel` so the whole
* document meets at ONE `AudioContext`: the browser caps them and the
* autoplay unlock gesture is per-context, so a second engine would leave
* one of the two mute. The engine is NOT owned here — whoever created it
* disposes it (`ownsSound` in `active-uix`). When absent, the channel
* creates and owns a private one (direct construction / unit tests).
*/
soundEngine?: EngineSound;
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/** HapticChannel built-in. `true`/object/Channel to enable; `false`/undef to skip. */
haptic?: true | false | HapticChannelOptions | Channel;
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
/**
* AnnounceChannel built-in (accessible ARIA live region). `true`/object to
* enable; `false`/undef to skip. Opt-in like sound / haptic. Pass
* `{ announce }` to delegate to an existing announcer (e.g.
* `ActiveUix.announce`); otherwise it owns a pair of live regions built from
* the injected `dom`. Book `BK-SIGNAL-A11Y` / `BK-A11Y-CRITICAL`.
*/
announce?: true | false | AnnounceChannelOptions | Channel;
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO F2 — lote mecánico (13 ítems): - DEP-2 ogl eliminado (0 imports) · DEP-1 clsx inlineado como toClassString propio + suite de contrato (props.test.ts; soma.md §12 cerrado). - THM-7: los 5 selectores manuales de sema.md reescritos con semaSelector (los ejemplos [data-toast-root] apuntaban a un part INEXISTENTE — la deriva que el builder previene, demostrada en el propio doc). - MOR-1 escape isomorfo + validación de attr-names en semaSelector + 9 tests (selectors.test.ts, matches() real con comillas/corchetes) · MOR-2 partMarkerAttr = única fuente compilador↔builder + test de paridad · MOR-3 _resetCompileCache borrado (0 usos). - SOM-2 keydown continue en match sin handler + keyboardFixtureMorfo · SOM-1 no-await de handlers (censo async = 0; contrato V1 cumplido) + pin. - SEM-2 trigger pre-attacha catch con logger (void trigger sin unhandled rejection; throw intacto para awaiters) + pin · SEM-3 fallback muerto de applyDominance → skip defensivo + timer tope de awaitExpression cancelado · SEC-1 adjudicado YA implementado (assertCssVariableValue desde 2026-05-11) + pin del path de VALOR. - accordion → outline (§32; su outline:none dejaba CERO anillo en HCM) — verificado en vivo · THM-6 radius-full 9999px · EID-4 recuentos 33. F3 — censos con guard: - SOM-3 cerrado: announcer + image-provider migrados a scheduler-preferred (consumidores cableados: date/time-field vía soma.uix.timers; avatar/image vía eidos.timers — verificado en vivo); guard de timers ENSANCHADO de soma/components a TODO soma y pasado a EVIDENCIA (setTimeout exige .schedule( en el fichero — layers/ y datetime/ escapaban del ámbito viejo). - THM-5: R-4.7 nueva (válvula same-line /* important: <razón> */, escaneo comment-blanked) + las 15 declaraciones anotadas con su razón + canon recipe-contract §3/§4. - SOM-4 adjudicado: el censo/guard YA existían (49 pins); knob/mask-field/ timeline pinneados (overrides documentados en call-site); media-player Batch-4 (35 hits, cero renderProps) = único batch restante, registrado. - THM-4 doctrinado en eidos.md §unused (comportamiento/composición = legítimo; deuda = eje visual sin consumidor; hotspots por lotes). F4-C — corpus documental (decisiones de usuario aplicadas): - DOC-3: los 15 enlaces muertos resueltos (repoint a la edición FINAL trackeada / des-link históricos) · docs:check I6-links WARN→ERROR. - DOC-1: tabla «Build contract» MIGRADA a component-guide con estados modernizados (A3–A5 → LIVE + guards de hoy); banners reapuntados; citas de CANON/sema.md historificadas; lápida-redirect en el §13 del fósil. - DOC-4: hold chain → holds.ts · FAQ event:* SUPERSEDED por signatures · gradient añadido a los DOS capstones (sextet real) · nota de paleta de demo-authoring corregida (universalPaletteDecls + decisión THM-2 = mecanismo universal como sucesor del tracker borrado). - DOC-5/6: recuentos anti-frágiles datados · §4.11 dup → §4.12 · Known gaps historificado · N-6/N-7 recuperadas de git (d68d2c45^) y canonizadas en eidos.md §pickers · authoring E2 → canon/tsc.md · air-old des-linkado · EID-3 (placement) en la fila RTL · AUX-2 disabledDom documentado. SEM-4 sesión 1 — el close polimórfico de los pickers, VIVO (D.11): - Reconciliación: los morfos ya no declaran close (delegated al Popover, de-dialoged 06-27); el agujero real era el cierre programático bypaseando dismissWith → save/cancel/select eran perceptualmente SILENCIOSOS. - Fix: PickerShellHandle.setPopoverDismiss + closeWith(cause) en los 5 providers (14 sitios; select/commit → 'save' = commit.save+fulfill, cancel → 'cancel' = emerge; fallback raw para headless) + UN inyector en el eidos PickerShell root (norma N-8). Picker genérico fuera a propósito (ya suena commit-set/cancel por diseño S9). - Verificado en vivo (date-picker): Done → close·commit·fulfill·active · Cancel → close·emerge · cierre real. Gates: matriz 141/141 (los 6 morfos nuevos de la pista de texto paralela también PASS) · contracts 38/38 · eidos 314 · sema 178 · morfo 94 · docs:check 0/0 con I6 en error · baseline propio 57. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
/**
* Optional diagnostics logger. When omitted, channel failures stay
* silent — a deliberate purity rule: the engine knows no platform, so it
* never falls back to `console` (the layer contract guards this). In
* practice the failures are NOT silent in apps: the ActiveUix
* composition root always injects its logger
* (`active-uix.svelte.ts` — `logger: eventsOpts.logger ?? logger`), so a
* silent engine only exists in bare constructions (unit tests /
* headless), by choice (SEM-2 adjudication, 2026-07-11).
*/
logger?: Logger;
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/**
* 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[];
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/**
* The SOUND PACK this app speaks with — entries layered over the framework's
* own, by name. This is the boot-time form of {@link EngineSemantic.applySoundPack};
* pass it here when the voice is a property of the product rather than
* something a theme switches at runtime.
*
* A partial pack is legitimate: names it omits keep the default sound, so a
* product can replace three earcons and inherit the rest.
*
* ```ts
* createActiveUix({ events: { sound: true, sounds: uiMp3Pack } })
* ```
*/
sounds?: Partial<Record<SoundName, NamedSound>>;
docs(reconciliation): soma deps truth + checklist enforcement column + sema cascade renumber + archetype pointers Phase 1d of PLAN-docs-reconciliation. - soma/README s3 + SOMA_ARCHITECTURE s2/s6/s12 + popover/link-preview READMEs: positioning is the in-house engine (layers/floating + $ethereal); @floating-ui is devDep-only. Layers table gains Stacking / AxialDrag / ZoomPan / ImageProvider / ListSelection (popper/ is an empty leftover dir — not documented). clsx documented as it really is: NOT declared but still imported by props/props.ts via a svelte transitive (phantom dep — flagged for a decision, chip spawned). - COMPONENT_COMPLETION_CHECKLIST: new Enforcement column on every rule table (audit | tool:X | manual) so declared-but-unchecked rules are explicit; morfo rule IDs renamed M-* -> A-* to grep-match what component-audit.ts emits; stale rows fixed against the corrected script (D-1.2 v2 9-tab, D-3.1 single eidosSnippet, E-2.2 wrapper import); implemented-but-undeclared rules added (R-2.7 literal typography, A-3.4b verb/family pairing); A-3.3 family count decopied; sI now requires an honest Enforcement value for new rules. - Sema cascade numbering unified to 1 family - 2 intent - 3 morfo - 4 runtime - 5a packs - 5b app across sema/README, engine.ts, resolver.ts, resolver.test.ts and soma/runtime.svelte.ts (three divergent schemes coexisted; CLAUDE.md already uses the chosen one). - '24 archetypes' copied lists replaced by pointers to ARCHETYPE_VOCABULARY in active_architecture s6 + morfo/README. - TriggerOptions.semantic JSDoc: phantom defaultSemantic removed (implemented shape is additive allowedFamilies, D.11); 'warns via logger' claim corrected — the override is silently ignored on non-polymorphic events (no warn exists in code). npm run check: 61 errors = pre-existing baseline, 0 new. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
/** Override layers 4 (runtime path) + 5b (app cascade). */
overrides?: EngineSemanticOverrides;
/**
* Projector forwarded to the built-in VisualChannel. The engine stores no
* projector itself; channels own their prepare/materialization details.
*/
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);
/**
* Managed timer scheduler (`uix.timers`) forwarded to the built-in
* channels. Every perceptual timer — the visual hold, the haptic delay,
* the sound earcon completion — runs on it instead of a raw `setTimeout`,
* so timing is cancellable, observable and test-deterministic. `active-uix`
* injects `uix.timers`; omitting it makes the channels fall back to
* `setTimeout` (unit-test / degraded path only).
*/
timers?: SemaTimerScheduler;
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
/**
* Per-channel reduction preferences (BK-REDUCTIONS, book ch. 32 §12 / ch.
* 33 §7). Forwarded to the built-in `sound` + `haptic` channels so an app
* can attenuate (`'reduce'`) or silence (`'off'`) a modality symmetrically.
* Read at dispatch, so backing it with reactive state makes it live.
* Visual-motion reduction lives in eidos CSS, not here.
*/
preferences?: SemaPreferences;
/**
* Frequency memory (BK-FREQ-MEMORY, book ch. 32 §11): repeated occurrences
* of the SAME event attenuate their non-visual channels so the 50th
* autosave does not sound like the first. `threat` is never damped. Default
* ON; set `false` to disable. Requires a `timers` scheduler for the decay
* window (falls back to a raw timeout in unit tests).
*/
frequencyMemory?: boolean;
/**
* Dominance arbiter (BK-DOMINANCE, book ch. 30 §7): when signals overlap in
* time, a signal dominated by a still-active one (evaluable > structural;
* higher activation; recency) has its non-visual channels muted while its
* visual hold survives. `threat` is never muted. Default ON; set `false` to
* disable.
*/
dominance?: boolean;
}
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;
}
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
// ── Frequency memory (C-2) + dominance (C-3) tuning ───────────────────────────
/** Non-threat signals of the same key start attenuating after this count. */
const FREQ_THRESHOLD = 2;
/** Attenuation lost per repeat past the threshold. */
const FREQ_STEP = 0.15;
/** Attenuation floor — repeated signals never fall below this fraction. */
const FREQ_FLOOR = 0.25;
/** Window of silence (ms) after which a key's repeat count resets to full. */
const FREQ_WINDOW_MS = 2000;
/** How long (ms) an occurrence counts as "active" for dominance overlap. */
const DOMINANCE_WINDOW_MS = 400;
/**
* Scale (or, at `factor <= 0`, mute) the non-visual channels. Muting DROPS
* `sound` / `haptic` from `activeChannels` rather than scaling to zero, because
* haptic intensity is floored (a `0` still buzzes). The visual hold and the
* accessible `announce` channel are never touched.
*/
function attenuateNonVisual(effective: EffectiveSignature, factor: number): EffectiveSignature {
if (factor >= 1) return effective;
if (factor <= 0) {
return {
...effective,
activeChannels: effective.activeChannels.filter((c) => c !== 'sound' && c !== 'haptic')
};
}
const next: EffectiveSignature = { ...effective };
if (next.sound) next.sound = { ...next.sound, gain: next.sound.gain * factor };
if (next.haptic) next.haptic = { ...next.haptic, intensity: next.haptic.intensity * factor };
return next;
}
/** Activation level implied by an intent (book ch. 31 §5 / ch. 32 §2). */
function activationOf(intent: EffectiveSignature['intent']): number {
switch (intent) {
case 'threat':
return 4;
case 'fulfill':
return 3;
case 'risk':
return 2;
case 'loss':
case 'affirm':
return 1;
default:
return 0;
}
}
/**
* Dominance rank of an occurrence (higher wins). One comparable number for the
* book's criteria: an evaluable event (commit / signal) outranks a structural
* one; within that, higher activation outranks lower. Recency is handled by the
* caller using a strict `>` so an equal-ranked newcomer stays audible.
*/
function occurrenceRank(effective: EffectiveSignature): number {
const evaluable = effective.family === 'commit' || effective.family === 'signal' ? 1 : 0;
return evaluable * 10 + activationOf(effective.intent);
}
feat(eidos,sema): un tema es UNA cosa — siete ejes, y ninguno fuera del alcance Cierra el eje: el haptico entra al mapa, `masterGain` deja de descartarse, y `applyTheme` gana el septimo eje. ⚠️ CORRIGE MI PROPIA PROPUESTA. Dije que la puerta unica iba en la raiz de composicion. Al medirlo, la dependencia corre AL REVES: `ActiveEidos` tiene `#uix` y lo conduce (registra presets con `uix.motion`), mientras `ActiveUix` no tiene eidos. Un tema compuesto en la raiz habria invertido la direccion. Va donde ya vive el metodo que se llama a si mismo «the capstone composing them», y que hasta hoy componia seis de ocho. eidos.applyTheme({ color: '#3b5bdb', sound: { soft: { gain: 0.08 } } }); eidos.clearTheme(); // revierte los siete Un eje que pasas se aplica, uno que omites revierte — la misma lectura atomica que ya tenian los seis visuales, ahora sobre siete. Sin `uix` (render headless) el eje perceptual se salta y los visuales siguen: un tema que se aplica a medias es mejor que uno que revienta. EL HAPTICO, AL MAPA. Su tabla `kind → pattern` vivia como const dentro del canal, asi que era el unico eje perceptual que un tema no alcanzaba — exactamente la asimetria que el trabajo del sonido existia para quitar, dejada en pie un canal mas alla. Ahora es `SEMA_MAP.haptics`, con perfiles derivados (`floorMs` + `scale`/`gapMs`) o constantes (`pattern`): los tres kinds evaluativos son ritmos constantes porque lo que hace reconocible a un `error` es su cadencia, no su longitud. El canal lo lee por GETTER, no por copia, asi que un `applyMap()` en caliente le llega sin re-registrarlo. `masterGain` SE APLICA. Pasarlo con motor compartido se tiraba en silencio (S-05/S-32) pese a ser una perilla viva del motor y la forma natural de poner el volumen desde la raiz. Ahora llega a `master.setGain`, y de las opciones que de verdad no pueden aplicarse a un motor ajeno se AVISA en vez de desaparecer. Y `DeltaValue` pasa a exportarse: es la forma de toda semilla de override, sin ella un consumidor no puede tipar el tema que esta pasando. Su ausencia era un error VIVO en el estudio de sema, que lo importaba y no podia — por eso check BAJA a 73 desde el baseline de 75. VERIFICADO: theming 8/8 (incluye re-vocear el haptico y que la tabla autorada quede intacta) · sema+eidos+morfo 802/802 · check 73 (baseline 75, −2 por el export) · docs:check 0/618 · suite completa 7 fallos, los mismos ajenos y preexistentes de contracts.test. ⚠️ Nada de esto se ha OIDO ni TOCADO en un dispositivo real: todo esta medido con el resolver y las suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* What a SHARED engine cannot be configured with from here. `masterGain` is
* deliberately absent: it IS applicable (a live knob on the engine) and is
* applied instead of dropped — the defect audit S-05 / S-32 measured.
*/
const SHARED_ENGINE_IGNORED =
'engine construction options were ignored: the engine is shared (injected by the composition root), so configure it there. `masterGain` is the exception and WAS applied.';
export class EngineSemantic {
private readonly channels = new Map<string, Channel>();
private nextSignalId = 0;
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* The perceptual map in force. NOT readonly: `applySounds` / `applyMap`
* rebuild it from the canonical `SEMA_MAP` so a theme can be retuned live and
* reverted, the same shape every eidos axis already has (`applyX` → managed
* block → `clearX`). Every `emit` reads it fresh, so a retune takes effect on
* the next occurrence with no re-boot.
*/
// Assigned in the constructor through `rebuildMap()`, which TypeScript's
// definite-assignment analysis cannot follow across a method call.
private map!: SemaMap;
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** The seed the current map was built from — kept so a retune can rebuild. */
private mapSeed: Record<string, DeltaValue> | undefined;
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** An installed sound pack, layered over the authored catalogue. */
private packOverride: Partial<Record<SoundName, NamedSound>> | undefined;
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** The root's shared engine, for warming when the sound channel is not ours. */
private sharedSoundEngine: EngineSound | undefined;
/** Only consulted on that same path — the built-in channel owns its own copy. */
private soundPreferences: SemaPreferences | undefined;
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>();
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
private readonly timers: SemaTimerScheduler | undefined;
private readonly frequencyMemory: boolean;
private readonly dominance: boolean;
/** Per-event-key repeat counts for frequency memory (C-2). */
private readonly freqCounts = new Map<string, { count: number; reset?: SemaTimerHandle }>();
/** Occurrences still "active" for dominance (C-3), keyed by occurrence id. */
private readonly activeOccurrences = new Map<string, { rank: number; expire: SemaTimerHandle }>();
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente `data-event-*` es UNA RANURA por elemento. Tres auditorias independientes encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie podia distinguir una redireccion legitima de una deriva, porque la opcion que redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia. EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64 ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo por diseño. LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba `data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a ser el suelo correcto. A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms, con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el trigger, press-squeeze jamas. `regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`, este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0 huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no se anuncia»), con sus 4 declaraciones muertas. El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay animaciones, asi que awaitExpression volvia al instante. El navegador midio el commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion. Medir la envolvente no es medir la salida. `renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform, aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement. Ademas: 5 selectores muertos en el predicado del focus-scope de palabras (barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome (inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union inline al `Direction` canonico, que destapo que el censo de direccion grepea el NOMBRE del tipo · deuda de chronos (scope, barrel, README). Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2 (el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite). Todos los guards nuevos vistos fallar antes de arreglar nada. Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno, reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177. Handoff: docs/process/CONTINUE-perceptual-surface.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* The perceptual SURFACE registry: target → a promise that settles when the
* occurrence currently projecting on it is done.
*
* `data-event-*` is one slot per element, so two occurrences on a node
* cannot both express. `regime: 'queue'` reads this to wait its turn
* instead of displacing (`SemaRegime`). Keyed by element and cleared by the
* holder itself, so it never outlives the emit that created it.
*/
private readonly surfaces = new Map<HTMLElement, Promise<void>>();
constructor(opts: EngineSemanticOptions = {}) {
this.logger = opts.logger;
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
this.timers = opts.timers;
this.frequencyMemory = opts.frequencyMemory ?? true;
this.dominance = opts.dominance ?? true;
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Layer 4 — runtime path overrides. The seed is REMEMBERED, not just
// consumed, so `applyMap` / `clearMap` can rebuild from the canonical
// map instead of layering onto whatever was applied before.
this.mapSeed = opts.overrides?.runtime;
assertMapPaths(SEMA_MAP, this.mapSeed);
// The app's own sound pack, if it brought one: same layering as a runtime
// `applySoundPack`, just decided at boot.
this.packOverride = opts.sounds;
this.rebuildMap();
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
docs(reconciliation): soma deps truth + checklist enforcement column + sema cascade renumber + archetype pointers Phase 1d of PLAN-docs-reconciliation. - soma/README s3 + SOMA_ARCHITECTURE s2/s6/s12 + popover/link-preview READMEs: positioning is the in-house engine (layers/floating + $ethereal); @floating-ui is devDep-only. Layers table gains Stacking / AxialDrag / ZoomPan / ImageProvider / ListSelection (popper/ is an empty leftover dir — not documented). clsx documented as it really is: NOT declared but still imported by props/props.ts via a svelte transitive (phantom dep — flagged for a decision, chip spawned). - COMPONENT_COMPLETION_CHECKLIST: new Enforcement column on every rule table (audit | tool:X | manual) so declared-but-unchecked rules are explicit; morfo rule IDs renamed M-* -> A-* to grep-match what component-audit.ts emits; stale rows fixed against the corrected script (D-1.2 v2 9-tab, D-3.1 single eidosSnippet, E-2.2 wrapper import); implemented-but-undeclared rules added (R-2.7 literal typography, A-3.4b verb/family pairing); A-3.3 family count decopied; sI now requires an honest Enforcement value for new rules. - Sema cascade numbering unified to 1 family - 2 intent - 3 morfo - 4 runtime - 5a packs - 5b app across sema/README, engine.ts, resolver.ts, resolver.test.ts and soma/runtime.svelte.ts (three divergent schemes coexisted; CLAUDE.md already uses the chosen one). - '24 archetypes' copied lists replaced by pointers to ARCHETYPE_VOCABULARY in active_architecture s6 + morfo/README. - TriggerOptions.semantic JSDoc: phantom defaultSemantic removed (implemented shape is additive allowedFamilies, D.11); 'warns via logger' claim corrected — the override is silently ignored on non-polymorphic events (no warn exists in code). npm run check: 61 errors = pre-existing baseline, 0 new. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
// Layers 5a/5b — flatten per-component packs (lower precedence) +
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// app-level cascade (higher precedence on tie).
const packCascade: SemaCascadeRule[] = [];
const preloadUrls = new Set<string>();
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
for (const pack of opts.components ?? []) {
for (const rule of pack.cascade) {
packCascade.push(rule as SemaCascadeRule);
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
for (const url of pack.preloadSamples ?? []) {
preloadUrls.add(url);
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
}
this.cascade = [...packCascade, ...(opts.overrides?.cascade ?? [])];
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// The map's own samples — a boot-time `sounds` pack — warm alongside the
// component packs' declared URLs. At runtime `warmSamples` does it.
for (const url of namedSampleUrls(this.map)) {
preloadUrls.add(url);
}
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// 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,
timers: opts.timers
});
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;
fix(sound): la auditoría del art, cerrada — niveles por reproducción y opciones que no mienten Los tres hallazgos que quedaban de `PLAN-sound-engine` §16, firmados y aplicados. Dos con código, uno con una medición que cambia la doctrina. **A-5 — `gainScale` es de la reproducción, no del documento.** `play()` escribía `master × gainScale` en el nodo master, del que cuelgan TODOS los earcons. Un parámetro transitorio corrompía un mando de política: el nivel se filtraba entre reproducciones, una nota en vuelo saltaba de volumen, y los dos consumidores que motivan el motor compartido —la reducción de sema y el ducking de un reproductor— se pisaban sobre un único valor. Ahora el factor multiplica el pico de la envolvente de ESA reproducción, y **también la profundidad del AM**: dejarla absoluta habría hecho que un earcon atenuado sonara relativamente más áspero, cambiando el timbre en vez del volumen, que es lo contrario de lo que significa una reducción. **RC-5 se reescribe, y es legítimo**: un contrato de regresión protege comportamiento, no defectos. Lo que pineaba —«la ganancia se fija ANTES de que la síntesis reviente»— sólo importaba porque el valor iba a un nodo compartido. La cobertura no se pierde, se coloca donde vive cada responsabilidad: `sound-port.test.ts` ya pineaba que el canal resuelve el NIVEL y lo entrega; `sound.test.ts` pinea ahora que el master **no se mueve**; y el art estrena un contexto falso que sí construye el grafo, para ver el pico de la envolvente. **A-4 — las opciones del canal, imposibles de equivocar.** `SoundChannelOptions` pasa a unión discriminada: `{ engine, preferences?, logger? }` **o** la forma de construcción (`audioContextFactory` / `fetcher` / `dom` / `masterGain` / `timers`). Nunca ambas — un motor llega ya construido, así que sus opciones de construcción no significan nada a su lado, y hasta hoy se aceptaban y se tiraban en silencio. La defensa primaria es el tipo (doctrina de la casa), con un guard `@ts-expect-error` que hace fallar `check` el día que la unión deje de rechazar la mezcla; el aviso por logger es la red para JS. `engine.ts` pasa de un spread a tres ramas en orden de precedencia. Y **`fetchFn` → `fetcher`**, para alinearse con `$perm`: un nombre por concepto. `audioContextFactory` **se queda** — es el patrón `idFactory` que ya usan `$logger` y `$bus`, y nombra el tipo exacto que fabrica (`context` a secas colisiona con el contexto GL de `$scene`). **A-3 — S-1 aceptado, medido: 2,2 KB.** El art entero son 6.410 bytes minificados / 2.177 gzip. Y mi propia propuesta para cerrarlo era falsa: quitar el fallback del canal no saca el art de ningún bundle, porque `createActiveUix` y `defineEngineSound` lo importan incondicionalmente. Cerrarlo de verdad exige carga diferida, que choca con que `prime()` deba ser síncrono dentro del gesto, y volver `uix.sound` perezoso — cambio de superficie pública por 2,2 KB. Se acepta: el art es servicio de núcleo, como `motion` y `scene`. Queda corregida la doctrina: S-1 era el más débil de los cuatro síntomas; los que justificaban la extracción eran S-2 y S-4. **La voz, declarada como diseño.** La quinta a 1.5×, el ADSR recortado a 15 %/40 %, el contour de ±400 cents y el mapeo del AM son decisiones perceptivas calibradas contra el vocabulario de sema, no maquinaria. Escrito en el README del art y en la cabecera de `engine-sound.ts`, con la regla: el día que un segundo consumidor quiera otra voz, ése es el momento de partir el art en dos —gobierno de contexto / voz—, no antes. Verificación: art + sema **17 suites / 204 tests** · `contracts.test.ts` 35/38 (los 3 rojos, ajenos) · `check` en la baseline exacta (73, 0 propios) · `docs:check` **0 errores** · los guards de A-5 **vistos fallar** al reintroducir la escritura al master · navegador `/temas/sema`, 4 ciclos de editar→disparar: **1 contexto, 16 osciladores, master en 1, cero errores de consola**. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Three shapes, in precedence order — and they are branches, not a
// spread, because the channel's options are a discriminated union:
// the engine's construction options exist ONLY when the channel
// builds its own (they used to be accepted and dropped in silence).
if (soundOptions.engine !== undefined) {
soundChannel = new SoundChannel({
engine: soundOptions.engine,
preferences: soundOptions.preferences ?? opts.preferences,
logger: opts.logger
});
} else if (opts.soundEngine !== undefined) {
feat(eidos,sema): un tema es UNA cosa — siete ejes, y ninguno fuera del alcance Cierra el eje: el haptico entra al mapa, `masterGain` deja de descartarse, y `applyTheme` gana el septimo eje. ⚠️ CORRIGE MI PROPIA PROPUESTA. Dije que la puerta unica iba en la raiz de composicion. Al medirlo, la dependencia corre AL REVES: `ActiveEidos` tiene `#uix` y lo conduce (registra presets con `uix.motion`), mientras `ActiveUix` no tiene eidos. Un tema compuesto en la raiz habria invertido la direccion. Va donde ya vive el metodo que se llama a si mismo «the capstone composing them», y que hasta hoy componia seis de ocho. eidos.applyTheme({ color: '#3b5bdb', sound: { soft: { gain: 0.08 } } }); eidos.clearTheme(); // revierte los siete Un eje que pasas se aplica, uno que omites revierte — la misma lectura atomica que ya tenian los seis visuales, ahora sobre siete. Sin `uix` (render headless) el eje perceptual se salta y los visuales siguen: un tema que se aplica a medias es mejor que uno que revienta. EL HAPTICO, AL MAPA. Su tabla `kind → pattern` vivia como const dentro del canal, asi que era el unico eje perceptual que un tema no alcanzaba — exactamente la asimetria que el trabajo del sonido existia para quitar, dejada en pie un canal mas alla. Ahora es `SEMA_MAP.haptics`, con perfiles derivados (`floorMs` + `scale`/`gapMs`) o constantes (`pattern`): los tres kinds evaluativos son ritmos constantes porque lo que hace reconocible a un `error` es su cadencia, no su longitud. El canal lo lee por GETTER, no por copia, asi que un `applyMap()` en caliente le llega sin re-registrarlo. `masterGain` SE APLICA. Pasarlo con motor compartido se tiraba en silencio (S-05/S-32) pese a ser una perilla viva del motor y la forma natural de poner el volumen desde la raiz. Ahora llega a `master.setGain`, y de las opciones que de verdad no pueden aplicarse a un motor ajeno se AVISA en vez de desaparecer. Y `DeltaValue` pasa a exportarse: es la forma de toda semilla de override, sin ella un consumidor no puede tipar el tema que esta pasando. Su ausencia era un error VIVO en el estudio de sema, que lo importaba y no podia — por eso check BAJA a 73 desde el baseline de 75. VERIFICADO: theming 8/8 (incluye re-vocear el haptico y que la tabla autorada quede intacta) · sema+eidos+morfo 802/802 · check 73 (baseline 75, −2 por el export) · docs:check 0/618 · suite completa 7 fallos, los mismos ajenos y preexistentes de contracts.test. ⚠️ Nada de esto se ha OIDO ni TOCADO en un dispositivo real: todo esta medido con el resolver y las suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// The shared engine is owned by whoever injected it, so most
// construction options cannot apply here. `masterGain` CAN —
// it is a live knob on the engine (`master.setGain`) and the
// natural way to set the volume from the composition root:
// `createActiveUix({ events: { sound: { masterGain: 0.5 } } })`.
// It used to compile and do absolutely nothing, because the
// engine filtered the key one level ABOVE the channel's own
// anti-smuggling warning (audit S-05 / S-32).
if (soundOptions.masterGain !== undefined) {
opts.soundEngine.master.setGain(soundOptions.masterGain);
}
const inapplicable = (
['audioContextFactory', 'fetcher', 'dom', 'timers'] as const
).filter((key) => soundOptions[key] !== undefined);
if (inapplicable.length > 0) {
opts.logger?.warn('sema.sound', SHARED_ENGINE_IGNORED, {
context: { ignored: inapplicable }
});
}
fix(sound): la auditoría del art, cerrada — niveles por reproducción y opciones que no mienten Los tres hallazgos que quedaban de `PLAN-sound-engine` §16, firmados y aplicados. Dos con código, uno con una medición que cambia la doctrina. **A-5 — `gainScale` es de la reproducción, no del documento.** `play()` escribía `master × gainScale` en el nodo master, del que cuelgan TODOS los earcons. Un parámetro transitorio corrompía un mando de política: el nivel se filtraba entre reproducciones, una nota en vuelo saltaba de volumen, y los dos consumidores que motivan el motor compartido —la reducción de sema y el ducking de un reproductor— se pisaban sobre un único valor. Ahora el factor multiplica el pico de la envolvente de ESA reproducción, y **también la profundidad del AM**: dejarla absoluta habría hecho que un earcon atenuado sonara relativamente más áspero, cambiando el timbre en vez del volumen, que es lo contrario de lo que significa una reducción. **RC-5 se reescribe, y es legítimo**: un contrato de regresión protege comportamiento, no defectos. Lo que pineaba —«la ganancia se fija ANTES de que la síntesis reviente»— sólo importaba porque el valor iba a un nodo compartido. La cobertura no se pierde, se coloca donde vive cada responsabilidad: `sound-port.test.ts` ya pineaba que el canal resuelve el NIVEL y lo entrega; `sound.test.ts` pinea ahora que el master **no se mueve**; y el art estrena un contexto falso que sí construye el grafo, para ver el pico de la envolvente. **A-4 — las opciones del canal, imposibles de equivocar.** `SoundChannelOptions` pasa a unión discriminada: `{ engine, preferences?, logger? }` **o** la forma de construcción (`audioContextFactory` / `fetcher` / `dom` / `masterGain` / `timers`). Nunca ambas — un motor llega ya construido, así que sus opciones de construcción no significan nada a su lado, y hasta hoy se aceptaban y se tiraban en silencio. La defensa primaria es el tipo (doctrina de la casa), con un guard `@ts-expect-error` que hace fallar `check` el día que la unión deje de rechazar la mezcla; el aviso por logger es la red para JS. `engine.ts` pasa de un spread a tres ramas en orden de precedencia. Y **`fetchFn` → `fetcher`**, para alinearse con `$perm`: un nombre por concepto. `audioContextFactory` **se queda** — es el patrón `idFactory` que ya usan `$logger` y `$bus`, y nombra el tipo exacto que fabrica (`context` a secas colisiona con el contexto GL de `$scene`). **A-3 — S-1 aceptado, medido: 2,2 KB.** El art entero son 6.410 bytes minificados / 2.177 gzip. Y mi propia propuesta para cerrarlo era falsa: quitar el fallback del canal no saca el art de ningún bundle, porque `createActiveUix` y `defineEngineSound` lo importan incondicionalmente. Cerrarlo de verdad exige carga diferida, que choca con que `prime()` deba ser síncrono dentro del gesto, y volver `uix.sound` perezoso — cambio de superficie pública por 2,2 KB. Se acepta: el art es servicio de núcleo, como `motion` y `scene`. Queda corregida la doctrina: S-1 era el más débil de los cuatro síntomas; los que justificaban la extracción eran S-2 y S-4. **La voz, declarada como diseño.** La quinta a 1.5×, el ADSR recortado a 15 %/40 %, el contour de ±400 cents y el mapeo del AM son decisiones perceptivas calibradas contra el vocabulario de sema, no maquinaria. Escrito en el README del art y en la cabecera de `engine-sound.ts`, con la regla: el día que un segundo consumidor quiera otra voz, ése es el momento de partir el art en dos —gobierno de contexto / voz—, no antes. Verificación: art + sema **17 suites / 204 tests** · `contracts.test.ts` 35/38 (los 3 rojos, ajenos) · `check` en la baseline exacta (73, 0 propios) · `docs:check` **0 errores** · los guards de A-5 **vistos fallar** al reintroducir la escritura al master · navegador `/temas/sema`, 4 ciclos de editar→disparar: **1 contexto, 16 osciladores, master en 1, cero errores de consola**. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
soundChannel = new SoundChannel({
engine: opts.soundEngine,
preferences: soundOptions.preferences ?? opts.preferences,
logger: opts.logger
});
} else {
soundChannel = new SoundChannel({
...soundOptions,
dom: soundOptions.dom ?? (isSoundChannelDom(opts.dom) ? opts.dom : undefined),
timers: soundOptions.timers ?? opts.timers,
preferences: soundOptions.preferences ?? opts.preferences,
logger: opts.logger
});
}
}
this.register(soundChannel);
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Pre-decode the samples declared by component packs so the first emit
// doesn't pay the fetch + decode latency.
//
// Both are REMEMBERED rather than just used here, because `warmSamples`
// needs the same dispatch when a pack is swapped at runtime: having it
// at boot only meant an app with its own sound channel warmed once and
// never again. `opts.preferences` and not the channel's — the built-in
// channel owns its own copy and this field is only read on the branch
// where the channel is somebody else's.
this.sharedSoundEngine = opts.soundEngine;
this.soundPreferences = opts.preferences;
if (preloadUrls.size > 0) this.warmUrls([...preloadUrls]);
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
if (opts.announce !== undefined && opts.announce !== false) {
let announceChannel: Channel;
if (isChannel(opts.announce)) {
announceChannel = opts.announce;
} else {
const announceOptions = opts.announce === true ? {} : opts.announce;
announceChannel = new AnnounceChannel({
...announceOptions,
dom: announceOptions.dom ?? (isAnnounceChannelDom(opts.dom) ? opts.dom : undefined)
});
}
this.register(announceChannel);
}
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if (opts.haptic !== undefined && opts.haptic !== false) {
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
let hapticChannel: Channel;
if (isChannel(opts.haptic)) {
hapticChannel = opts.haptic;
} else {
const hapticOptions = opts.haptic === true ? {} : opts.haptic;
hapticChannel = new HapticChannel({
...hapticOptions,
dom: hapticOptions.dom ?? (isHapticChannelDom(opts.dom) ? opts.dom : undefined),
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
timers: hapticOptions.timers ?? opts.timers,
feat(eidos,sema): un tema es UNA cosa — siete ejes, y ninguno fuera del alcance Cierra el eje: el haptico entra al mapa, `masterGain` deja de descartarse, y `applyTheme` gana el septimo eje. ⚠️ CORRIGE MI PROPIA PROPUESTA. Dije que la puerta unica iba en la raiz de composicion. Al medirlo, la dependencia corre AL REVES: `ActiveEidos` tiene `#uix` y lo conduce (registra presets con `uix.motion`), mientras `ActiveUix` no tiene eidos. Un tema compuesto en la raiz habria invertido la direccion. Va donde ya vive el metodo que se llama a si mismo «the capstone composing them», y que hasta hoy componia seis de ocho. eidos.applyTheme({ color: '#3b5bdb', sound: { soft: { gain: 0.08 } } }); eidos.clearTheme(); // revierte los siete Un eje que pasas se aplica, uno que omites revierte — la misma lectura atomica que ya tenian los seis visuales, ahora sobre siete. Sin `uix` (render headless) el eje perceptual se salta y los visuales siguen: un tema que se aplica a medias es mejor que uno que revienta. EL HAPTICO, AL MAPA. Su tabla `kind → pattern` vivia como const dentro del canal, asi que era el unico eje perceptual que un tema no alcanzaba — exactamente la asimetria que el trabajo del sonido existia para quitar, dejada en pie un canal mas alla. Ahora es `SEMA_MAP.haptics`, con perfiles derivados (`floorMs` + `scale`/`gapMs`) o constantes (`pattern`): los tres kinds evaluativos son ritmos constantes porque lo que hace reconocible a un `error` es su cadencia, no su longitud. El canal lo lee por GETTER, no por copia, asi que un `applyMap()` en caliente le llega sin re-registrarlo. `masterGain` SE APLICA. Pasarlo con motor compartido se tiraba en silencio (S-05/S-32) pese a ser una perilla viva del motor y la forma natural de poner el volumen desde la raiz. Ahora llega a `master.setGain`, y de las opciones que de verdad no pueden aplicarse a un motor ajeno se AVISA en vez de desaparecer. Y `DeltaValue` pasa a exportarse: es la forma de toda semilla de override, sin ella un consumidor no puede tipar el tema que esta pasando. Su ausencia era un error VIVO en el estudio de sema, que lo importaba y no podia — por eso check BAJA a 73 desde el baseline de 75. VERIFICADO: theming 8/8 (incluye re-vocear el haptico y que la tabla autorada quede intacta) · sema+eidos+morfo 802/802 · check 73 (baseline 75, −2 por el export) · docs:check 0/618 · suite completa 7 fallos, los mismos ajenos y preexistentes de contracts.test. ⚠️ Nada de esto se ha OIDO ni TOCADO en un dispositivo real: todo esta medido con el resolver y las suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
preferences: hapticOptions.preferences ?? opts.preferences,
// A GETTER, not a snapshot: `applyMap()` retunes the feel of
// every kind and the channel sees it on the next emission.
profiles: hapticOptions.profiles ?? (() => this.map.haptics)
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
});
}
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
}
}
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// ── Theming the perceptual map ───────────────────────────────────────────
//
// The same shape every eidos axis has — `applyX(seed)` installs, `clearX()`
// reverts to the authored foundation — because half a perceptual system that
// retunes live while the other half only reads its seed at boot is not a
// theming system, it is two mechanisms wearing one name. Until 2026-08-06
// `overrides.runtime` was consumed in the constructor and never again: a
// product could silence sound and could override one occurrence, but could
// not re-voice the system after boot, and could not undo what it had set.
/**
* Retune the perceptual map live, from the CANONICAL map — not from whatever
* is currently applied, so calling it twice is idempotent rather than
* cumulative, exactly like `applyTheme(seed)` on the visual axes.
*
* Paths address the map: `families.commit.base.sound.gain`,
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* `intents.fulfill.deltas.sound.gain`, `sounds.tick.gain`. Takes effect on
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* the next `emit` — no re-boot, no re-registration of channels.
*
* @throws SemaConfigError when a path does not exist in the map. A theming
* API that swallows a typo is worse than no API: the symptom is «the sound
* did not change», with nothing to point at (audit S-09).
*/
applyMap(seed: Record<string, DeltaValue> | undefined): void {
assertMapPaths(SEMA_MAP, seed);
this.mapSeed = seed;
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
this.rebuildMap();
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
}
/**
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* Retune ONLY the sound catalogue, by name: `{ tick: { gain: 0.1 } }`.
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* Sugar over {@link applyMap} for the axis a product most often wants — the
* VOICE of the system — without having to spell `sounds.` on every path.
*
* The vocabulary stays closed: `SoundName` is `keyof SEMA_MAP.sounds`, so a
* theme changes what a name sounds like and cannot invent one.
*/
applySounds(seed: Partial<Record<SoundName, Record<string, DeltaValue>>>): void {
const paths: Record<string, DeltaValue> = { ...(this.mapSeed ?? {}) };
for (const [name, slice] of Object.entries(seed)) {
for (const [key, value] of Object.entries(slice ?? {})) {
paths[`sounds.${name}.${key}`] = value;
}
}
this.applyMap(paths);
}
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* Install a whole SOUND PACK — replace entries outright rather than tweak
* their values.
*
* `applySounds` retunes what an entry already is (a gain, a pitch);
* `applySoundPack` swaps the entry for a different KIND of thing, which is
* what a real pack does: where the default has a synthesised recipe, a pack
* may put an `.mp3`. Names the pack omits keep whatever they had, so a
* partial pack is legitimate — it borrows the rest of its voice from the
* default.
*
* The vocabulary does not change: a pack fills names, it cannot invent
* them. Adding a NAME is a separate act (declare it, then register it).
*/
applySoundPack(pack: Partial<Record<SoundName, NamedSound>>): void {
this.packOverride = { ...(this.packOverride ?? {}), ...pack };
this.rebuildMap();
}
/** Revert every retune AND any installed pack, back to the authored map. */
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
clearMap(): void {
this.mapSeed = undefined;
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
this.packOverride = undefined;
feat(sema): el mapa perceptual se TEMATIZA, y con la misma forma que los ejes visuales El autor: «que yo no pueda definir a nivel de tema la personalizacion es un fallo». Lo era, y medirlo destapo algo peor que el hueco que se veia. NO ERA UN CAMPO QUE FALTABA EN EL SEED: eran dos formas distintas de personalizar. Eidos retunea en vivo, atomico y revertible (applyX/clearX); sema leia `overrides.runtime` UNA VEZ en el constructor y nunca mas, sin forma de revertir. Un `applyTheme` unico construido encima habria sido peor que dos puertas honestas — una llamada donde el color cambia ahora y el sonido no cambia nunca, porque ya arranco. Por eso la puerta unica va la ultima y aqui va el sustrato. 1. EL CATALOGO ENTRA EN EL MAPA (`families · intents · sounds`). Es dato, y el mapa es la estructura que un tema direcciona. Mientras vivio fuera como const de modulo —error mio de ayer— el VOCABULARIO era el unico eje perceptual que un producto no podia re-vocear a ninguna hora. El resolver pasa a resolver el nombre contra el mapa que recibe, no contra un const, que es lo que hace que un tema surta efecto. 2. `applyMap(seed)` · `applySounds({ soft: { gain } })` · `clearMap()` — en vivo y revertibles, reconstruyendo desde el mapa autorado, asi que aplicar dos veces es aplicar una. Misma forma que los seis applyX de eidos. El engine ya pasaba `this.map` por emision, asi que el retuneo surte efecto en la siguiente ocurrencia sin re-arranque. 3. UNA RUTA MALA LANZA (S-09). Antes CREABA la rama: `families.commmit.…` dejaba el valor real intacto y hacia crecer un fantasma al lado, en silencio; y descender a traves de una hoja primitiva la sustituia por un objeto. En una API de theming eso es intolerable — el sintoma es «el sonido no cambio» y no hay nada a lo que apuntar. Se valida tambien en construccion, no solo en el retuneo. LO QUE SIGUE CERRADO A PROPOSITO: el vocabulario. `SoundName` es `keyof SOUND_CATALOGUE`, asi que un tema cambia a que suena un nombre y no puede inventar uno que ningun componente pueda referenciar. La voz se abre; las palabras no. ⚠️ REGRESION PROPIA, cazada y corregida antes de commitear: tipar el mapa `as const satisfies SemaMap` daba las claves literales que `SoundName` necesita, pero estrechaba TODO el mapa y rompia a los consumidores que lo recorren en generico (el estudio de sema: +5 errores en check). El catalogo pasa a ser su propio const y `SEMA_MAP` conserva su anotacion ancha. ⚠️ Y un hallazgo del harness: los tres primeros tests median 0.85x de lo esperado. No era el codigo — era BK-FREQ-MEMORY atenuando la tercera emision del mismo evento. La memoria de frecuencia funciona. VERIFICADO: `theming.test.ts` 7/7 (re-vocea un NOMBRE en vivo y revierte, re-vocea una FAMILIA, idempotente, no muta el mapa canonico, rechaza las dos rutas malas de S-09) · sema+morfo+sound 444/444 · check 75 = baseline · docs:check 0/618. QUEDA, y es decision de producto no defecto: la puerta unica `uix.applyTheme(seed)` en la raiz de composicion. Mas dos menores: `masterGain` sigue descartandose en silencio (S-05/S-32) y la tabla haptica `kind → pattern` sigue en el canal en vez de en el mapa, asi que es el unico eje perceptual que un tema no alcanza. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
this.map = SEMA_MAP;
}
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/** One place that composes the authored map + path seed + installed pack. */
private rebuildMap(): void {
const base = applyMapOverrides(SEMA_MAP, this.mapSeed);
this.map = this.packOverride
? { ...base, sounds: { ...base.sounds, ...this.packOverride } }
: base;
feat(sema,sound): un pack se entrega como quieras — y un `data:` no se pide, se descodifica La entrega deja de ser parte del contrato. Un pack de ficheros puede llegar como quince URLs o como UN JSON de base64 (`packFromBase64`): mismo tipo, mismo intercambio en caliente, misma reserva sintética por entrada, y un nombre que no existe sigue sin compilar. Medido sobre `static/sounds/ui-inline.json`: una petición en vez de quince, y 69.082 B con brotli frente a 79.332 B de los ficheros sueltos — el +33 % del base64 lo deshace la compresión, y el JSON gana además porque nadie comprime `audio/mpeg` y todos comprimen `application/json`. ## `step` no se oía Era a la vez el MÁS CORTO (18 ms) y el MÁS FLOJO (gain 0.025), y esas dos cosas se multiplican: el oído integra energía durante ~100-200 ms, así que un tono corto necesita MÁS amplitud, no menos. Medido, rendía a −29,8 dBFS con la novena parte de la energía de `touch`. Ahora 0.06 a los mismos 18 ms. Lo que no había era el guard: la distinguibilidad es una RELACIÓN, y dos sonidos pueden ser perfectamente distintos entre sí y ser los dos inaudibles. `sound-names.test.ts` gana un suelo absoluto sobre `gain² × duración`. ## El precalentamiento no precalentaba - `preload` pedía el contexto con `getOrCreateContext()`, que hace `await resume()` — y antes del primer gesto esa promesa se queda PENDIENTE en Chrome, no se rechaza. No descargaba nada hasta que el usuario ya había pulsado, que es justo la latencia que el preload existe para quitar. `decodeAudioData` funciona en un contexto suspendido. - Aplicar un pack en caliente no calentaba la caché: cada nombre pagaba su viaje la primera vez que sonaba. Ahora `rebuildMap()` llama a `warmSamples()`. - Con `preferences.sound === 'off'` no se descarga NADA — un pack que no se puede oír son bytes gastados en silencio. Pero la petición se RECUERDA en vez de tirarse: el nivel se lee en el dispatch, así que la primera reproducción audible vacía la cola y encender el sonido no devuelve una caché fría. ## Un `data:` se descodifica en el sitio `connect-src 'self'` BLOQUEA `fetch('data:…')` — la directiva casa por ESQUEMA y `'self'` no cubre `data:` — mientras deja pasar una ruta del mismo origen. Un pack inline enrutado por `fetch` caía a síntesis EN SILENCIO justo en el entorno que llevó a alguien a hacerlo inline. Y es ~6x más lento. `bytesFor()` lo resuelve con `atob`. ## De la revisión adversarial - Las claves del JSON se validan: una que no sea `SoundName` LANZA, igual que una ruta mala en `assertMapPaths` y por la misma razón — tragarse la errata da el síntoma «parte de mi pack no se aplicó» sin nada a lo que apuntar. - `isSoundName` preguntaba `name in SEMA_MAP.sounds`, y `in` recorre el prototipo: `isSoundName('toString')` devolvía `true`. Inofensivo mientras todos los llamantes tecleaban el nombre; nada inofensivo al validar red. - `warmSamples` sólo tenía una de las dos ramas que sí tiene el constructor, así que una app con canal de sonido propio se calentaba al arrancar y nunca más; y esa rama leía la preferencia UNA vez, en construcción. Las dos se caían por lo mismo: ahora comparten `warmUrls()` en vez de duplicarse. - El demo del catálogo cancela su fetch: elegir `inline` y saltar a `synth` antes de que llegara reinstalaba el pack sobre un `clearMap()` ya hecho. - `fallbackFor(name)` borra los dos casts que copiaba todo el que autorase un pack de ficheros. ## Documentación `architecture/sema.md` gana la sección de las dos formas de entrega y el párrafo de `preloadSamples` reescrito — describía un precalentamiento que ya no es el que ocurre. El README del arte conoce la ruta `data:`. La página de packs gana su sección equivalente y deja de señalar unos `.wav` sueltos como «el ejemplo». Y un fantasma: `soft` no existe — es un nombre del catálogo viejo que quedaba como ejemplo en ocho sitios, incluido el docstring de `applySounds`, cuyo ejemplo copiado literalmente LANZABA. `static/sounds/ui-inline.json` se genera desde `packs/ui-mp3.ts`; el comando está documentado ahí y reproduce el fichero byte a byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
this.warmSamples();
}
/**
* Pre-decode every sample the current map can reach.
*
* The engine's cache is keyed by URL, so a file is fetched and decoded ONCE
* and every later play is free — but only from the SECOND play on. Swapping
* a pack at runtime turns synthetic entries into files, and without this
* each name paid its own round trip the first time it sounded, which is the
* one time latency is audible. Warming costs one parallel burst instead.
*
* A no-op at boot (no channel is registered yet); the constructor folds the
* same URLs into its own preload for that case.
*/
private warmSamples(): void {
const urls = namedSampleUrls(this.map);
if (urls.length > 0) this.warmUrls(urls);
}
/**
* Hand URLs to whatever can pre-decode them.
*
* The built-in channel is preferred because it OWNS the sound preference: it
* downloads nothing while sound is off and defers the request instead of
* discarding it. A replacement channel has no such method, so the shared
* engine is preloaded directly — and there the preference has no owner, so
* the check is written here. It is read at CALL time, like everywhere else
* the level is read, so flipping sound on and swapping a pack works.
*/
private warmUrls(urls: string[]): void {
const channel = this.channels.get('sound');
if (channel instanceof SoundChannel) {
void channel.preloadSamples(urls);
return;
}
if (this.sharedSoundEngine && (this.soundPreferences?.sound ?? 'full') !== 'off') {
void this.sharedSoundEngine.preload(urls);
}
feat(sema): pack de ejemplo con ficheros mp3, recortados y normalizados El autor dejo una biblioteca de 35 mp3 en `static/sounds/ui` y pidio recortarlos y normalizarlos para usarlos como pack alternativo. Medidos ANTES de tocar nada, los originales tenian tres problemas que habrian hecho parecer roto al sistema: · LATENCIA DE ARRANQUE. `button_hard` no empezaba a sonar hasta los 347 ms. Un sonido de pulsacion que llega un tercio de segundo despues no confirma nada: se lee como eco. `button_squishy` 148 ms, `blocked` 78 ms, y varios mas con 25 ms de silencio de cabeza. · DURACIONES FUERA DE ESCALA. `item_select` 862 ms para seleccionar en una lista donde puedes recorrer cinco elementos por segundo — se solapan tres a la vez. `success_chime` 1591 ms. · NIVELES SIN NORMALIZAR, factor 14x entre picos (0.066 … 0.961): unos inaudibles al lado de otros. PROCESADO con ffmpeg: recorte de silencio en ambos extremos + loudnorm a −20 LUFS / pico real −1.5 dB, mono 44.1 kHz. Resultado medido en el navegador: TODOS arrancan en 0 ms, y las duraciones se desploman — `button_soft` de 862 ms a 28 ms, que resulta ser la longitud ideal para un toque; `copy` a 78 ms, casi exacto al `tick` sintetico. 292 KB en total. ⚠️ Los originales NO estaban en git y el procesado los reemplaza. Copia de seguridad de esta sesion en /tmp/ui-originales (efimera): si los quieres conservar, guardalos tu. EL PACK (`src/uix/sema/packs/ui-mp3.ts`) mapea 16 nombres a 16 ficheros. Es una TRADUCCION, no una busqueda: la biblioteca esta nombrada por componente (`button_`, `tab_`, `panel_`, `window_`) que es justo el eje que el vocabulario rechaza, asi que cuatro ficheros compiten por el nombre `open` y sobran quince. Cada entrada conserva como reserva el sonido sintetico del framework, de modo que un 404 degrada a la voz por defecto en vez de enmudecer. Lo imperfecto queda ESCRITO en el fichero, no escondido: `tick.fulfill` dura 425 ms y `tick.loss` 723 ms, muy por encima de la ventana perceptual que persigue el pack sintetico. Es el precio de usar material encontrado — suena mas rico y se comporta peor. Un pack grabado PARA este sistema se grabaria a sus duraciones. `engine.applySoundPack(pack)` instala entradas enteras (frente a `applySounds`, que retunea valores de las que ya hay); `clearMap()` revierte tambien el pack. Y el catalogo de la documentacion estrena conmutador: mismo nombre, otra voz, para comparar A OIDO. VERIFICADO en el navegador espiando el grafo de audio: con el pack por defecto `tick` crea dos osciladores a 850/1275 Hz; con `ui-mp3` crea un buffer de 78 ms y CERO osciladores. El cambio funciona de punta a punta. sema+morfo+soma 1075/1075 · uix sin errores de tipos nuevos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 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.
*
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* Lifecycle:
* 1. Generate id and run channel prepare hooks.
* VisualChannel.prepare() projects `data-event-*` on target.
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* 2. resolveSignature → cascade selectors match against the now-
* projected target via native `target.matches()`.
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
* 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
*
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 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 };
docs(reconciliation): soma deps truth + checklist enforcement column + sema cascade renumber + archetype pointers Phase 1d of PLAN-docs-reconciliation. - soma/README s3 + SOMA_ARCHITECTURE s2/s6/s12 + popover/link-preview READMEs: positioning is the in-house engine (layers/floating + $ethereal); @floating-ui is devDep-only. Layers table gains Stacking / AxialDrag / ZoomPan / ImageProvider / ListSelection (popper/ is an empty leftover dir — not documented). clsx documented as it really is: NOT declared but still imported by props/props.ts via a svelte transitive (phantom dep — flagged for a decision, chip spawned). - COMPONENT_COMPLETION_CHECKLIST: new Enforcement column on every rule table (audit | tool:X | manual) so declared-but-unchecked rules are explicit; morfo rule IDs renamed M-* -> A-* to grep-match what component-audit.ts emits; stale rows fixed against the corrected script (D-1.2 v2 9-tab, D-3.1 single eidosSnippet, E-2.2 wrapper import); implemented-but-undeclared rules added (R-2.7 literal typography, A-3.4b verb/family pairing); A-3.3 family count decopied; sI now requires an honest Enforcement value for new rules. - Sema cascade numbering unified to 1 family - 2 intent - 3 morfo - 4 runtime - 5a packs - 5b app across sema/README, engine.ts, resolver.ts, resolver.test.ts and soma/runtime.svelte.ts (three divergent schemes coexisted; CLAUDE.md already uses the chosen one). - '24 archetypes' copied lists replaced by pointers to ARCHETYPE_VOCABULARY in active_architecture s6 + morfo/README. - TriggerOptions.semantic JSDoc: phantom defaultSemantic removed (implemented shape is additive allowedFamilies, D.11); 'warns via logger' claim corrected — the override is silently ignored on non-polymorphic events (no warn exists in code). npm run check: 61 errors = pre-existing baseline, 0 new. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
// Explicit silence (capa 3 morfo override): `signal.channels: []`
// skips EVERYTHING — no projection, no dispatch, no hold. The morfo
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// 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;
}
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente `data-event-*` es UNA RANURA por elemento. Tres auditorias independientes encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie podia distinguir una redireccion legitima de una deriva, porque la opcion que redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia. EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64 ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo por diseño. LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba `data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a ser el suelo correcto. A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms, con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el trigger, press-squeeze jamas. `regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`, este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0 huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no se anuncia»), con sus 4 declaraciones muertas. El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay animaciones, asi que awaitExpression volvia al instante. El navegador midio el commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion. Medir la envolvente no es medir la salida. `renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform, aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement. Ademas: 5 selectores muertos en el predicado del focus-scope de palabras (barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome (inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union inline al `Direction` canonico, que destapo que el censo de direccion grepea el NOMBRE del tipo · deuda de chronos (scope, barrel, README). Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2 (el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite). Todos los guards nuevos vistos fallar antes de arreglar nada. Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno, reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177. Handoff: docs/process/CONTINUE-perceptual-surface.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// `queue` — the surface is one slot, and this occurrence declared that it
// waits rather than displacing. Only the IRREDUCIBLE pairs use it: two
// events the doctrine requires on one node (a toggle's contact + commit;
// the knob's drop + set). Where the collision came from a redirection the
// answer was to stop redirecting (A-36), not to arbitrate.
//
// The WHOLE emit waits, sound included — not just the projection. The
// cascade resolves by matching selectors against the `data-event-*` this
// signal is about to write, so projecting late while resolving early
// would read the OTHER occurrence's stamp and pick its sound.
//
// Audio unlock is safe: you only queue behind something that just fired
// on the same gesture, and that one already primed the context.
if (signal.regime === 'queue' && signal.target) {
const live = this.surfaces.get(signal.target);
if (live) await live;
}
// Register this occurrence as the surface's holder, so a later `queue`
// waits for it. Resolved in the `finally` below — including the failure
// path, so a throw can never leave a surface permanently busy.
let releaseSurface: (() => void) | undefined;
let heldPromise: Promise<void> | undefined;
if (signal.target) {
heldPromise = new Promise<void>((resolve) => {
releaseSurface = resolve;
});
this.surfaces.set(signal.target, heldPromise);
}
const preparations: ChannelPreparation[] = [];
for (const channel of this.channels.values()) {
const handle = channel.prepare?.(enriched);
if (handle) preparations.push(handle);
}
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 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
const persistence = signal.persistence ?? 'transient';
const shouldAutoCleanup = persistence === 'transient';
let cleanedUp = false;
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
try {
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
let effective = resolveSignature(enriched, {
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
map: this.map,
cascade: this.cascade
});
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
// Post-resolution perceptual modulation. Both touch ONLY the
// non-visual channels (sound / haptic); the visual hold is never
// altered, so eidos's CSS reactions are unaffected.
// C-2 frequency memory — repeated same-key signals attenuate.
// C-3 dominance — a signal dominated by a still-active one is muted.
effective = this.applyFrequencyMemory(enriched, effective);
effective = this.applyDominance(enriched, effective);
const visualChannel = this.channels.get('visual');
const otherChannels: Channel[] = [];
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
for (const channel of this.channels.values()) {
if (channel.id !== 'visual') otherChannels.push(channel);
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente `data-event-*` es UNA RANURA por elemento. Tres auditorias independientes encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie podia distinguir una redireccion legitima de una deriva, porque la opcion que redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia. EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64 ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo por diseño. LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba `data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a ser el suelo correcto. A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms, con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el trigger, press-squeeze jamas. `regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`, este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0 huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no se anuncia»), con sus 4 declaraciones muertas. El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay animaciones, asi que awaitExpression volvia al instante. El navegador midio el commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion. Medir la envolvente no es medir la salida. `renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform, aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement. Ademas: 5 selectores muertos en el predicado del focus-scope de palabras (barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome (inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union inline al `Direction` canonico, que destapo que el censo de direccion grepea el NOMBRE del tipo · deuda de chronos (scope, barrel, README). Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2 (el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite). Todos los guards nuevos vistos fallar antes de arreglar nada. Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno, reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177. Handoff: docs/process/CONTINUE-perceptual-surface.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Free the surface when the HOLD elapses, not when the emit ends.
// The emit also waits for `awaitExpression` — every running animation
// on the target, capped at 1500ms — and gating the queue on that was
// measured on a real Toggle to delay the queued commit by **1.6s**,
// because unrelated transitions kept the node busy. What the holder is
// owed is its registration floor; what happens after must not block
// the next occurrence. The displaced projection is safe either way:
// the unstamp checks ownership (`data-event-id`), so the holder's late
// cleanup is a no-op once the queued one has taken the slot.
if (releaseSurface && visualChannel instanceof VisualChannel) {
const holdMs = visualChannel.holdMsFor(enriched, effective);
semaDelay(this.timers, holdMs, releaseSurface, {
channel: 'visual',
signal: enriched.id
});
}
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
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 }
});
});
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
// 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.
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if (visualChannel) {
await visualChannel.handle(enriched, effective);
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
} 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;
}
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente `data-event-*` es UNA RANURA por elemento. Tres auditorias independientes encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie podia distinguir una redireccion legitima de una deriva, porque la opcion que redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia. EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64 ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo por diseño. LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba `data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a ser el suelo correcto. A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms, con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el trigger, press-squeeze jamas. `regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`, este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0 huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no se anuncia»), con sus 4 declaraciones muertas. El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay animaciones, asi que awaitExpression volvia al instante. El navegador midio el commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion. Medir la envolvente no es medir la salida. `renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform, aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement. Ademas: 5 selectores muertos en el predicado del focus-scope de palabras (barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome (inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union inline al `Direction` canonico, que destapo que el censo de direccion grepea el NOMBRE del tipo · deuda de chronos (scope, barrel, README). Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2 (el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite). Todos los guards nuevos vistos fallar antes de arreglar nada. Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno, reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177. Handoff: docs/process/CONTINUE-perceptual-surface.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Free the surface for anyone queued behind it. Two guards: only the
// CURRENT holder clears the entry (a later `replace` may already have
// taken it), and this runs on the throw path too — a rejected emit
// must not leave a surface busy forever.
if (signal.target && this.surfaces.get(signal.target) === heldPromise) {
this.surfaces.delete(signal.target);
}
releaseSurface?.();
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
}
// 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);
}
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
/**
* C-2 — frequency memory (BK-FREQ-MEMORY, book ch. 32 §11). Attenuates the
* non-visual channels of a signal repeated within a short window so a burst
* of the same event fades instead of hammering. `threat` is exempt. A pause
* of `FREQ_WINDOW_MS` resets the key to full intensity.
*/
private applyFrequencyMemory(
signal: SemanticSignal,
effective: EffectiveSignature
): EffectiveSignature {
if (!this.frequencyMemory) return effective;
if (effective.intent === 'threat') return effective;
refactor(sema)!: el sonido se ELIGE, no se modula — evento.intent = sonido Directiva del autor, sin matices: «todo el sistema de sonido es una puta mierda... la unica solucion es evento sonido, evento.intent = sonido, y punto y nada de mierdas de que si el intent modifica nada». Tenia razon, y lo que lo destapo fue oirlo: los dieciseis nombres del catalogo anterior sonaban practicamente igual. Medido despues: cinco de ellos eran LA MISMA nota de 700 Hz a cinco volumenes, y las variantes de direccion se separaban 20 Hz. El modelo de modulacion producia teoria bonita y mush audible. LA REGLA ENTERA, y no hay mas: nombre = per-emit ?? cascada ?? pack ?? morfo ?? familia[verbo] ?? familia.default sonido = pack[`${nombre}.${intent}`] ?? pack[nombre] ?? nada Dos busquedas. El intent SELECCIONA un sonido entero; si no existe la variante se desprecia y suena la base; si no hay base, silencio. Un `threat` no es un `tick` con mas aspereza: es otro sonido —mas grave, mas rasposo, descendente— y no se pueden confundir. EL TIER DEL VERBO es lo que vacia los packs. La tabla de familia se indexa por verbo (`emerge.close → 'close'`), asi que ni un solo overlay escribe su cierre. De ~165 reglas de sonido quedan 30, en 16 de los 71 packs — y las 135 borradas no decian mas que el defecto. Para que el verbo llegue, ahora VIAJA en la señal: se calculaba y se tiraba con un `void` (hallazgo S-40), el tipo prometia que se emitia y era falso. CATALOGO NUEVO: 10 bases + 6 variantes, cada una un sonido diseñado entero. Medido en el grafo de audio real: de 240 Hz (`tick.loss`) a 1900 Hz (`step`), casi tres octavas, y las variantes evaluativas traen su propio modulador de aspereza — texturas distintas, no un tono movido. 100% sintesis a proposito: el pack por defecto no lleva binarios, funciona sin red y no puede dar 404. Los .wav quedan como pack de EJEMPLO. EL GUARD QUE FALTABA: `distinguishability` exige que dos entradas se separen en al menos DOS ejes perceptuales. Nacio en rojo contra mi propio catalogo —ocho pares casi gemelos— y dirigio el diseño hasta separarlos. Una instantanea no lo habria cazado: cada valor era exactamente el que la tabla decia; lo que faltaba era una RELACION entre entradas. GESTOS POR REPETICION (idea del autor). Un arrastre ya emite a su ritmo, asi que toca `step` (18 ms) por emision: la velocidad del gesto ES la del trinquete, como una rueda fisica. Mueren los tres resolvers que sintetizaban tono desde posicion y velocidad —la ultima aritmetica del sistema— y `handle` queda EXENTA de la memoria de frecuencia, que si no estrangularia el trinquete al primer arrastre. Perdida firmada: el ritmo lleva la velocidad, pero la posicion ya no mapea a altura. NADIE ESCRIBE PARAMETROS, ni la app. `SemaSignatureOverride.sound` y `SemaCascadeRule.sound` aceptan un nombre o SILENT. La rendija de autoria inline «para la personalizacion» sobraba: un producto REGISTRA su sonido (nombre + definicion, una vez) y lo nombra. ⚠️ BUG PROPIO durante la cirugia: al reemplazar el bloque del sonido borre tambien la aplicacion de deltas de intent, y el HAPTICO dejo de recibirlos. Lo cazaron sus tests. Restaurado — el haptico sigue modulando porque su vocabulario es categorico y sus deltas cambian el KIND, que tambien es seleccion. MUERE: los deltas de sonido del intent, las bases de sonido de familia, la capa 1.5, `op:'add'` para sonido, `mergeSoundOverride`, y `d7-intent-survives.test.ts` — vigilaba una inversion que ya no puede existir. VERIFICADO: sema+morfo+soma+sound 1108/1108 · docs:check 0/618 · la pagina `/uix/docs/sound` reescrita y medida en navegador (commit+threat → tick.threat; commit+affirm → tick, el intent se desprecia; emerge+close → close sin una linea de pack; sustain → silencio). Los 7 fallos de la suite completa son los ajenos y preexistentes de contracts.test. ⚠️ NO SE HA OIDO. Todo esta medido en el grafo de audio, no con un oido. Si `open` suena a lo que debe sonar un panel abriendose es tu decision, y se afina en un fichero. ⚠️ El estudio `/temas/sema` compila pero edita firmas crudas: su modelo es el viejo. Queda por reconvertir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// `handle` is EXEMPT: a continuous gesture sounds by repetition, so its
// emissions are supposed to be many and even. The anti-fatigue rule
// would read the ratchet as hammering and choke it to the floor within
// the first drag — the dynamics of a gesture are its RHYTHM, not a
// level to be walked down.
if (effective.family === 'handle') return effective;
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
const key = signal.name || `${effective.family ?? ''}:${effective.intent ?? ''}`;
const entry = this.freqCounts.get(key) ?? { count: 0 };
entry.count += 1;
entry.reset?.cancel();
entry.reset = semaDelay(this.timers, FREQ_WINDOW_MS, () => this.freqCounts.delete(key), {
channel: 'frequency'
});
this.freqCounts.set(key, entry);
const over = entry.count - FREQ_THRESHOLD;
if (over <= 0) return effective;
const atten = Math.max(FREQ_FLOOR, 1 - over * FREQ_STEP);
return attenuateNonVisual(effective, atten);
}
/**
* C-3 — dominance arbiter (BK-DOMINANCE, book ch. 30 §7). Registers each
* occurrence as active for a short overlap window and mutes the non-visual
* channels of an INCOMING signal that a still-active occurrence out-ranks
* (evaluable > structural; higher activation; recency breaks ties in favour
* of the newcomer). `threat` is never muted. The visual hold survives so the
* dominated event still reads structurally.
*/
private applyDominance(
signal: SemanticSignal,
effective: EffectiveSignature
): EffectiveSignature {
if (!this.dominance) return effective;
fix(cleanroom): F2+F3+F4-C+SEM-4s1 — lote mecánico, censos con guard, corpus documental y el close polimórfico de los pickers VIVO F2 — lote mecánico (13 ítems): - DEP-2 ogl eliminado (0 imports) · DEP-1 clsx inlineado como toClassString propio + suite de contrato (props.test.ts; soma.md §12 cerrado). - THM-7: los 5 selectores manuales de sema.md reescritos con semaSelector (los ejemplos [data-toast-root] apuntaban a un part INEXISTENTE — la deriva que el builder previene, demostrada en el propio doc). - MOR-1 escape isomorfo + validación de attr-names en semaSelector + 9 tests (selectors.test.ts, matches() real con comillas/corchetes) · MOR-2 partMarkerAttr = única fuente compilador↔builder + test de paridad · MOR-3 _resetCompileCache borrado (0 usos). - SOM-2 keydown continue en match sin handler + keyboardFixtureMorfo · SOM-1 no-await de handlers (censo async = 0; contrato V1 cumplido) + pin. - SEM-2 trigger pre-attacha catch con logger (void trigger sin unhandled rejection; throw intacto para awaiters) + pin · SEM-3 fallback muerto de applyDominance → skip defensivo + timer tope de awaitExpression cancelado · SEC-1 adjudicado YA implementado (assertCssVariableValue desde 2026-05-11) + pin del path de VALOR. - accordion → outline (§32; su outline:none dejaba CERO anillo en HCM) — verificado en vivo · THM-6 radius-full 9999px · EID-4 recuentos 33. F3 — censos con guard: - SOM-3 cerrado: announcer + image-provider migrados a scheduler-preferred (consumidores cableados: date/time-field vía soma.uix.timers; avatar/image vía eidos.timers — verificado en vivo); guard de timers ENSANCHADO de soma/components a TODO soma y pasado a EVIDENCIA (setTimeout exige .schedule( en el fichero — layers/ y datetime/ escapaban del ámbito viejo). - THM-5: R-4.7 nueva (válvula same-line /* important: <razón> */, escaneo comment-blanked) + las 15 declaraciones anotadas con su razón + canon recipe-contract §3/§4. - SOM-4 adjudicado: el censo/guard YA existían (49 pins); knob/mask-field/ timeline pinneados (overrides documentados en call-site); media-player Batch-4 (35 hits, cero renderProps) = único batch restante, registrado. - THM-4 doctrinado en eidos.md §unused (comportamiento/composición = legítimo; deuda = eje visual sin consumidor; hotspots por lotes). F4-C — corpus documental (decisiones de usuario aplicadas): - DOC-3: los 15 enlaces muertos resueltos (repoint a la edición FINAL trackeada / des-link históricos) · docs:check I6-links WARN→ERROR. - DOC-1: tabla «Build contract» MIGRADA a component-guide con estados modernizados (A3–A5 → LIVE + guards de hoy); banners reapuntados; citas de CANON/sema.md historificadas; lápida-redirect en el §13 del fósil. - DOC-4: hold chain → holds.ts · FAQ event:* SUPERSEDED por signatures · gradient añadido a los DOS capstones (sextet real) · nota de paleta de demo-authoring corregida (universalPaletteDecls + decisión THM-2 = mecanismo universal como sucesor del tracker borrado). - DOC-5/6: recuentos anti-frágiles datados · §4.11 dup → §4.12 · Known gaps historificado · N-6/N-7 recuperadas de git (d68d2c45^) y canonizadas en eidos.md §pickers · authoring E2 → canon/tsc.md · air-old des-linkado · EID-3 (placement) en la fila RTL · AUX-2 disabledDom documentado. SEM-4 sesión 1 — el close polimórfico de los pickers, VIVO (D.11): - Reconciliación: los morfos ya no declaran close (delegated al Popover, de-dialoged 06-27); el agujero real era el cierre programático bypaseando dismissWith → save/cancel/select eran perceptualmente SILENCIOSOS. - Fix: PickerShellHandle.setPopoverDismiss + closeWith(cause) en los 5 providers (14 sitios; select/commit → 'save' = commit.save+fulfill, cancel → 'cancel' = emerge; fallback raw para headless) + UN inyector en el eidos PickerShell root (norma N-8). Picker genérico fuera a propósito (ya suena commit-set/cancel por diseño S9). - Verificado en vivo (date-picker): Done → close·commit·fulfill·active · Cancel → close·emerge · cierre real. Gates: matriz 141/141 (los 6 morfos nuevos de la pista de texto paralela también PASS) · contracts 38/38 · eidos 314 · sema 178 · morfo 94 · docs:check 0/0 con I6 en error · baseline propio 57. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
// `emit` always enriches the signal with an id before this runs. The
// old `?? sig-N++` fallback here was dead — and, had it ever fired, it
// would have minted an id DIFFERENT from the one the rest of the
// pipeline uses, desyncing the occurrence bookkeeping (SEM-3). A
// degenerate un-enriched call now skips dominance instead.
const id = signal.id;
if (id === undefined) return effective;
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
const rank = occurrenceRank(effective);
let dominated = false;
if (effective.intent !== 'threat') {
for (const active of this.activeOccurrences.values()) {
if (active.rank > rank) {
dominated = true;
break;
}
}
}
this.activeOccurrences.get(id)?.expire.cancel();
const expire = semaDelay(
this.timers,
DOMINANCE_WINDOW_MS,
() => this.activeOccurrences.delete(id),
{ channel: 'dominance' }
);
this.activeOccurrences.set(id, { rank, expire });
return dominated ? attenuateNonVisual(effective, 0) : effective;
}
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);
}
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();
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
// Cancel the frequency-memory and dominance decay timers.
for (const entry of this.freqCounts.values()) entry.reset?.cancel();
this.freqCounts.clear();
for (const entry of this.activeOccurrences.values()) entry.expire.cancel();
this.activeOccurrences.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'
);
}
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
feat(audit): component re-audit + fixes P0–P5 (P4 complete, P5 in progress) Re-audit of the whole component catalog at pilot depth (91 fichas + the checkpoint verdicts in docs/audit/components/) and the executed fix packages. - P0–P3: doctrine (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline), guards (morfo-vocabulary-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos-lint classHooks), morfo contracts (role=application removed ×4, aria-selected off the Day, drp translationRef, field data-state prune, pin-input commit-set, media-player renames), 13 new sema packs + 12 morfos family-default → pack. - P4 (N1–N10) COMPLETE: selectionMode (select/combobox/toggle-group/calendar); onValueCommit terminal-callback norm (pin-input/search/password/textarea + date/time/color-field add); typed validation reason + onInvalid (tags-input, css-field); index/onIndexChange (carousel); deselectable; openDelay/ groupSkipDelay; allowCustomValue; defaultValue prune. - P5 (in progress): S1 class-hooks codemod (22 hooks → data-attrs across cropper/button/image-picker/toggle/image-adjustments); S5 outline remnant in field.css; depth pass (data-depth=overlay on tooltip/toast/float-panel); touch-rows (--touch-target token, decoupled ::before hit-slop, area-not-visual, 44 AAA). S6 (Field composition) + S8 (calendar-surface) pending. Verification: errors-outside-(alpha|words|palabras|chronos) == 59 (baseline); morfo-vocabulary-check exit 0; eidos-lint class-hooks/invalid 0 on touched components; per-component vitest suites green. Handoff + remaining plan: docs/process/continue-audit-fixes-2026-07.md Excluded (broken by the N1 rename, left broken per user decision, not staged): words/**, palabras/**, chronos, web/routes/alpha/**. Reconciliation pending: the touch-rows ::before for checkbox/switch reverses changelog §37's earlier pseudo-element rejection (WCAG-overlap) that had routed markers to a labeled-row/Field task — flagged for the user in the handoff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
function isAnnounceChannelDom(value: unknown): value is AnnounceChannelDom {
return (
value !== null &&
typeof value === 'object' &&
typeof (value as AnnounceChannelDom).getDocument === 'function'
);
}
fix: address architectural audit findings + sync ecosystem docs Audit: src/audit-opus-4-6-26.md. All non-words findings remediated. Code: - E1: navigation-menu indicator data-state visible -> open (render bug; the active underline was permanently invisible). Clears the only invalid lint selector. - SO1 + E2: raw `new ResizeObserver` -> ActiveDom.observeResize in carousel-provider and the canvas-text useContainerWidth hook (+ s-text / s-text-virtual-list pass eidos.dom). iframe/popup-safe, lifecycle-tracked. - A1: defineUixServices now registers `motion`, so attach-mode app.motion is real and the active-uix fallback becomes the true edge case (test guard updated). - A2/A4: contracts.ts pins `motion` + `announce` in ActiveUixServiceContract + publicSurface; dispose() comment corrected. - SO2: carousel drops the hardcoded `transform 300ms ease-out` (the recipe already handles it via [data-dragging]); also fixed the recipe's undefined `--duration-base` token -> `--duration-slow` (it was masked by the inline). - S1: HapticChannel reduced-motion via an injected ActiveDom port (mirrors SoundChannelDom) instead of global matchMedia. - T1: 10 sites repointed `$libs/dom` -> `$adom` (sema x5 + its tests x3, active-uix value import, arts/prefs). - M2 / S2 / S3: dead code removed (button `states:['idle','loading']`, SemaRuntimeChannelId, SEMA_VALENCED_FAMILY_LIST). Docs: - Motion-as-service reflected across the ecosystem: CLAUDE.md (aliases + arch + service note), arts/README, active_architecture, soma SOMA_ARCHITECTURE, eidos README, eidos-motion.md. - X1: CLAUDE.md "5 canonical channels" (false) -> the single canonical narrative (8 book channels; Sema runs 2 + visual meta-channel, Eidos materializes 5). - X2/X3/X4: sema/README (8 families + intentRequirement/intentGuidance split), types.ts JSDoc (SEMA_INTENT_POLICY -> SEMA_FAMILY_POLICY), engine.ts cascade 6->5, alias table ($frontend out, $lang->$langs, +$clipboard). - E4: codex_audit.md HISTORICO banner. Deferred: SU2 (test-only layering, not a build violation); Words M1/T5/E3 (WIP). Verify: npm run check -> 1 pre-existing error (grafito), 0 new; sema 152/152, contracts 31/32 (1 pre-existing words), carousel 4/4, motion 22/22. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
function isHapticChannelDom(value: unknown): value is HapticChannelDom {
return (
value !== null &&
typeof value === 'object' &&
typeof (value as HapticChannelDom).prefersReducedMotion === 'object' &&
(value as HapticChannelDom).prefersReducedMotion !== null
);
}

Powered by TurnKey Linux.