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/docs/architecture/sema.md

1235 lines
58 KiB

---
title: Sema — the perceptual engine and channels
type: reference
audience: human + agent
authority: E1 architecture — the semantic domain, the emit contract, the resolution cascade and the channels
status: current
source: migrated from src/uix/sema/README.md (2026-07-02, docs-book F7.2)
---
# Sema
`Sema` defines UIX's canonical semantic domain and orchestrates the emission
of perceptual signals.
## What it is
- canonical families (8): `contact`, `commit`, `signal`, `handle`, `emerge`,
`shift`, `sustain`, `delegate`
- canonical intents: `neutral`, `affirm`, `fulfill`, `risk`, `threat`, `loss`
- a per-family intent policy (`SEMA_FAMILY_POLICY`) with **two axes**:
`intentRequirement` (`'required'` | `'optional'` | `'forbidden'`) — the
compile-time gate that shapes the discriminated union — and
`intentGuidance` (`'expected'` | `'contextual'` | `'discouraged'`) — a
doctrinal hint for lint/tooling
- normalization between the structured shape and the canonical label
- minimal domain validation
- `EngineSemantic` as the channel registry + occurrence dispatch
`Sema` does not decide which event happened. The provider decides.
`EngineSemantic` receives the occurrence and dispatches it to the registered
perceptual channels. Each channel materializes the signal in its modality
(DOM, audio, vibration).
## What it no longer is
`Sema` is no longer a monolithic multimodal runtime.
`EngineSemantic` does not contain:
- a global accessibility policy
- a cross-channel perceptual map
- decisions about which modal effect to apply
Those live in each channel separately:
- `EngineSemantic` keeps a channel registry and dispatches each signal
- the DOM projector materializes the signal as `data-event*` in the DOM
- `SoundChannel`, `HapticChannel` and future modal engines register as
independent channels that receive the signal and decide how to materialize
it on their plane
### Channel registry — open
The channel set is **NOT closed**. The framework ships canonical signatures
for `sound` and `haptic`. The `visual` channel exists as the projection/hold
meta-channel but has no slice of its own in `EffectiveSignature`. Apps can add
channels with a signature via declaration merging:
```ts
// app bootstrap
declare module '$uix/sema' {
interface SemaChannelSignatures {
a11y: A11ySignature; // narrator / live-region
voice: VoiceSignature; // text-to-speech
}
}
```
Only the visual channel is mandatory (with the `visual: false` escape).
Sound and haptic are opt-in. Any new channel registers its `Channel` and
receives the dispatch.
## The `emit` contract
```ts
docs(sema): la doctrina deja de mentir — deriva propia, copias podridas y dos leyes muertas Pasada de saneamiento documental tras los tres commits de sonido, con el informe de AUDIT-sema-2026-08-05 como mapa. Tres clases de defecto. 1) DERIVA QUE YO MISMO DEJE (lo mas urgente) El 05 renombre las claves silenciadoras a `commit.silent` / `emerge.silent` y el 06 las elimine del catalogo — dejando media docena de docs citando claves que ya no existen, y afirmando ademas comportamientos que ya no ocurren: - switch / toggle / toggle-group README: decian «silent-by-default (`commit.silent`)». Hoy es `commit.medium` (gain 0.1) y suenan. Corregidos con el nivel real y su razon (un tercio de una pulsacion de boton). - tooltip README + docblock del pack: decian que el tuning «resta el gain de la familia, asi que neutral emite a 0» y que «threat / fulfill siguen aflorando». Ambas cosas son falsas desde el 06: es `SILENT`, el resolver retira el canal y NADA aflora, porque no queda ganancia que subir. - toggle-group apuntaba a `LIBRO_VARIACIONES_Y_EXTENSIONES.md`, que es un stub movido; ahora apunta a book-deviations. - La nota del renombrado en D.5 decia «hoy es `commit.silent`»: una clave que vivio UN DIA. Marcada como tal, con la lista de las que siguen vivas. 2) COPIAS PODRIDAS — la ley del corpus es enlazar, no copiar, y estas dos entradas la incumplian - D.9 transcribia `SEMA_HOLDS_BY_INTENT` entera y se habia quedado atras: D.12 corrigio DOS valores el 2026-07-06 (`commit.fulfill` noticed→settled, `signal.loss` noticed→brief, ambos contra el texto del libro) y la copia siguio afirmando los viejos un mes. Sustituida por el puntero a holds.ts + el enumerado generado, con la leccion escrita en el sitio. - sema.md transcribia `SEMA_VERBS` y le faltaban DOS verbos vivos: `commit.unselect` y `handle.zoom`. Retirada; queda el puntero a verbs.ts y a vocabularies.md, que si se genera y tiene guard de frescura. 3) LEYES QUE LA PRACTICA YA HABIA DEROGADO, Y NADIE REGISTRO - El contrato `emit` publicaba `Promise<void>`; devuelve `Promise<string>` desde D.9. Corregido, y explicado que ese string es el id de la ocurrencia — el unico asidero para cerrar una senal persistente con `clear`. - channels.md fijaba «3 canales runtime» y D.8 cerraba la puerta a Announce («hoy no»). El `AnnounceChannel` existe desde el 2026-07-04: built-in, opt-in y exportado. channels.md pasa a 4 con la distincion que importa (visual/sound/haptic EXPRESAN; announce SUSTITUYE) y D.8 queda marcada PARCIALMENTE SUPERSEDED. - sema.md prescribia `{ announce: uix.announce }` — y eso NO COMPILA: las dos firmas no casan (bolsa de opciones vs posicional). Ahora ensena el adaptador de una linea que si compila, y declara que ninguna raiz cablea el canal hoy, asi que activarlo cae en el fallback que anade un SEGUNDO par de live regions. El arreglo de codigo queda sin tomar: es decision de diseno. - La cabecera de engine-sound.ts y la fila de arts/README seguian afirmando como MECANISMO que «`prefs.sound` mapea al bus ui», que la auditoria AU-4 ya habia corregido en el README del propio arte: lo garantizado por construccion es el NEGATIVO (ninguna politica de UI escribe el bus content). AMBIGUEDADES QUE NO RESUELVO PORQUE SON TUYAS, pero que dejan de estar escondidas: D.7 prohibe canonizar samples fuera de `signal` y `proof-of-human` los usa en dos eventos `commit`, aplastando la modulacion por intent que esa misma entrada existe para proteger —y el comentario del pack afirma literalmente lo contrario de lo que hace—; y D.8 declara que los packs deben respetar el `activeChannels` de la familia mientras sema.md prescribe lo contrario y el pack de dialog deja dos reglas inertes. Ambas quedan marcadas con su estado real y los dos caminos excluyentes, pendientes de tu firma. VERIFICADO: docs:check 0/616 · sonido + sema 244/244 · cero referencias vivas a las tres claves muertas (las que quedan en book-deviations son historia declarada como tal, y las de cronica no se reescriben) · prettier: los 6 docs con avisos ya estaban sucios en HEAD, no los toco. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
semantic.emit(signal: SemanticSignal): Promise<string>
```
docs(sema): la doctrina deja de mentir — deriva propia, copias podridas y dos leyes muertas Pasada de saneamiento documental tras los tres commits de sonido, con el informe de AUDIT-sema-2026-08-05 como mapa. Tres clases de defecto. 1) DERIVA QUE YO MISMO DEJE (lo mas urgente) El 05 renombre las claves silenciadoras a `commit.silent` / `emerge.silent` y el 06 las elimine del catalogo — dejando media docena de docs citando claves que ya no existen, y afirmando ademas comportamientos que ya no ocurren: - switch / toggle / toggle-group README: decian «silent-by-default (`commit.silent`)». Hoy es `commit.medium` (gain 0.1) y suenan. Corregidos con el nivel real y su razon (un tercio de una pulsacion de boton). - tooltip README + docblock del pack: decian que el tuning «resta el gain de la familia, asi que neutral emite a 0» y que «threat / fulfill siguen aflorando». Ambas cosas son falsas desde el 06: es `SILENT`, el resolver retira el canal y NADA aflora, porque no queda ganancia que subir. - toggle-group apuntaba a `LIBRO_VARIACIONES_Y_EXTENSIONES.md`, que es un stub movido; ahora apunta a book-deviations. - La nota del renombrado en D.5 decia «hoy es `commit.silent`»: una clave que vivio UN DIA. Marcada como tal, con la lista de las que siguen vivas. 2) COPIAS PODRIDAS — la ley del corpus es enlazar, no copiar, y estas dos entradas la incumplian - D.9 transcribia `SEMA_HOLDS_BY_INTENT` entera y se habia quedado atras: D.12 corrigio DOS valores el 2026-07-06 (`commit.fulfill` noticed→settled, `signal.loss` noticed→brief, ambos contra el texto del libro) y la copia siguio afirmando los viejos un mes. Sustituida por el puntero a holds.ts + el enumerado generado, con la leccion escrita en el sitio. - sema.md transcribia `SEMA_VERBS` y le faltaban DOS verbos vivos: `commit.unselect` y `handle.zoom`. Retirada; queda el puntero a verbs.ts y a vocabularies.md, que si se genera y tiene guard de frescura. 3) LEYES QUE LA PRACTICA YA HABIA DEROGADO, Y NADIE REGISTRO - El contrato `emit` publicaba `Promise<void>`; devuelve `Promise<string>` desde D.9. Corregido, y explicado que ese string es el id de la ocurrencia — el unico asidero para cerrar una senal persistente con `clear`. - channels.md fijaba «3 canales runtime» y D.8 cerraba la puerta a Announce («hoy no»). El `AnnounceChannel` existe desde el 2026-07-04: built-in, opt-in y exportado. channels.md pasa a 4 con la distincion que importa (visual/sound/haptic EXPRESAN; announce SUSTITUYE) y D.8 queda marcada PARCIALMENTE SUPERSEDED. - sema.md prescribia `{ announce: uix.announce }` — y eso NO COMPILA: las dos firmas no casan (bolsa de opciones vs posicional). Ahora ensena el adaptador de una linea que si compila, y declara que ninguna raiz cablea el canal hoy, asi que activarlo cae en el fallback que anade un SEGUNDO par de live regions. El arreglo de codigo queda sin tomar: es decision de diseno. - La cabecera de engine-sound.ts y la fila de arts/README seguian afirmando como MECANISMO que «`prefs.sound` mapea al bus ui», que la auditoria AU-4 ya habia corregido en el README del propio arte: lo garantizado por construccion es el NEGATIVO (ninguna politica de UI escribe el bus content). AMBIGUEDADES QUE NO RESUELVO PORQUE SON TUYAS, pero que dejan de estar escondidas: D.7 prohibe canonizar samples fuera de `signal` y `proof-of-human` los usa en dos eventos `commit`, aplastando la modulacion por intent que esa misma entrada existe para proteger —y el comentario del pack afirma literalmente lo contrario de lo que hace—; y D.8 declara que los packs deben respetar el `activeChannels` de la familia mientras sema.md prescribe lo contrario y el pack de dialog deja dos reglas inertes. Ambas quedan marcadas con su estado real y los dos caminos excluyentes, pendientes de tu firma. VERIFICADO: docs:check 0/616 · sonido + sema 244/244 · cero referencias vivas a las tres claves muertas (las que quedan en book-deviations son historia declarada como tal, y las de cronica no se reescriben) · prettier: los 6 docs con avisos ya estaban sucios en HEAD, no los toco. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
The resolved value is the **occurrence id**. Transient signals can ignore it;
a non-transient one (`untilFix` / `untilAction` / `stateBound`, see
[persistence](#persistence)) must be held so the caller can end it with
`clear(id)` or `clearTarget(el)` — there is no other handle on it.
One signature. It covers the three scenarios when composed with `dom.apply`:
```ts
// Structural change without a signal
dom.apply(change);
// Structural change with a signal
await semantic.emit(signal);
dom.apply(change);
// Signal without structural change
void semantic.emit(signal);
```
### Promise semantics — sequential strict
`emit(signal)` resolves when the `VisualChannel` has completed its whole
materialization:
1. `VisualChannel.prepare()` projected `data-event*` onto the DOM
2. the attributes lived in the DOM for the configured `hold`
3. the attributes were already removed (cleanup complete)
4. the Promise resolves
This is **sequential strict**: the caller applies the structural commit
AFTER the perceptual signal has finished. There is no parallelism between the
event and the state change.
Non-visual channels (sound, haptic) are fire-and-forget: they start in
parallel with the visual one but do not affect the Promise's timing.
### `emit`'s internal lifecycle
```
1. The engine generates the occurrence's id/session
2. The engine runs `channel.prepare(...)` on the registered channels
- `VisualChannel.prepare()` projects `data-event*` for the cascade
3. The engine resolves the cascade and dispatches the signal to ALL registered channels
- non-visual channels (sound, haptic) → fire-and-forget (not awaited)
- the visual channel → awaited
4. The engine resolves the Promise when the visual channel has finished
(strict sequential semantics: cleanup BEFORE the resolve)
```
### Channels as modules
Sema is organized in symmetric perceptual channels:
```
src/uix/sema/
├── engine.ts registry + channel prepare/dispatch + cascade composition
├── resolver.ts resolveSignature(signal, opts): EffectiveSignature
├── stamp.ts stampEventAttrs / unstampEventAttrs (data-event-*)
├── channels.ts channel ids, signatures and override types
├── sounds.ts nominal sound repository + dynamic recipes
├── sema-map.ts SEMA_MAP data + per-component Sema packs
├── components/ per-component perceptual packs (CSEM)
│ ├── dialog.ts dialogSema — cascade rules + preloadSamples
│ ├── toast.ts (future)
│ └── …
└── chans/
├── types.ts Channel interface — handle(signal, effective)
├── visual.ts VisualChannel (data-event projection + hold)
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
├── sound.ts SoundChannel (gate + reduction policy; the Web Audio
│ runtime lives in the `$sound` art, injected)
└── haptic.ts HapticChannel (Vibration API + categorical kinds)
```
The engine does not mutate DOM attributes directly: it runs the generic
`channel.prepare(...)` hook. In the visual channel that hook delegates the
projection to a `SignalProjector`. When `ActiveUix` builds it, that projector
writes through UIX's `ActiveDom`. The engine resolves each signal into an
`EffectiveSignature` with `hold`, `sound`, `haptic` and future typed
channels, and dispatches `(signal, effective)` to each channel. Each channel
reads its slice (`effective.sound` for audio, `effective.haptic` for
vibration, etc.) or ignores the signature if it doesn't use it. Only the
visual channel blocks the caller with the perceptual hold; the rest are
fire-and-forget.
### Resolver and sema-map — the resolution cascade
On every `emit`, the engine:
1. Runs the channels' `prepare` hooks. The `VisualChannel` projects the
semantic tokens `data-event`, `data-event-family`, `data-event-intent`,
`data-event-phase`, `data-event-id` onto `signal.target`. These are the
tokens the cascade and eidos's CSS read.
2. Calls `resolveSignature(signal, opts)`, which applies the cascade
(canonical numbering **1 · 2 · 3 · 4 · 5a · 5b** — the same in
`engine.ts`, `resolver.ts` and CLAUDE.md; each layer overrides the
previous):
```
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
THE SOUND — two lookups, and nothing modulates:
nombre = per-emit ?? cascada ?? pack ?? morfo ?? familia[verbo] ?? familia.default
sonido = pack[`${nombre}.${intent}`] ?? pack[nombre] ?? nada
EVERYTHING ELSE — a CSS-like cascade, each layer over the previous:
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. FAMILY base — SEMA_MAP.families[f].base (haptic + activeChannels + hold)
2. INTENT deltas — SEMA_MAP.intents[i].deltas; HAPTIC ONLY since 2026-08-06
3. MORFO overrides — signal.overrides + signal.channels (minus `sound`)
4. RUNTIME overrides — engineOpts.overrides.runtime, baked into the map
5a. PACK cascade — engineOpts.components (minus their `sound`)
5b. APP cascade — engineOpts.overrides.cascade
```
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
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**The sound does not participate in that cascade, and that is the design.**
An intent SELECTS a whole sound; it never bends one. A pack NAMES a sound; it
never authors one. There is nothing to override because there are no
parameters to override — a rule carries a name, and a name replaces a name.
What that removed, and why it is worth knowing before you go looking for the
machinery that used to be here: sound used to be resolved through all six
layers, with intent deltas adding to a family base signature and pack rules
replacing primitives afterwards. Measured across the 71 packs, that produced
214 rules authoring 33 distinct signatures, of which five were the same 700 Hz
note at five volumes and the «direction» variants differed by 20 Hz. The theory
was expressive; the output was mush. It also generated a whole class of defect
(D.7, the S-07 finding, three guards) whose only job was to police arithmetic
that should never have been in a component.
**Precedence among the things that NAME.** Most specific wins: a name passed at
trigger-time beats the app cascade, which beats a pack rule, which beats the
morfo event, which beats the family's verb table. This INVERTED the old order,
in which the cascade beat the per-emit override — whoever fires the occurrence
knows it best.
**The verb tier is what empties the packs.** `SEMA_MAP.families[f].sounds` is
keyed by the event's canonical VERB, with `default` as the fallback cell, so
`emerge` + `close` finds `close` without one line in any overlay's pack. The
verb travels in the signal for exactly this reason (it used to be computed and
discarded — audit S-40). A pack only speaks when it DIFFERS from the default:
the framework's 71 packs carry 30 sound rules between them.
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
3. Dispatches to each channel with the resolved signature.
4. Awaits the VisualChannel (which contributes the hold).
5. Runs the cleanup of the handles returned by `prepare`.
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Overrides vs deltas convention — numbers.** Applies to the HAPTIC channel
and to `hold`; the sound channel has no numbers to converge on any more.
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Layer 2 (intent.deltas)**: `intensity: 0.2` adds to the base. Modifiers.
- **Layers 3, 4, 5a/5b (overrides)**: `intensity: 0.7` sets. Like CSS.
- To add explicitly from an override layer: `{ op: 'add', value: 0.1 }`.
- To multiply: `{ op: 'multiply', factor: 1.2 }`.
- To replace non-numeric primitives: `{ op: 'replace', value: ... }`.
If `signal.family` is missing or not in the map, it returns an empty
`EffectiveSignature` — the channels no-op.
### Semantic tokens — `data-event-*`
`VisualChannel.prepare()` projects the following attrs onto `signal.target`
BEFORE the cascade resolves. Rules with selectors over these attrs match
natively via `target.matches()` / `target.closest()`:
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
| Attr | Value | Origin |
| ------------------- | -------------------- | ------------------------------ |
| `data-event` | `'close-after-fail'` | `signal.name` |
| `data-event-family` | `'signal'` | `signal.family` |
| `data-event-intent` | `'threat'` | `signal.intent` (when present) |
| `data-event-phase` | `'active'` | while the hold lasts |
| `data-event-id` | `'sig-42'` | occurrence id |
Those tokens are the **cross-channel contact surface**: sema's cascade
(`sound`, `haptic` and future channels) reads them with CSS selectors, the
same way eidos's CSS reads them to tint borders / animate states during the
hold. One perceptual surface, separate owners.
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 surface is ONE SLOT — ownership and `regime`
Those five attrs are a **single slot per element**. Two occurrences on one node
cannot both express, and the framework spent a year not saying so: three
independent audits found the same defect and none closed it (fable **S1**
2026-07-01 · sema **S-17** 2026-08-05 · blocks **A-36 / A-65**, reproduced in
the browser). Two rules now hold it shut.
**1 · The unstamp checks ownership.** `unstampEventAttrs` retires the
projection only when `data-event-id` still matches the signal that wrote it.
Before 2026-08-10 it took the signal and discarded it, so the occurrence
finishing FIRST wiped whichever one currently held the slot — measured on the
knob, a `commit-set` projection died 24 ms into a 240 ms hold.
**2 · `regime` says what an ARRIVING occurrence does to a busy surface**
(`SemaRegime`, declared per morfo event, default `replace`):
| Value | Behaviour |
| --- | --- |
| `replace` | Takes the surface at once, displacing the live projection. **Default** — and correct only since rule 1. |
| `queue` | Waits for the live occurrence's HOLD to elapse, then takes it. |
`queue` waits for the **hold**, never for the whole expression.
`awaitExpression` keeps the projection alive until the target's animations
finish (capped at `MAX_EXPRESSION_WAIT_MS`), which is right for unstamping and
wrong for queueing: gating on it delayed a Toggle's queued `commit-toggle` by
**1.6 s**, because unrelated transitions kept the node busy. The engine and the
channel resolve that number through one path (`VisualChannel.holdMsFor`).
**Reach for `regime` last.** It is for pairs the doctrine REQUIRES on one node
and no re-targeting can separate — a toggle's `contact-press` + `commit-toggle`
(its provider IS the button), the knob's `handle-drop` + `commit-set`. When the
collision comes from a **redirection** instead, the fix is to stop redirecting:
that is how A-36 closed, with the overlays' `open` giving up a `targetOverride`
left over from when they were `sequence: 'pre'`. Measured after: the Button's
`contact-activate` stamps the trigger and the Drawer's `open` stamps its
content — two surfaces, both expressing.
`collapse` and `lock` were declared here from the founding commit and never
meant anything; both were retired on 2026-08-10 rather than left as a contract
that lies. The reasons are in `SemaRegime`'s own docblock.
### Cascade rules — flat CSS-like shape
```ts
interface SemaCascadeRule {
selector: string // CSS selector — matches state attrs + event tokens
priority?: number // CSS-specificity override (optional)
channels?: readonly SemaChannelId[] // restricts / silences active channels
sound?: …
haptic?: …
}
```
One rule = one selector + one block of deltas. No intermediate
`overrides: { eventLabel: ... }` layer — the event's identity is read from
the selector via the `[data-event*]` tokens.
### Cascade selectors — typed builder, no hand-written strings
Cascade rules in `sema/components/*.ts` MUST build their `selector` via
`semaSelector(morfo, partKebab, matchers?)` from `$uix/morfo`. The helper
closes the loop between morfo's part/event contract and the selectors the
cascade evaluates.
```ts
import { semaSelector } from '$uix/morfo';
import { dialogMorfo } from '$uix/morfo/components/dialog';
const onContent = (matchers?: Parameters<typeof semaSelector<typeof dialogMorfo>>[2]) =>
semaSelector(dialogMorfo, 'content', matchers);
cascade: [
// [data-dialog-content][data-event-family="commit"]
{ selector: onContent({ eventFamily: 'commit' }), haptic: { kind: 'tap' } },
// [data-dialog-content][data-event="close-after-fail"]
{ selector: onContent({ eventName: 'close-after-fail' }), sound: { sampleUrl: '/fail.wav' } },
// [data-dialog-content][data-event^="close-"][data-event-family="emerge"]
{
selector: onContent({ eventNamePrefix: 'close-', eventFamily: 'emerge' }),
sound: { contour: 'descending', pitch: { op: 'add', value: -150 } }
}
];
```
Compile-time guarantees:
- `partKebab` is checked against `morfo.parts[].kebab`.
- `eventName` is checked against `morfo.events[].name`.
- `eventFamily` / `eventIntent` are typed against the canonical sema unions.
- `state` / `aria` / `pseudo` accept plain strings (the data-attr vocabulary
is per-component and not yet typed-derived).
Renaming a part or event in morfo breaks the cascade at type-check time, not
silently in production. **Hand-written selector strings in cascade rules are
a code smell** — review them as drift.
### Per-component packs — `sema/components/{name}.ts`
Each component ships its perceptual-defaults pack in
`src/uix/sema/components/{name}.ts`, symmetric to soma and eidos:
```ts
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
import { semaSelector } from '$uix/morfo';
import { dialogMorfo } from '$uix/morfo/components/dialog';
import type { Sema } from '../sema-map';
import { sound, soundSampleUrls } from '../sounds';
export const dialogSema: Sema = {
name: 'dialog',
preloadSamples: soundSampleUrls(['notification.ping']),
cascade: [
{
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
// `[data-dialog-content][data-event-intent="threat"]`, built from
// the morfo contract — a part rename breaks at compile time.
selector: semaSelector(dialogMorfo, 'content', { eventIntent: 'threat' }),
sound: sound('notification.ping'),
haptic: { kind: 'error', pattern: [50, 80, 50, 80, 50] }
}
]
};
```
The app imports the packs it uses (tree-shakable):
```ts
import { dialogSema } from '$uix/sema/components/dialog';
defineEngineSemantic({
components: [dialogSema],
overrides: {
cascade: [
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
// App-level rules win over packs on specificity ties. Apps use the
// SAME typed builder; instance scoping goes in `ancestor`.
{
selector: semaSelector(dialogMorfo, 'content', { ancestor: '#critical' }),
sound: { sampleUrl: '/x.wav' }
}
]
}
});
```
`preloadSamples` is concatenated across all packs and passed to
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
`SoundChannel.preloadSamples()` when the engine is created, together with every
sample URL the map itself can reach — so installing a pack at runtime
(`applySoundPack`) warms it too, instead of leaving the first play of each name
to pay its own round trip. Preloading does NOT wait for the unlock gesture:
`decodeAudioData` works on a suspended context, while awaiting `resume()` before
the first click stays pending forever and downloaded nothing.
With `preferences.sound === 'off'` it downloads nothing at all — a pack that
cannot be heard is bytes spent on silence. The request is REMEMBERED, not
dropped: the level is read at dispatch, so the first audible play flushes the
deferred warm, and turning sound on does not hand back a cold cache.
The global unlock listener is not registered in the channel's constructor; it is
installed only once there is an `AudioContext` to unlock.
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
### When a component deserves a pack — participation doctrine (2026-07-07)
`Morfo.expression` (`SemaExpressionMode`, [`morfo/types.ts`](../../src/uix/morfo/types.ts),
author decision 2026-05-26) declares HOW a sema-scoped morfo contributes its
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
perceptual signature. THIS section is the canonical doctrine; it was
canonized at the component-audit checkpoint (verdicts S3a/S11 — historical
record in [`docs/audit/components/_veredictos.md`](../audit/components/_veredictos.md)):
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
four legitimate stances, each with shipped exemplars —
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. **`'pack'`** — a cascade in `sema/components/{kebab}.ts` NAMES the sound
per event. Justified when the pattern ADDS meaning beyond family + intent
deltas (date-field naming a fall for its clear; menubar's in-place note:
"subtle commit + tap haptic for high-frequency top-level triggers"). A pack
that only restates the family default is not a pack — it is noise, and the
type makes that cheap to see: one word, or nothing.
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
2. **`'family-default'`** — family base + intent deltas suffice. Legitimate
when (a) the gesture is generic contact (button), (b) the book itself
counsels restraint (command, citing ch. 22 §8: celebrating at the click is
*"celebrate before time"* — the outcome fires downstream), or (c) a
composed child supplies the character (field-langs: the embedded
ToggleGroup's pack puts the tap). The reason is written IN the morfo.
3. **`'delegated'`** — composite; expression lives in the children's packs.
The composite declares ONLY the events its children don't own (date-picker:
its own `commit-reset`; selection/commit sound through the embedded
date-field/calendar). The anti-duplication rationale is radio-cards'
morfo header: *"declaring them here would duplicate the contract"*.
4. **Declared debt** — `events: []` with an honest "yet" comment (the generic
Picker) is better than silence but MUST carry a deadline or become a
decision: an undated "yet" is a hole in disguise.
Coherence is guarded: `morfo-vocabulary-check` fails when a pack file exists
and `expression` declares anything other than `'pack'`, and warns when a pack
exists with no `expression` at all (verdict S11d).
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### The sound pack — `SEMA_MAP.sounds`
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**A component names a sound. It never writes a parameter.** The whole authoring
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
surface of the sound channel is one word, and only when the component differs
from its family's default:
```ts
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
{ selector: onTrigger({ eventName: 'contact-activate' }), sound: 'snap' }
```
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`Sema['cascade'].sound` accepts a `SoundName` or `SILENT`, **and nothing else** —
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
the law is a type, not a convention. The same holds for the morfo's per-event
`overrides.sound`, for `overrides.cascade` (the app's door) and for the per-emit
option. Nobody authors a signature inline, not even the product: bringing a new
sound means REGISTERING it (declare the name, define it once) and then naming it.
fix(sema): los toggles recuperan voz, y el silencio deja de sintetizarse — muere el prefijo form. Tres cosas que el mismo hilo destapo, de la mas visible a la mas de fondo. 1) LOS TOGGLES SUENAN OTRA VEZ (directiva de autor 2026-08-05) switch, toggle y toggle-group llevaban SILENCIADOS desde el 2026-05-26: su pack aplicaba un tuning que restaba exactamente el gain base de la familia commit (0.3 - 0.3 = 0). Medido en navegador: pico 0.0001, el suelo de la rampa. Un silencio por defecto no se percibe como sobriedad sino como componente roto. Pasan a `commit.medium` (gain 0.1): audible, y un tercio de una pulsacion de boton (contact base 0.25) para que un flip no pese mas que una activacion. Los deltas de intent siguen montando encima (threat +0.1, fulfill +0.05). El haptico ligero no cambia. Medido despues: switch pico 0.1. Conservan el silencio, con su justificacion intacta, solo los dos donde el silencio ES la firma: tooltip (revela al pasar el raton, sonaria en cada cruce) y media-player (su propia salida ES audio — D-AP2.7, decision del hilo de audio). 2) EMITIR SILENCIO YA NO CUESTA `SoundChannel.handle` cortaba DESPUES de resolver la firma, asi que un tuning silenciador levantaba dos osciladores, un filtro y una envolvente programada para no sonar, en cada interaccion. Ahora corta antes de sintetizar cuando la ganancia resuelta es <= 0. El corte mira la ganancia RESUELTA, no el tuning, asi que un intent que la devuelve por encima de 0 (threat, fulfill) suena igual. Fijado con test propio, verificado en ROJO quitando el corto. 3) EL AFINADO SE NOMBRA POR LA FAMILIA QUE MODIFICA El catalogo mezclaba tres ejes en el primer segmento —familia (emerge.*, contact.silent), componente (tooltip.*, tabs.*) y la categoria huerfana form.*— sin regla declarada y sin guard que mirara. `form.commit.soft/subtle` entro el 2026-05-19 (09e261878, el commit que CREA sounds.ts) nombrando con verdad a siete controles de formulario; el 2026-05-26, 2d562f378 lo extendio a menus y arboles en la misma pasada que escribia la doctrina: 7 -> 29 consumidores en un commit, 50 hoy, de los que exactamente UNO era el Form. El D.6 de ese commit llego a poner el nombre correcto —commit.select— en la linea que usaba la clave incorrecta. form.commit.soft -> commit.soft form.commit.subtle -> commit.subtle form.toggle.silent -> commit.silent tooltip.silent -> emerge.silent tabs.select.soft -> commit.select.soft 131 ocurrencias en 68 ficheros, dirigido por `git ls-files` — nunca por recorrido de directorio: hay 3 worktrees vivos y build/ con 449 apariciones mas que un sed recursivo habria corrompido en silencio. La cola queda LIBRE a proposito: media-player aplica el silenciador al commit-set de un slider, no a un toggle — la cola describe la FIRMA, no el evento, asi que cerrarla contra el vocabulario de verbos fabricaria otra mentira. Por eso emerge.* no se toca pese a que `exit` no sea verbo canonico. Quedan tres silenciadores, uno por familia con base audible, con el mismo nombre: contact.silent (-0.25), commit.silent (-0.3), emerge.silent (-0.2). Eso convierte la coherencia en INVARIANTE: - `sounds-grammar.test.ts` (guard nuevo): cabeza ∈ SEMA_FAMILIES; forma {family}.{tail}; y todo {family}.silent resta EXACTAMENTE el gain base de su familia, leido de sema-map.ts —nunca copiado—. Verificado en ROJO: el guard calculo solo el -0.4 esperado de signal. - Ley escrita en sema.md § Tuning naming shapes, junto a la seccion hermana que ya legislaba los nombres de EVENTO. - vocabularies.md —"the ONE place the closed sets are spelled out"— pasa a enumerar los 14 afinados por familia; el generador los extrae del const y docs:check vigila su frescura. - D.5 gana las dos notas: el renombrado y la reversion del silencio. VERIFICACION: check 75 (linea base exacta) · sema 198/198 · docs:check 0/615 · navegador antes/despues (switch 0.0001 -> 0.1; button 0.25 sin tocar) · los 25 avisos de prettier entre lo tocado son deriva YA en HEAD, ninguno mio. Cronica NO reescrita (4 ficheros): AUDIT-blocks-2026-08-01, PLAN-audio-player, handoffs-claude-md y audit/components/date-field registran lo que ERA cierto. DESVIACION DECLARADA: sema/components/chronos.ts entra con 6 sustituciones mecanicas —chronos es de escritura excluida, pero dejarlo fuera rompia el type-check del repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### The two kinds of entry
fix(sema): los toggles recuperan voz, y el silencio deja de sintetizarse — muere el prefijo form. Tres cosas que el mismo hilo destapo, de la mas visible a la mas de fondo. 1) LOS TOGGLES SUENAN OTRA VEZ (directiva de autor 2026-08-05) switch, toggle y toggle-group llevaban SILENCIADOS desde el 2026-05-26: su pack aplicaba un tuning que restaba exactamente el gain base de la familia commit (0.3 - 0.3 = 0). Medido en navegador: pico 0.0001, el suelo de la rampa. Un silencio por defecto no se percibe como sobriedad sino como componente roto. Pasan a `commit.medium` (gain 0.1): audible, y un tercio de una pulsacion de boton (contact base 0.25) para que un flip no pese mas que una activacion. Los deltas de intent siguen montando encima (threat +0.1, fulfill +0.05). El haptico ligero no cambia. Medido despues: switch pico 0.1. Conservan el silencio, con su justificacion intacta, solo los dos donde el silencio ES la firma: tooltip (revela al pasar el raton, sonaria en cada cruce) y media-player (su propia salida ES audio — D-AP2.7, decision del hilo de audio). 2) EMITIR SILENCIO YA NO CUESTA `SoundChannel.handle` cortaba DESPUES de resolver la firma, asi que un tuning silenciador levantaba dos osciladores, un filtro y una envolvente programada para no sonar, en cada interaccion. Ahora corta antes de sintetizar cuando la ganancia resuelta es <= 0. El corte mira la ganancia RESUELTA, no el tuning, asi que un intent que la devuelve por encima de 0 (threat, fulfill) suena igual. Fijado con test propio, verificado en ROJO quitando el corto. 3) EL AFINADO SE NOMBRA POR LA FAMILIA QUE MODIFICA El catalogo mezclaba tres ejes en el primer segmento —familia (emerge.*, contact.silent), componente (tooltip.*, tabs.*) y la categoria huerfana form.*— sin regla declarada y sin guard que mirara. `form.commit.soft/subtle` entro el 2026-05-19 (09e261878, el commit que CREA sounds.ts) nombrando con verdad a siete controles de formulario; el 2026-05-26, 2d562f378 lo extendio a menus y arboles en la misma pasada que escribia la doctrina: 7 -> 29 consumidores en un commit, 50 hoy, de los que exactamente UNO era el Form. El D.6 de ese commit llego a poner el nombre correcto —commit.select— en la linea que usaba la clave incorrecta. form.commit.soft -> commit.soft form.commit.subtle -> commit.subtle form.toggle.silent -> commit.silent tooltip.silent -> emerge.silent tabs.select.soft -> commit.select.soft 131 ocurrencias en 68 ficheros, dirigido por `git ls-files` — nunca por recorrido de directorio: hay 3 worktrees vivos y build/ con 449 apariciones mas que un sed recursivo habria corrompido en silencio. La cola queda LIBRE a proposito: media-player aplica el silenciador al commit-set de un slider, no a un toggle — la cola describe la FIRMA, no el evento, asi que cerrarla contra el vocabulario de verbos fabricaria otra mentira. Por eso emerge.* no se toca pese a que `exit` no sea verbo canonico. Quedan tres silenciadores, uno por familia con base audible, con el mismo nombre: contact.silent (-0.25), commit.silent (-0.3), emerge.silent (-0.2). Eso convierte la coherencia en INVARIANTE: - `sounds-grammar.test.ts` (guard nuevo): cabeza ∈ SEMA_FAMILIES; forma {family}.{tail}; y todo {family}.silent resta EXACTAMENTE el gain base de su familia, leido de sema-map.ts —nunca copiado—. Verificado en ROJO: el guard calculo solo el -0.4 esperado de signal. - Ley escrita en sema.md § Tuning naming shapes, junto a la seccion hermana que ya legislaba los nombres de EVENTO. - vocabularies.md —"the ONE place the closed sets are spelled out"— pasa a enumerar los 14 afinados por familia; el generador los extrae del const y docs:check vigila su frescura. - D.5 gana las dos notas: el renombrado y la reversion del silencio. VERIFICACION: check 75 (linea base exacta) · sema 198/198 · docs:check 0/615 · navegador antes/despues (switch 0.0001 -> 0.1; button 0.25 sin tocar) · los 25 avisos de prettier entre lo tocado son deriva YA en HEAD, ninguno mio. Cronica NO reescrita (4 ficheros): AUDIT-blocks-2026-08-01, PLAN-audio-player, handoffs-claude-md y audit/components/date-field registran lo que ERA cierto. DESVIACION DECLARADA: sema/components/chronos.ts entra con 6 sustituciones mecanicas —chronos es de escritura excluida, pero dejarlo fuera rompia el type-check del repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
The pack has BASES (`tick`) and VARIANTS (`tick.threat`). Resolution is two
lookups: `name.intent`, then bare `name`, then silence. So a pack that ships only
bases works whole — the intent is ignored where no variant exists — and a pack
that cares adds variants only where it matters.
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
The default pack is **pure synthesis**: it works offline, pays no fetch and
cannot 404. A sample entry (`wav` / `mp3` / `ogg` / `aac`) carries its URL plus a
synthetic FALLBACK, so a missing file degrades to a designed sound rather than to
silence. Whether a name is a recipe or a file is not the component's business.
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. 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
##### Two ways to DELIVER a file-based pack
The delivery is not part of the contract. A pack is
`Partial<Record<SoundName, NamedSound>>` either way, hot-swappable either way,
and a name that is not a `SoundName` is a compile error either way.
- **One file per name.** The pack points at URLs (`sample('/sounds/tick.mp3', …)`).
The engine's cache is keyed by URL, so each file is fetched and decoded once;
the browser caches each independently, and `preload: false` on an entry keeps
it out of the warm-up so rare sounds load on demand.
- **One file for the whole pack.** A JSON of base64 payloads keyed by sound
name, turned into a pack by `packFromBase64()` — the entries become `data:`
URIs. Measured on `static/sounds/ui-inline.json`: **one** request instead of
fifteen, and 69.082 B under brotli against 79.332 B for the loose files,
because base64's +33 % is undone by compression while `audio/mpeg` is not
compressed by anyone. The cost is granularity: the pack caches as one object,
so one changed sound re-downloads all of them.
A `data:` URI is decoded IN PLACE — never fetched. A `connect-src 'self'` CSP
BLOCKS `fetch('data:…')` (the directive matches by scheme, and `'self'` does not
cover `data:`) while letting a same-origin path through, so routing an inline
pack through `fetch` made it degrade silently to synthesis in exactly the
environment that made someone inline it. It is also ~6x slower.
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### Designed to be told apart
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`sound-names.test.ts` fails when two entries are not separated on at least TWO
perceptual axes — a fifth of pitch, a third of brightness, half again as long,
a different contour, double the level, or 0.2 of roughness.
That guard exists because its absence was measured, by ear, by the author: a
previous catalogue had five names that were the same 700 Hz note at five
volumes, and every test passed. A snapshot could not have caught it — each value
was exactly what the table said. **The missing property was a RELATION between
entries**, and that is what the guard asserts.
#### Verbs, not components
Names describe an occurrence, never a widget: `open`, `tick`, `alert`. A library
organised by component (`button_hard`, `panel_expand`, `window_open`) needs
TRANSLATING into this vocabulary, and several of its files will compete for one
name — that is normal, and the choosing is where a pack author's ear works. See
`packs/ui-mp3.ts` for a worked example, including what it could not use.
#### Samples: what a file can and cannot carry
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`playSample` reads **exactly two** fields of the resolved signature: `sampleUrl`
docs: el corpus entero deja de describir un sistema que ya no existe Tras rehacer el sonido dos veces en un dia, la doctrina escrita seguia explicando la cascada de cinco capas, los tunings y la modulacion por intent — exactamente la clase de mentira que esta sesion entera se dedico a perseguir en el codigo. 23 ficheros al dia. FUENTES DE DOCTRINA - `architecture/sema.md` — la seccion de resolucion ya no numera seis capas para el sonido: enuncia las dos busquedas y dice que el sonido NO participa de la cascada, que es el diseño. La convencion de deltas se acota al haptico y a `hold`, que son los unicos que aun tienen numeros. La seccion del catalogo se reescribe como «el sound pack»: bases y variantes, el pack por defecto sin binarios, el guard de distinguibilidad con la razon de por que existe (se detecto por OIDO, no por test), y por que una biblioteca nombrada por componente hay que TRADUCIRLA. - `CANON.md` §7 — la fila del sonido dice ahora «un nombre, y solo cuando difiere de su familia», y se añade que el intent SELECCIONA, con el motivo perceptual: reconocemos sonidos, no desplazamientos de parametro. - `CLAUDE.md` — la doctrina operativa pasa de tres reglas a seis, y todas enunciadas por su mecanismo: las dos busquedas, que NADIE escribe un parametro, el tier del verbo que vacia los packs, el guard de distinguibilidad, el limite fisico de las grabaciones y los gestos por repeticion. - `book-deviations` D.7/S-07 — dejan de explicarse por «el nombre va debajo del intent» (modelo intermedio, tambien muerto) y se explican por la ausencia de aritmetica. Y D.8 corrige la tabla de canales: `handle` suena, y los tres resolvers de gesto que la entrada citaba como justificacion ya no existen. PROCESO - `PLAN-sound-names.md` se marca SUPERADO EL MISMO DIA, con la leccion escrita arriba del todo: un sistema de modulacion produce variantes que difieren sobre el papel y son identicas al oido, y eso solo se detecta escuchando. Se conserva como cronica de por que NO se hizo asi. - `CONTINUE-sema-audit.md` gana §3.0-bis con el modelo vigente, los tres commits y dos avisos que la siguiente sesion necesita: que nada se ha oido sobre un componente real, y que el catalogo es una PROPUESTA. COMPONENTES — 16 READMEs citaban nombres de tuning que ya no existen (`commit.subtle`, `emerge.exit.soft`, `handle.pickup.air`…). Migrados a los nombres vivos. ⚠️ Distingui los que eran pares FAMILIA.VERBO (`commit.set`, `emerge.open`) y esos no se tocan: siguen siendo correctos. ⚠️ `chronos/SPEC.md` lo migre y lo REVERTI al darme cuenta: es de escritura excluida, asi que sigue citando nombres muertos — decision tuya. VERIFICADO: docs:check 0/618 · check sin errores nuevos · las unicas referencias a `SOUND_TUNINGS` que quedan son las de S-07, deliberadas: citan lo que ya no existe para explicar por que dejo de existir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
and `gain`. There is no `playbackRate` and no `detune`. So over a recording the
intent can only move the LEVEL.
Consequence, and it is physics rather than policy: **a sample that must carry
evaluative weight needs one file per intent** — `tick.mp3` AND
`tick.threat.mp3`, not one file bent two ways. With synthesis the problem does
not arise, because each variant is designed whole.
fix(sema): los toggles recuperan voz, y el silencio deja de sintetizarse — muere el prefijo form. Tres cosas que el mismo hilo destapo, de la mas visible a la mas de fondo. 1) LOS TOGGLES SUENAN OTRA VEZ (directiva de autor 2026-08-05) switch, toggle y toggle-group llevaban SILENCIADOS desde el 2026-05-26: su pack aplicaba un tuning que restaba exactamente el gain base de la familia commit (0.3 - 0.3 = 0). Medido en navegador: pico 0.0001, el suelo de la rampa. Un silencio por defecto no se percibe como sobriedad sino como componente roto. Pasan a `commit.medium` (gain 0.1): audible, y un tercio de una pulsacion de boton (contact base 0.25) para que un flip no pese mas que una activacion. Los deltas de intent siguen montando encima (threat +0.1, fulfill +0.05). El haptico ligero no cambia. Medido despues: switch pico 0.1. Conservan el silencio, con su justificacion intacta, solo los dos donde el silencio ES la firma: tooltip (revela al pasar el raton, sonaria en cada cruce) y media-player (su propia salida ES audio — D-AP2.7, decision del hilo de audio). 2) EMITIR SILENCIO YA NO CUESTA `SoundChannel.handle` cortaba DESPUES de resolver la firma, asi que un tuning silenciador levantaba dos osciladores, un filtro y una envolvente programada para no sonar, en cada interaccion. Ahora corta antes de sintetizar cuando la ganancia resuelta es <= 0. El corte mira la ganancia RESUELTA, no el tuning, asi que un intent que la devuelve por encima de 0 (threat, fulfill) suena igual. Fijado con test propio, verificado en ROJO quitando el corto. 3) EL AFINADO SE NOMBRA POR LA FAMILIA QUE MODIFICA El catalogo mezclaba tres ejes en el primer segmento —familia (emerge.*, contact.silent), componente (tooltip.*, tabs.*) y la categoria huerfana form.*— sin regla declarada y sin guard que mirara. `form.commit.soft/subtle` entro el 2026-05-19 (09e261878, el commit que CREA sounds.ts) nombrando con verdad a siete controles de formulario; el 2026-05-26, 2d562f378 lo extendio a menus y arboles en la misma pasada que escribia la doctrina: 7 -> 29 consumidores en un commit, 50 hoy, de los que exactamente UNO era el Form. El D.6 de ese commit llego a poner el nombre correcto —commit.select— en la linea que usaba la clave incorrecta. form.commit.soft -> commit.soft form.commit.subtle -> commit.subtle form.toggle.silent -> commit.silent tooltip.silent -> emerge.silent tabs.select.soft -> commit.select.soft 131 ocurrencias en 68 ficheros, dirigido por `git ls-files` — nunca por recorrido de directorio: hay 3 worktrees vivos y build/ con 449 apariciones mas que un sed recursivo habria corrompido en silencio. La cola queda LIBRE a proposito: media-player aplica el silenciador al commit-set de un slider, no a un toggle — la cola describe la FIRMA, no el evento, asi que cerrarla contra el vocabulario de verbos fabricaria otra mentira. Por eso emerge.* no se toca pese a que `exit` no sea verbo canonico. Quedan tres silenciadores, uno por familia con base audible, con el mismo nombre: contact.silent (-0.25), commit.silent (-0.3), emerge.silent (-0.2). Eso convierte la coherencia en INVARIANTE: - `sounds-grammar.test.ts` (guard nuevo): cabeza ∈ SEMA_FAMILIES; forma {family}.{tail}; y todo {family}.silent resta EXACTAMENTE el gain base de su familia, leido de sema-map.ts —nunca copiado—. Verificado en ROJO: el guard calculo solo el -0.4 esperado de signal. - Ley escrita en sema.md § Tuning naming shapes, junto a la seccion hermana que ya legislaba los nombres de EVENTO. - vocabularies.md —"the ONE place the closed sets are spelled out"— pasa a enumerar los 14 afinados por familia; el generador los extrae del const y docs:check vigila su frescura. - D.5 gana las dos notas: el renombrado y la reversion del silencio. VERIFICACION: check 75 (linea base exacta) · sema 198/198 · docs:check 0/615 · navegador antes/despues (switch 0.0001 -> 0.1; button 0.25 sin tocar) · los 25 avisos de prettier entre lo tocado son deriva YA en HEAD, ninguno mio. Cronica NO reescrita (4 ficheros): AUDIT-blocks-2026-08-01, PLAN-audio-player, handoffs-claude-md y audit/components/date-field registran lo que ERA cierto. DESVIACION DECLARADA: sema/components/chronos.ts entra con 6 sustituciones mecanicas —chronos es de escritura excluida, pero dejarlo fuera rompia el type-check del repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### Silence is a value, not a name
fix(sema): el silencio deja de ser una resta y pasa a ser un valor — SILENT Directiva del autor: «el silencio no admite verbos ni intents, es universal, por lo tanto es un valor canonico que cuando se le envia al canal semantico este simplemente lo ignora y no envia nada al soundengine». LO QUE ERA. `commit.silent`, `emerge.silent` y `contact.silent` no eran silencio: eran una RESTA del gain base de su familia (-0.3, -0.2, -0.25). Por eso hacian falta tres —cada una tenia que saber que base cancelaba— y por eso el silencio tenia familia, que es el absurdo que el autor senalo. Y un cero es un NUMERO, y las capas posteriores mueven numeros. LA MEDICION QUE LO CIERRA. Los deltas de intent suman RUGOSIDAD (`risk` +0.2, `threat` +0.4), lo que cruza el umbral del AM del motor; y el modulador se conecta al AudioParam de la envolvente —y conectar a un AudioParam SUMA, no multiplica—, asi que con la envolvente en cero el tremolo pasaba a ser la senal entera. Renderizado offline con el grafo real del motor: toggle "silenciado" neutral -77.0 dBFS (mudo de verdad) toggle "silenciado" risk -13.9 dBFS <- con gain declarado 0 toggle "silenciado" threat -6.7 dBFS boton normal, referencia -9.3 dBFS El "silencio" era mas fuerte que un boton, y lo que sonaba no era el earcon: era un zumbido sin ataque ni caida, cortado en seco. La medicion que yo mismo di por buena esta manana (0.0001) solo cubria el caso neutral. LO QUE ES AHORA. `SILENT` — un valor canonico exportado por `$uix/sema`. Se declara en el slice del canal y el RESOLVER lo honra retirando ese canal de la firma. Un solo mecanismo para todos los canales, y nada aguas abajo ve jamas un string donde espera una firma. Es la forma que el propio motor ya prescribia y se estaba saltando: «muting DROPS the channel rather than scaling to zero, because a `0` still buzzes» (engine.ts). - universal por construccion: ninguna familia, ningun verbo, ningun intent puede resucitarlo, porque no queda ningun numero que mover; - liftable solo por REEMPLAZO: declarar sonido es una decision, no un delta; - `SoundOverride` lo EXCLUYE por tipo — un afinado es un delta, nunca una ausencia. Migrado: tooltip (1) y media-player (6). Los tres tunings mueren del catalogo. GUARDS. `resolver.test.ts` fija las cuatro mitades del contrato (retira el canal · universal sobre 4 familias x 4 intents · vale igual para haptic · liftable por reemplazo), verificado en ROJO. `sounds-grammar.test.ts` cambia su invariante muerta por las dos que ahora importan: ninguna clave `*.silent`, y ningun afinado que deje su familia en `gain <= 0` — silencio disfrazado. Doctrina al dia: sema.md gana la seccion «Silence is a value, not a tuning» con la medicion; D.5 marca DEROGADA su solucion de mayo y registra el porque; vocabularies.md deja de enumerar silenciadores y explica donde vive el silencio. VERIFICADO: sema 211/211 · check 76 = la base 75 + 1 error del fichero huerfano `__adv-verify-haptic-dead.test.ts` que dejo un agente de la auditoria (sin trackear, no se borra sin instruccion) · docs:check 0/616 · navegador: el transporte del player EMITE (`contact-activate`, `commit-toggle-play`) y levanta CERO osciladores · prettier limpio (el unico aviso mio, resolver.ts, formateado: +17 lineas, sin ruido). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
`SILENT` is THE canonical silence — one value for the whole system, exported
from `$uix/sema`. Set a channel slice to it and **the channel that owns the
slice ignores it and dispatches nothing downstream**: no signature is
resolved, no engine is called, no context is opened.
It admits no family, no verb and no intent, because there is nothing to
modulate. That is why it is not a key: a per-family silencer would need to
know which base it is cancelling, which is how the catalogue ended up with
three of them.
```ts
// a pack rule that does not speak in sound
{ selector: onProvider({ eventName: 'emerge-open' }), sound: SILENT }
```
**Absorbing by construction.** Intent deltas do not apply on top of `SILENT`
(layer 2 cannot un-silence). A later override CAN lift it by REPLACING the
slice with a full signature — declaring sound is a decision, not a delta.
**Why it is a value and not a zero.** Until 2026-08-06 silence was arithmetic:
`{family}.silent` subtracted the family's base gain so neutral landed at 0.
A zero gain is a NUMBER, and later layers move numbers. Measured on a
"silenced" toggle: an intent delta that raises `roughness` past the AM
threshold (`risk` +0.2, `threat` +0.4) turned the signature into a raw tremolo
at **−13.9 dBFS** (`risk`) and **−6.7 dBFS** (`threat`) — louder than a real
button press at −9.3. The engine had already written the rule it was breaking:
*«muting DROPS the channel rather than scaling to zero, because a `0` still
buzzes»* (`engine.ts`).
The grammar and the ban on silence-as-arithmetic are enforced by
`src/uix/sema/sounds-grammar.test.ts`, which reads the families from `event.ts`
and the base gains from `sema-map.ts` rather than restating them; the runtime
half (`SILENT` reaches no engine) is pinned in `chans/sound.test.ts`. The guard
exists because `form.*` named seven form controls truthfully on 2026-05-19, was
extended to menus and trees a week later, and reached 50 packs — one of which
was the Form — before anyone noticed, since no check ever looked at these keys.
fix(sema): los toggles recuperan voz, y el silencio deja de sintetizarse — muere el prefijo form. Tres cosas que el mismo hilo destapo, de la mas visible a la mas de fondo. 1) LOS TOGGLES SUENAN OTRA VEZ (directiva de autor 2026-08-05) switch, toggle y toggle-group llevaban SILENCIADOS desde el 2026-05-26: su pack aplicaba un tuning que restaba exactamente el gain base de la familia commit (0.3 - 0.3 = 0). Medido en navegador: pico 0.0001, el suelo de la rampa. Un silencio por defecto no se percibe como sobriedad sino como componente roto. Pasan a `commit.medium` (gain 0.1): audible, y un tercio de una pulsacion de boton (contact base 0.25) para que un flip no pese mas que una activacion. Los deltas de intent siguen montando encima (threat +0.1, fulfill +0.05). El haptico ligero no cambia. Medido despues: switch pico 0.1. Conservan el silencio, con su justificacion intacta, solo los dos donde el silencio ES la firma: tooltip (revela al pasar el raton, sonaria en cada cruce) y media-player (su propia salida ES audio — D-AP2.7, decision del hilo de audio). 2) EMITIR SILENCIO YA NO CUESTA `SoundChannel.handle` cortaba DESPUES de resolver la firma, asi que un tuning silenciador levantaba dos osciladores, un filtro y una envolvente programada para no sonar, en cada interaccion. Ahora corta antes de sintetizar cuando la ganancia resuelta es <= 0. El corte mira la ganancia RESUELTA, no el tuning, asi que un intent que la devuelve por encima de 0 (threat, fulfill) suena igual. Fijado con test propio, verificado en ROJO quitando el corto. 3) EL AFINADO SE NOMBRA POR LA FAMILIA QUE MODIFICA El catalogo mezclaba tres ejes en el primer segmento —familia (emerge.*, contact.silent), componente (tooltip.*, tabs.*) y la categoria huerfana form.*— sin regla declarada y sin guard que mirara. `form.commit.soft/subtle` entro el 2026-05-19 (09e261878, el commit que CREA sounds.ts) nombrando con verdad a siete controles de formulario; el 2026-05-26, 2d562f378 lo extendio a menus y arboles en la misma pasada que escribia la doctrina: 7 -> 29 consumidores en un commit, 50 hoy, de los que exactamente UNO era el Form. El D.6 de ese commit llego a poner el nombre correcto —commit.select— en la linea que usaba la clave incorrecta. form.commit.soft -> commit.soft form.commit.subtle -> commit.subtle form.toggle.silent -> commit.silent tooltip.silent -> emerge.silent tabs.select.soft -> commit.select.soft 131 ocurrencias en 68 ficheros, dirigido por `git ls-files` — nunca por recorrido de directorio: hay 3 worktrees vivos y build/ con 449 apariciones mas que un sed recursivo habria corrompido en silencio. La cola queda LIBRE a proposito: media-player aplica el silenciador al commit-set de un slider, no a un toggle — la cola describe la FIRMA, no el evento, asi que cerrarla contra el vocabulario de verbos fabricaria otra mentira. Por eso emerge.* no se toca pese a que `exit` no sea verbo canonico. Quedan tres silenciadores, uno por familia con base audible, con el mismo nombre: contact.silent (-0.25), commit.silent (-0.3), emerge.silent (-0.2). Eso convierte la coherencia en INVARIANTE: - `sounds-grammar.test.ts` (guard nuevo): cabeza ∈ SEMA_FAMILIES; forma {family}.{tail}; y todo {family}.silent resta EXACTAMENTE el gain base de su familia, leido de sema-map.ts —nunca copiado—. Verificado en ROJO: el guard calculo solo el -0.4 esperado de signal. - Ley escrita en sema.md § Tuning naming shapes, junto a la seccion hermana que ya legislaba los nombres de EVENTO. - vocabularies.md —"the ONE place the closed sets are spelled out"— pasa a enumerar los 14 afinados por familia; el generador los extrae del const y docs:check vigila su frescura. - D.5 gana las dos notas: el renombrado y la reversion del silencio. VERIFICACION: check 75 (linea base exacta) · sema 198/198 · docs:check 0/615 · navegador antes/despues (switch 0.0001 -> 0.1; button 0.25 sin tocar) · los 25 avisos de prettier entre lo tocado son deriva YA en HEAD, ninguno mio. Cronica NO reescrita (4 ficheros): AUDIT-blocks-2026-08-01, PLAN-audio-player, handoffs-claude-md y audit/components/date-field registran lo que ERA cierto. DESVIACION DECLARADA: sema/components/chronos.ts entra con 6 sustituciones mecanicas —chronos es de escritura excluida, pero dejarlo fuera rompia el type-check del repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
### Channels and signatures
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
| Channel | Consumed slice | Behavior when not applicable |
| -------- | ------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `visual` | `effective.hold` for the hold | Falls back to the family table (SEMA_DURATIONS) or `defaultHold` |
| `sound` | `effective.sound` (skips if `'sound'` not in activeChannels) | no-op |
| `haptic` | `effective.haptic` (Vibration API, honors `prefers-reduced-motion`) | no-op if there is no `navigator.vibrate` |
| (custom) | declaration merging of `SemaChannelSignatures` | read by the registered channel |
### The visual channel's attr namespace
`VisualChannel.prepare()` projects **only** attributes under the
`data-event-*` prefix:
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
| Attr | When |
| ------------------- | ------------------- |
| `data-event` | always |
| `data-event-id` | always |
| `data-event-phase` | always (`'active'`) |
| `data-event-family` | if `signal.family` |
| `data-event-intent` | if `signal.intent` |
**Rule**: the channel never touches state attrs (`data-state`,
`data-intent`, `data-disabled`, ...). State is managed by the runtime/morfo.
Reason: state is persistent and a signal is transient; reusing the same name
would force the channel to erase state on cleanup (or into fragile
save/restore if state mutates during the hold).
For CSS:
- `[data-event-intent='risk']` → reacts to the transient occurrence's intent
- `[data-intent='risk']` → reacts to the component's persistent state
Both can coexist on the same element with distinct semantics.
### Hold — the visual channel's resolution chain
The `VisualChannel` resolves its `hold` (how long the `data-event-*` live in
the DOM) in this order:
1. `signal.hold` — imperative per-call override.
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
2. `effective.hold` — composed by the resolver from
`resolveHoldsByIntent(family, intent)` (`holds.ts`,
`SEMA_HOLDS_BY_INTENT` — the ONE canonical hold source; the per-family
`SEMA_MAP.families[*].hold` field was REMOVED 2026-07-06 because it
duplicated this table and had drifted from the book's regions).
3. The same canonical table, consulted directly by the channel — defense
when a custom map's resolver path didn't compose a hold.
4. The channel's global `defaultHold` (`brief`, 240ms by default).
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
See `holds.ts` (`SEMA_HOLDS_BY_INTENT`) for the concrete family+intent values.
**The hold timer runs on the managed scheduler, not on raw `setTimeout`.**
The engine receives `timers` (in `ActiveUix` it is `uix.timers`) and forwards
it to the three channels: the `VisualChannel`'s hold, the `HapticChannel`'s
`delay` and the `SoundChannel`'s earcon duration are scheduled via
`uix.timers.schedule(...)` (the `semaDelay` helper in
`src/uix/sema/timers.ts`). That makes perceptual timing cancelable on
`dispose`, observable, and deterministic under a fake clock in tests. It only
falls back to `setTimeout` when a channel is built without a scheduler
(direct unit tests); production always injects the scheduler.
> **Overlay openings — `sequence: 'post'`, not `'pre'`.** An appearance event
> whose provider sets `open` in the HANDLER (Popover `present`, Dialog
> `open`, Drawer `present`) MUST declare `sequence: 'post'`. With `'pre'` the
> runtime awaits the emit — and therefore the ~240ms hold — BEFORE the
> handler, gating the content mount behind the hold: the overlay opens late
> and its first render lands inside the hold's `setTimeout` turn (the
> "setTimeout handler took N ms" violation). Same doctrine as the
> checkbox-lag fix. Closing (`close`) stays `'pre'`: there the element exists
> and the signal MUST precede the unmount.
```ts
// Per-signal override
semantic.emit({ ..., hold: 1200 })
// Override the visual channel's global default
const semantic = new EngineSemantic({ dom, visual: { defaultHold: 400 } })
// Disable visual (DOM-less environments)
const semantic = new EngineSemantic({ visual: false })
// Enable the built-in sound channel (opt-in: audible side effect)
const semantic = new EngineSemantic({ dom, sound: true })
// With SoundChannel options
const semantic = new EngineSemantic({
dom,
sound: { masterGain: 0.6 }
})
// Enable the built-in haptic channel (opt-in: device feedback)
const semantic = new EngineSemantic({ dom, haptic: true })
// With HapticChannel options
const semantic = new EngineSemantic({
dom,
haptic: { masterIntensity: 0.7 }
})
// Register custom channels (a11y, voice, future)
class A11yChannel implements Channel {
readonly id = 'a11y'
async handle(signal, effective) { /* live-region updates, etc. */ }
}
semantic.register(new A11yChannel())
```
### Override layers — recipes
```ts
import { dialogSema } from '$uix/sema/components/dialog';
const semantic = new EngineSemantic({
dom,
sound: true,
haptic: true,
// Layer 5a — component packs (defaults shipped with each component)
components: [dialogSema /* , toastSema, drawerSema, … */],
overrides: {
// Layer 4 — targeted SEMA_MAP edits, valid app-wide
runtime: {
'families.commit.base.sound.pitch': 850,
'intents.threat.deltas.haptic.intensity': 0.4
},
// Layer 5b — app CSS-like rules, matched against signal.target
// AFTER the component packs
cascade: [
{
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
// Typed builder here too — `ancestor` carries the instance id
// (it lives outside the morfo's contract: plain string by design).
selector: semaSelector(dialogMorfo, 'content', {
eventIntent: 'threat',
ancestor: '#delete-confirm-dialog'
}),
sound: { sampleUrl: '/sounds/scary.wav', gain: 0.25 },
haptic: { kind: 'error', pattern: [50, 80, 50, 80, 50] }
},
{
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
// Targets NO morfo-emitted attr (a global reduce gate over the
// engine's own `data-event-*` stamps) — legitimately hand-written;
// the builder mandate covers morfo-targeting selectors only.
selector: ':root[data-sound="reduce"] [data-event-phase="active"]',
priority: 100,
sound: { gain: 0.05 },
channels: ['sound']
}
]
}
});
```
```ts
// Layer 3 — per-event override declared in the component's own morfo.
// Propagates via SomaRuntime → SemanticSignal → resolver. The app can
// still override from the cascade (layers 5a/5b).
// src/uix/morfo/components/dialog.ts
{
name: 'close-after-fail',
semantic: {
family: 'signal',
verb: 'alert',
target: v.partRef('content'),
sequence: 'pre',
intent: 'threat',
// Per-event silencing: no sound, to avoid competing with the live region
channels: ['haptic'],
// Per-event override: the Dialog's own sample
overrides: {
haptic: { kind: 'error', pattern: [60, 80, 60, 80, 60] }
}
}
}
```
### Specificity between rules
Like CSS:
- **Computed selector specificity** — IDs × 100, attributes × 10, classes ×
10, pseudo-classes × 10, elements × 1.
- **Rules apply in ascending specificity order** — the last applied wins each
pointwise conflict.
- **Tie** → declaration order. Component packs appear BEFORE
`overrides.cascade`, so app rules win ties.
- **`priority?: number`** — overrides the computed value for cases that must
win without counting attributes (typically accessibility, `priority: 100+`).
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
To vary by event, intent, family, name, etc. — everything goes through the
typed builder's matchers, which stamp the `[data-event-*]` tokens. (The
hand-written examples this section used to show targeted `[data-toast-root]`
— a part that never existed in the toast morfo: exactly the silent drift
`semaSelector` turns into a compile error.)
```ts
// Vary by the event's intent
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
{ selector: semaSelector(toastMorfo, 'item', { eventIntent: 'threat' }), haptic: { kind: 'error' } }
// Vary by family
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
{ selector: semaSelector(toastMorfo, 'item', { eventFamily: 'signal' }), sound: { gain: 0.4 } }
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
// Vary by exact event name (typed against the morfo's declared events)
{ selector: semaSelector(toastMorfo, 'item', { eventName: 'dismiss' }), sound: { sampleUrl: '/dismiss.wav' } }
// Vary by event prefix
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
{ selector: semaSelector(dialogMorfo, 'content', { eventNamePrefix: 'close-' }), sound: { contour: 'descending' } }
```
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
### SoundChannel — doctrine here, machine in `$sound`
> **The Web Audio machinery does NOT live in sema** (since 2026-07-30). The
> context lifecycle, the synthesis graph, the unlock-on-gesture and the sample
> path were extracted to the art [`$sound`](../../src/arts/sound/README.md)
> (`EngineSound`) — they were ~63% of a 407-line "channel" and are machinery,
> not perceptual doctrine. Same move `$motion` made out of eidos, for the same
> reason and with the same result: the art owns the RUNTIME, the layer keeps its
> DATA and doctrine.
What the channel keeps — all of it doctrine:
- the `prepare` gate (does this signal admit sound at all?),
- the per-channel reduction policy (BK-REDUCTIONS): `off` silences (meaning
migrates via `SEMA_MIGRATION.sound` → presence / live region), `reduce`
attenuates,
feat(sound): $sound orquestador (buses+voces+media) y player v2 F1-F3 sobre el servicio Rediseno clean-room del sonido (gate D-SR.1-12 firmado; la doctrina "la voz es del art" de PLAN-sound-engine s16.1 queda revocada): - Mezcla: master -> buses ui/content (gain/mute/duck refcount, gana la mas fuerte; politica pre-contexto). prefs de UI jamas tocan la obra. - Voces registradas: la calibracion es DATO del consumidor (sema registra 'sema'; el default conserva la calibracion historica - cero cambio audible, A/B del usuario: "suena igual"). - Ciudadania de media: sound.media(el) PROVEE el transporte (la forma exacta del puerto MediaProvider) + foco mixed/exclusive/duck PERSISTENTE (estado derivado; el ducker mas reciente retiene la palabra) + duckUiWhileContent + MediaSession de la fuente activa (play/pause/seekto + posicion, liberacion y promocion) + visibilidad content-aware + attach() opt-in (irreversible, CORS declarado). Re-registro idempotente por elemento. - diagnostics.ts con catalogo tipado (contrato de arts) y guards post-dispose (ningun camino abre un contexto huerfano). Auditoria AU-1..9 resuelta; bundle medido: 16,2 KB min / 5,6 KB gz. Player v2 (gate D-AP2.1-13 firmado) F1-F3: - F1 gaps de framework: aria-valuetext en Slider (valueText por thumb) + parte SecondaryRange con token --slider-secondary-bg (buffer/clip/capitulos) + formatDuration en $libs/days + fix del selector del pack (H-1: la regla de play/pause construia sobre provider y el stamp aterriza en play-button). - F2 morfo: 6 partes audio-only (artwork/artist/identity/transport/ rate-button/live-indicator), commit-set-rate, commit-set-time RETIRADO (D-AP2.13: el scrubber delega en el commit-set del Slider compuesto), sustain-loading cableado (H-9), teclado j/l, </> y 0-9 (con guard de modificadores para no pisar atajos del navegador). - F3 la migracion: el transporte default es uix.sound.media(el, {metadata}) (pineado con el motor real); nativeMediaProvider RETIRADO (media-provider.ts queda como contrato puro del puerto, sin shim); G-5: los commits de play/mute/rate cabalgan el evento de RESULTADO (play/pause saltando el pause de ended, volumechange con transicion de muted, ratechange); formatTime -> formatDuration; sub-providers + wrappers + exports de las 6 partes; prop metadata publica (captura al registrar, documentado). Verificacion: 45 suites / 458 tests verdes en el alcance (sound, sema, active-uix, active-app, morfo, slider, media-player, days) - check 74 = baseline de la rama, 0 propios - docs:check 0 - invariante de UN AudioContext intacta (e2e sin tocar). Planes de proceso y handoff al dia (PLAN-sound-redesign, PLAN-audio-player-v2, CONTINUE-sound-engine). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
- **its voice** — `SEMA_SOUND_VOICE` in `sounds.ts`, the synthesis calibration
sema's vocabulary was tuned against, registered on the engine at channel
construction (redesign 2026-07-31, D-SR.3 in `PLAN-sound-redesign.md`; it was
baked into the art as constants before). Earcons play on the engine's **`ui`
bus** with that voice — so UI-sound policy (BK-REDUCTIONS, resolved here and
handed over as `gainScale`) can never touch the content's volume; the `ui`
bus itself is the art's graph-side lever, with no direct prefs wiring today,
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
- and the division of labour: **the channel resolves the LEVEL, the engine
applies the gain**.
docs(sema)!: retirar D.7 y S-07 — el orden hace imposible lo que prohibian F6 del PLAN-sound-names, y el motivo importa mas que el borrado: no se derogan por cambio de opinion, se DISUELVEN. La doctrina del autor que las origino sigue siendo cierta; lo que cambia es quien la sostiene. D.7 prohibia canonizar un sample sobre un evento cuya familia tuviera base.sound, porque sound() devolvia una firma COMPLETA y la cascada la aplicaba en replace DESPUES del intent: un fulfill perdia su +300 Hz ascendente. S-07 era el mismo defecto una capa mas abajo — el gain desnudo de los 14 afinados de escalera — y seguia ABIERTO por decision del autor. Con el nombre aplicado ANTES de los deltas de intent no queda ninguna capa posterior que pueda pisarlos. Aplastar el perfil evaluativo ya no esta prohibido: es INEXPRESABLE. Y S-07 pierde su premisa entera, porque SOUND_TUNINGS ya no existe. LO QUE SE CONSERVA, con su sitio nuevo escrito: el silencio como firma valida (SILENT), el criterio de cuando crear un pack, y los samples como recurso de producto (el catalogo los admite con nombre; la app autora los suyos por overrides.cascade, que sigue abierto a proposito). LO UNICO DE D.7 QUE HAY QUE SEGUIR SABIENDO, y por eso queda escrito en tres sitios: playSample lee EXACTAMENTE dos campos, sampleUrl y gain. No hay playbackRate ni detune. Sobre un .wav el intent solo mueve el volumen — medido, risk es indistinguible de neutral porque solo aporta roughness y la ruta de sample lo ignora. Un sample con carga evaluativa necesita UN FICHERO POR INTENT, que es lo que hace signal con ping frente a error y la verdadera razon de que fuese la excepcion. Es fisica del audio, no politica. Tocado: book-deviations §D.7 + §S-07 · sema.md §the sound catalogue (reescrita entera, con el orden y el limite del WAV) · CLAUDE.md §sema doctrine · los dos README del art · CONTINUE-sema-audit §3.0/§3.1/§3.6, que es el handoff vivo y estaba describiendo una cola que ya no existe. ⚠️ Prettier reformatea entero book-deviations.md y sema.md porque NO estaban formateados en HEAD; reaplicado a mano para que el diff sea solo lo mio. Mismo motivo por el que CLAUDE.md y arts/README.md van con 1-2 lineas en vez de 30. VERIFICADO: sema+morfo+sound 437/437 · check 75 = baseline · docs:check 0/618 · prettier limpio en los ficheros que toque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Everything else — `SOUNDS` (the named catalogue), the gesture resolvers, the
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
cascade — is unchanged and still sema's.
**How it reaches the engine.** `EngineSemantic` takes a `soundEngine` option and
fix(sound): la invariante de un solo AudioContext, cerrada en attach y medida `PLAN-sound-engine` daba por cableada la propiedad del motor «en los dos modos de arranque». En attach no lo estaba: `defineUixServices` registraba `motion` y `scene` junto a `dom` pero NO `sound`, y `defineEngineSemantic` no reenviaba `soundEngine`. Una app en attach con sonido activo tenía el motor privado del canal MÁS `uix.sound` — dos motores, y dos contextos en cuanto el reproductor primase el suyo. `attachActiveUix` DOCUMENTABA el footgun en un comentario en vez de cerrarlo, mientras `contracts.ts` ya prometía `singleContextPerDocument`. - `defineUixServices` declara `sound` junto a `motion` / `scene`, y `defineEngineSemantic` lo toma como `serviceDependencies: ['dom', 'sound']` y lo reenvía. Degrada igual que `dom`: sin el servicio declarado llega `undefined` y el canal vuelve a crear el suyo (construcción directa y tests). - La nota de `attachActiveUix` pasa de describir el footgun a describir el cierre, y deja escrito el único caso que sigue vivo: un esquema de servicios a mano que declare `events` sin `sound`. - Fila `events` de `contracts.ts` con `serviceDependencies`, y el assert DERIVA de la tabla en vez de hardcodearla — que es lo que su propia doctrina pide. - El estudio de sema (`/temas/sema`) enchufa `uix.sound`. Era la única página capaz de ejercitar la inyección y no lo hacía. MEDIDO en Chrome, no deducido. Con el motor privado, cada edición del draft reconstruye el `EngineSemantic`, el `dispose()` del canal cierra el contexto y el siguiente disparo abre otro: 9 ediciones dejaron **10 contextos creados y 9 cerrados**, perdiendo además la caché de samples decodificados en cada una. Con el motor compartido: **1 contexto, 0 cierres**, y 24 osciladores construidos en 6 ciclos — el earcon sigue vivo. Dos guards, y los dos VISTOS FALLAR sin el cableado. Es la lección de la retirada de F2, donde una medición vacua se presentó como prueba: - `active-uix.svelte.test.ts`: el `events` de la app prima el motor de la app (quitando el reenvío: `expected null not to be null`). - `sound-e2e.test.ts`: un motor inyectado sobrevive a 3 reconstrucciones con UN contexto y sin `close()`; sólo lo cierra quien lo creó. Verificación: `client` 130/130 · 842/842 · `server` 262/264 · 3718/3722 — los 3 rojos son ajenos (`aura`, `menubar`, `radio-group`) y el cuarto fue un timeout de `soma-attr-audit` por contención, verde aislado en 3,1 s · `check` en la baseline exacta (73 errores, 0 propios). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
forwards it. Both boot modes fill it without the integrator doing anything:
standalone `ActiveUix` creates the engine and passes it in (exposing it as
`uix.sound`); in attach, `defineEngineSemantic()` declares `sound` as a service
docs(process): el motor cierra sus fases y abre 5 hallazgos; el player, al gate `PLAN-sound-engine` pasa a CERRADO (F0…F5) y `PLAN-audio-player` a DESBLOQUEADO. Pero cerrar las fases no es dejar el diseño incuestionado, así que el plan gana una auditoría propia que lo contradice donde toca. - **§15 — la invariante, cerrada y medida.** Incluye lo que la medición DESMINTIÓ: el handoff anterior predecía un aviso por consola en `/temas/sema` que no podía aparecer, porque el aviso cuenta contextos VIVOS y allí sólo había uno. Y **E-8**, el agujero de attach que E-1 dio por cableado sin estarlo. - **§16 — 5 hallazgos ABIERTOS sobre el art**, tres con decisión pendiente: `analyze.ts` sigue abriendo su contexto (medido 1 → 2 al importar un `.wav`); el aviso de segundo contexto es ciego a los contextos crudos; **S-1 NO está cerrado** y §7 afirmaba lo contrario —queda tachado en su sitio—; el canal tira en silencio `masterGain` y compañía cuando le inyectan el motor; y `gainScale`, que es de UNA reproducción, se escribe en el master compartido. - **§16.1 — la pregunta de fondo**: el corte separa módulos, **no conocimiento**. La quinta a 1.5×, el ADSR recortado a 15 %/40 %, el contour de ±400 cents y el mapeo de `roughness` son diseño sonoro calibrado contra el vocabulario de sema, viviendo dentro de lo que el plan llama «maquinaria». El precedente de `$motion` —datos en la capa, mecanismos en el art— no se replica tan de cerca como §3 afirma. Y la superficie no sirve a dos de los tres consumidores que la justificaban: `Waveform` y el analizador necesitan los `AudioBuffer`, y `preload(urls)` los esconde en una caché privada. - **`sema.md` corregido**: «sema no importa el art» era cierto para la INSTANCIA y falso para el grafo de módulos. - **`PLAN-audio-player` con las 5 correcciones de su cabecera incorporadas al cuerpo**, que era la condición para presentar nada: segmentos en la matriz §2 y en el contrato §7.5; **G-2 sube a bloqueante** (buffer, ventana de clip, capítulos); las dos invariantes de §7.6 (`prefs.sound` NUNCA toca el volumen del contenido; el silencio de UI debe alcanzar al `Slider` compuesto). **D-AP.11 escrita contra el art** —su premisa, «no hay motor», era falsa— y **D-AP.12** (segmentos `clip`) añadida; las dos existían como referencia sin fila en §4. **D-AP.7 reescrita** como ducking. - El handoff añade dos reglas que costaron caro: **un guard que no has visto fallar no vale**, y acoplarse al dev server vivo del usuario en vez de levantar otro. D-AP.1…D-AP.12 siguen SIN FIRMAR: el gate es lo primero de F0 y no se escribe una línea de código del player antes. `docs:check` deja 1 error ajeno (`blocks/banner/README.md:15`, «8 roles» por 9). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
dependency and forwards whatever `defineUixServices()` registered. The engine
arrives as a structural port, the `MotionDom` / `SceneDom` pattern — sema never
reaches for it. (**Precision**: the *instance* is injected, but the module graph
is not free of the art — `chans/sound.ts` imports `createEngineSound` as a value
for the private-engine fallback, so an app that builds an `EngineSemantic` still
bundles the synthesizer with sound off. See `PLAN-sound-engine.md` §16.)
Without an injected engine the channel creates a private one and owns
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
its lifecycle — *whoever creates, disposes*; a shared engine is never closed by
fix(sound): la invariante de un solo AudioContext, cerrada en attach y medida `PLAN-sound-engine` daba por cableada la propiedad del motor «en los dos modos de arranque». En attach no lo estaba: `defineUixServices` registraba `motion` y `scene` junto a `dom` pero NO `sound`, y `defineEngineSemantic` no reenviaba `soundEngine`. Una app en attach con sonido activo tenía el motor privado del canal MÁS `uix.sound` — dos motores, y dos contextos en cuanto el reproductor primase el suyo. `attachActiveUix` DOCUMENTABA el footgun en un comentario en vez de cerrarlo, mientras `contracts.ts` ya prometía `singleContextPerDocument`. - `defineUixServices` declara `sound` junto a `motion` / `scene`, y `defineEngineSemantic` lo toma como `serviceDependencies: ['dom', 'sound']` y lo reenvía. Degrada igual que `dom`: sin el servicio declarado llega `undefined` y el canal vuelve a crear el suyo (construcción directa y tests). - La nota de `attachActiveUix` pasa de describir el footgun a describir el cierre, y deja escrito el único caso que sigue vivo: un esquema de servicios a mano que declare `events` sin `sound`. - Fila `events` de `contracts.ts` con `serviceDependencies`, y el assert DERIVA de la tabla en vez de hardcodearla — que es lo que su propia doctrina pide. - El estudio de sema (`/temas/sema`) enchufa `uix.sound`. Era la única página capaz de ejercitar la inyección y no lo hacía. MEDIDO en Chrome, no deducido. Con el motor privado, cada edición del draft reconstruye el `EngineSemantic`, el `dispose()` del canal cierra el contexto y el siguiente disparo abre otro: 9 ediciones dejaron **10 contextos creados y 9 cerrados**, perdiendo además la caché de samples decodificados en cada una. Con el motor compartido: **1 contexto, 0 cierres**, y 24 osciladores construidos en 6 ciclos — el earcon sigue vivo. Dos guards, y los dos VISTOS FALLAR sin el cableado. Es la lección de la retirada de F2, donde una medición vacua se presentó como prueba: - `active-uix.svelte.test.ts`: el `events` de la app prima el motor de la app (quitando el reenvío: `expected null not to be null`). - `sound-e2e.test.ts`: un motor inyectado sobrevive a 3 reconstrucciones con UN contexto y sin `close()`; sólo lo cierra quien lo creó. Verificación: `client` 130/130 · 842/842 · `server` 262/264 · 3718/3722 — los 3 rojos son ajenos (`aura`, `menubar`, `radio-group`) y el cuarto fue un timeout de `soma-attr-audit` por contención, verde aislado en 3,1 s · `check` en la baseline exacta (73 errores, 0 propios). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
a consumer. That last rule is what lets a page rebuild its `EngineSemantic`
freely: the sema studio does it on every draft edit, and the context survives.
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
**Why one engine matters.** Browsers cap concurrent `AudioContext`s and the
autoplay unlock gesture is per-context, so a second context leaves one of the
two mute. An app that also plays content (a media player, a waveform) must take
`uix.sound` — or pass `soundEngine` to its own `EngineSemantic` — instead of
opening its own. The art warns when a second context goes live.
Behaviour, unchanged by the extraction:
- **Prepare-time priming**, no constructor side effect: the context is created +
resumed in `prepare()` when the signal admits `sound`, synchronously inside
the user gesture.
- The unlock listener (`pointerdown` / `mousedown` / `click` / `touchstart` /
`keydown`) is registered only AFTER the context exists, through the injected
DOM surface. An engine that never plays installs no global listeners.
- Synthesis: two oscillators (sine + a fifth) → biquad lowpass (centroid) →
fix(sound): la rugosidad pasa a ser un tremolo EN SERIE — deja de sumarse a la envolvente Conectar un nodo a un AudioParam SUMA a su automatizacion; no la escala. El modulador de AM colgaba de `envelope.gain`, asi que su profundidad se mantenia CONSTANTE mientras la envolvente se movia: el tremolo ni crecia con el ataque ni moria con la caida, y alli donde la automatizacion bajaba de la profundidad el parametro CRUZABA CERO — modulacion en anillo, no tremolo. MEDIDO con render offline del grafo real, viejo contra nuevo: caso cuerpo RMS cola PICO corte A commit neutral (control) 0.242 -> 0.242 0.149 -> 0.149 0.0002 (igual) B signal neutral 0.332 -> 0.275 0.145 -> 0.088 0.0666 -> 0.0002 C commit + risk 0.243 -> 0.200 0.263 -> 0.103 ~0 D commit.subtle + threat 0.143 -> 0.018 0.333 -> 0.009 ~0 - B: el clic del cierre de cada aviso/error desaparece (x333 menos amplitud en el corte). Era el tick audible al final de TODOS los earcons de `signal`. - C: la cola dejaba +76% de pico sobre el control; ahora muere por debajo, que es lo que una cola debe hacer. El sustain no se toca (ahi el tremolo ES el percepto de rugosidad buscado). - D: el caso grave. `commit.subtle` (gain 0.03) cargado con `threat` (roughness 0.5) salia a RMS 0.143 — SIETE VECES su nivel declarado, con inversiones de polaridad. Ahora sale a 0.018, su nivel de diseño. Escenario real: un item de menu destructivo con intent="threat". - A: el control sin AM no se mueve ni un digito. El fix no toca lo que no debe. LA FORMA. Etapa de ganancia en serie entre el filtro y la envolvente, con valor base `1 - depth` y el modulador sobre su propio `.gain`: el factor es `(1 - depth) + depth*sin`, que hace pico en 1 —el tremolo nunca ANADE nivel— y multiplica la envolvente, asi que silencio por cualquier cosa es silencio. `depth` clampeada a 0.5 para que `1 - 2*depth >= 0`: invertir polaridad es otro efecto. Y deja de escalarse por `gainScale`: siendo un ratio ya es relativa por construccion, que es justo lo que el comentario viejo perseguia a mano («scaling both keeps the timbre and only moves the volume»). CONTRAPARTIDA DECLARADA: los earcons con rugosidad bajan 1.6-2.7 dB de nivel medio (B: -1.6 dB de cuerpo, -2.7 dB de pico), porque el AM ya no suma nivel. Es correccion, no perdida — pero es un cambio perceptual real en la familia `signal` y en todo `risk`/`threat`. Si el autor quiere recalibrar los gains base, es una decision de catalogo aparte. GUARD. `engine-sound.test.ts` sustituye el test que fijaba la propiedad vieja (la profundidad escalada por gainScale) por el que fija la invariante que de verdad importa: NADA puede conectarse al AudioParam de la envolvente, y la etapa en serie vale `1 - depth`. Verificado en ROJO (al reconectar el modulador a `envelope.gain` falla con su mensaje). El fallo colateral que aparecio al hacerlo —el test de la voz desconocida avisando dos veces— era cascada: al fallar el assert no corre `dispose()`, el AudioContext queda vivo y el siguiente test recibe el aviso de «segundo contexto». Doctrina: `sema.md` (sintesis) y `arts/sound/README.md` (voces) describen ahora la etapa en serie y por que nunca se parchea un modulador sobre `envelope.gain`. VERIFICADO: sonido + sema 244/244 · check 76 = base 75 + 1 del fichero huerfano `__adv-verify-haptic-dead.test.ts` (sin trackear, sin borrar sin instruccion) · docs:check 0/616 · el dev server sirve el fix (comprobado por fetch del modulo) · prettier: los dos ficheros eran limpios en HEAD y quedan limpios. Cierra el hallazgo S-06 de AUDIT-sema-2026-08-05. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
`roughness > 0.2` inserts a tremolo stage → ADSR-lite envelope; `contour`
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
(`flat` / `ascending` / `descending` / `arc` / `bell`) rides `osc.detune`.
fix(sound): la rugosidad pasa a ser un tremolo EN SERIE — deja de sumarse a la envolvente Conectar un nodo a un AudioParam SUMA a su automatizacion; no la escala. El modulador de AM colgaba de `envelope.gain`, asi que su profundidad se mantenia CONSTANTE mientras la envolvente se movia: el tremolo ni crecia con el ataque ni moria con la caida, y alli donde la automatizacion bajaba de la profundidad el parametro CRUZABA CERO — modulacion en anillo, no tremolo. MEDIDO con render offline del grafo real, viejo contra nuevo: caso cuerpo RMS cola PICO corte A commit neutral (control) 0.242 -> 0.242 0.149 -> 0.149 0.0002 (igual) B signal neutral 0.332 -> 0.275 0.145 -> 0.088 0.0666 -> 0.0002 C commit + risk 0.243 -> 0.200 0.263 -> 0.103 ~0 D commit.subtle + threat 0.143 -> 0.018 0.333 -> 0.009 ~0 - B: el clic del cierre de cada aviso/error desaparece (x333 menos amplitud en el corte). Era el tick audible al final de TODOS los earcons de `signal`. - C: la cola dejaba +76% de pico sobre el control; ahora muere por debajo, que es lo que una cola debe hacer. El sustain no se toca (ahi el tremolo ES el percepto de rugosidad buscado). - D: el caso grave. `commit.subtle` (gain 0.03) cargado con `threat` (roughness 0.5) salia a RMS 0.143 — SIETE VECES su nivel declarado, con inversiones de polaridad. Ahora sale a 0.018, su nivel de diseño. Escenario real: un item de menu destructivo con intent="threat". - A: el control sin AM no se mueve ni un digito. El fix no toca lo que no debe. LA FORMA. Etapa de ganancia en serie entre el filtro y la envolvente, con valor base `1 - depth` y el modulador sobre su propio `.gain`: el factor es `(1 - depth) + depth*sin`, que hace pico en 1 —el tremolo nunca ANADE nivel— y multiplica la envolvente, asi que silencio por cualquier cosa es silencio. `depth` clampeada a 0.5 para que `1 - 2*depth >= 0`: invertir polaridad es otro efecto. Y deja de escalarse por `gainScale`: siendo un ratio ya es relativa por construccion, que es justo lo que el comentario viejo perseguia a mano («scaling both keeps the timbre and only moves the volume»). CONTRAPARTIDA DECLARADA: los earcons con rugosidad bajan 1.6-2.7 dB de nivel medio (B: -1.6 dB de cuerpo, -2.7 dB de pico), porque el AM ya no suma nivel. Es correccion, no perdida — pero es un cambio perceptual real en la familia `signal` y en todo `risk`/`threat`. Si el autor quiere recalibrar los gains base, es una decision de catalogo aparte. GUARD. `engine-sound.test.ts` sustituye el test que fijaba la propiedad vieja (la profundidad escalada por gainScale) por el que fija la invariante que de verdad importa: NADA puede conectarse al AudioParam de la envolvente, y la etapa en serie vale `1 - depth`. Verificado en ROJO (al reconectar el modulador a `envelope.gain` falla con su mensaje). El fallo colateral que aparecio al hacerlo —el test de la voz desconocida avisando dos veces— era cascada: al fallar el assert no corre `dispose()`, el AudioContext queda vivo y el siguiente test recibe el aviso de «segundo contexto». Doctrina: `sema.md` (sintesis) y `arts/sound/README.md` (voces) describen ahora la etapa en serie y por que nunca se parchea un modulador sobre `envelope.gain`. VERIFICADO: sonido + sema 244/244 · check 76 = base 75 + 1 del fichero huerfano `__adv-verify-haptic-dead.test.ts` (sin trackear, sin borrar sin instruccion) · docs:check 0/616 · el dev server sirve el fix (comprobado por fetch del modulo) · prettier: los dos ficheros eran limpios en HEAD y quedan limpios. Cierra el hallazgo S-06 de AUDIT-sema-2026-08-05. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
The tremolo is a gain node IN SERIES at `1 - depth`, modulated on its own
`.gain`, so the factor peaks at 1 and the trill scales WITH the envelope.
It used to be patched onto `envelope.gain`, where a connection ADDS to the
automation instead of scaling it: the depth stayed constant while the
envelope moved, closing every `signal` note on a non-zero sample (an audible
tick) and inflating a `commit.subtle` charged with `threat` to seven times
its declared level. Pinned by `engine-sound.test.ts`.
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
- A signature carrying `sampleUrl` plays the sample (with an `AudioBuffer`
cache) and **falls back to synthesis** on fetch / decode failure.
- Any failure is absorbed — sema is ornamental.
The prepare-time priming pattern applies in general to any channel whose
backend has a "first time must happen inside a gesture" restriction: audio,
vibration, fullscreen, clipboard write. Documented as **convention 12** in
[`guia-semantica-historica.md`](../decisions/guia-semantica-historica.md)
(historical seed).
### Error policy
- Errors in NON-visual channels are logged but never propagate. Sema is
ornamental: an audio-context or vibration-API failure must not abort the
provider's operation.
- If the visual channel throws, the `emit` Promise rejects. The caller
decides.
- In fire-and-forget (`void semantic.emit(...)`), a visual rejection
propagates as an unhandled promise — a conscious policy.
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
## Perceptual arbitration, reductions and accessibility (C-series, 2026-07-04)
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
Landed from the Sema↔book audit (`sema-findings.md`, a process registry since
removed from the tree); anchors in [`book-map.md`](../book-map.md). All
opt-outable, all covered by unit tests.
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 — `engine.frequencyMemory` (default on)
`BK-FREQ-MEMORY` (ch. 32 §11): _"la gramática debe tener memoria de frecuencia"_.
Repeated occurrences of the **same** event identity (`signal.name`, else
`family:intent`) attenuate their non-visual channels — `sound.gain` and
`haptic.intensity` — past a small threshold, down to a floor, so the 50th
autosave doesn't sound like the first. `threat` is exempt; the visual hold is
never touched. A window of silence resets the key to full. Distinct from the
pointer-rate throttle (that samples ONE continuous gesture; this damps _repeated_
discrete signals). Disable with `frequencyMemory: false`.
### Dominance arbiter — `engine.dominance` (default on)
`BK-DOMINANCE` (ch. 30 §7): when signals overlap, the engine ranks each active
occurrence (evaluable `commit`/`signal` > structural; higher activation from
intent; recency breaks ties for the newcomer) and **mutes the non-visual
channels** of an incoming signal a still-active occurrence out-ranks — "lo
evaluable calla a lo estructural". `threat` is never muted; the visual hold and
the `announce` channel survive (a dominated event still reads structurally and
remains accessible). Occurrences stay active for a short overlap window. Disable
with `dominance: false`.
### Announce channel — `engine.announce` (opt-in)
`BK-SIGNAL-A11Y` / `BK-A11Y-CRITICAL`: the accessible live region is now a
first-class **channel** (`AnnounceChannel`, id `announce`), opt-in like sound /
haptic. It reads the human-facing text from `signal.message` (never inferred from
the technical event name), derives priority from intent (`threat`/`loss` →
`assertive`, else `polite`), and materializes it either through an injected
announcer (`{ announce }`, e.g. `ActiveUix.announce`, one shared region) or a
self-owned pair of live regions built from the injected `dom`. Fire-and-forget,
never rejects. Supersedes `book-deviations.md` D.8's deferral.
**Live-region doctrine — who owns what (AUX-1 / SEM-1, 2026-07-11).** The
framework has ONE sink, two emitters, and an app-level component:
- `uix.announce` (ActiveUix) owns the SHARED pair of live regions — the
low-level sink (its zwsp-toggle re-announce is an implementation detail of
that sink, not a third policy).
- The two framework **emitters** both prefer delegating to it: the soma
runtime's a11y commitments (`a11ySemantic.requiresLiveRegion` →
docs(sema): la doctrina deja de mentir — deriva propia, copias podridas y dos leyes muertas Pasada de saneamiento documental tras los tres commits de sonido, con el informe de AUDIT-sema-2026-08-05 como mapa. Tres clases de defecto. 1) DERIVA QUE YO MISMO DEJE (lo mas urgente) El 05 renombre las claves silenciadoras a `commit.silent` / `emerge.silent` y el 06 las elimine del catalogo — dejando media docena de docs citando claves que ya no existen, y afirmando ademas comportamientos que ya no ocurren: - switch / toggle / toggle-group README: decian «silent-by-default (`commit.silent`)». Hoy es `commit.medium` (gain 0.1) y suenan. Corregidos con el nivel real y su razon (un tercio de una pulsacion de boton). - tooltip README + docblock del pack: decian que el tuning «resta el gain de la familia, asi que neutral emite a 0» y que «threat / fulfill siguen aflorando». Ambas cosas son falsas desde el 06: es `SILENT`, el resolver retira el canal y NADA aflora, porque no queda ganancia que subir. - toggle-group apuntaba a `LIBRO_VARIACIONES_Y_EXTENSIONES.md`, que es un stub movido; ahora apunta a book-deviations. - La nota del renombrado en D.5 decia «hoy es `commit.silent`»: una clave que vivio UN DIA. Marcada como tal, con la lista de las que siguen vivas. 2) COPIAS PODRIDAS — la ley del corpus es enlazar, no copiar, y estas dos entradas la incumplian - D.9 transcribia `SEMA_HOLDS_BY_INTENT` entera y se habia quedado atras: D.12 corrigio DOS valores el 2026-07-06 (`commit.fulfill` noticed→settled, `signal.loss` noticed→brief, ambos contra el texto del libro) y la copia siguio afirmando los viejos un mes. Sustituida por el puntero a holds.ts + el enumerado generado, con la leccion escrita en el sitio. - sema.md transcribia `SEMA_VERBS` y le faltaban DOS verbos vivos: `commit.unselect` y `handle.zoom`. Retirada; queda el puntero a verbs.ts y a vocabularies.md, que si se genera y tiene guard de frescura. 3) LEYES QUE LA PRACTICA YA HABIA DEROGADO, Y NADIE REGISTRO - El contrato `emit` publicaba `Promise<void>`; devuelve `Promise<string>` desde D.9. Corregido, y explicado que ese string es el id de la ocurrencia — el unico asidero para cerrar una senal persistente con `clear`. - channels.md fijaba «3 canales runtime» y D.8 cerraba la puerta a Announce («hoy no»). El `AnnounceChannel` existe desde el 2026-07-04: built-in, opt-in y exportado. channels.md pasa a 4 con la distincion que importa (visual/sound/haptic EXPRESAN; announce SUSTITUYE) y D.8 queda marcada PARCIALMENTE SUPERSEDED. - sema.md prescribia `{ announce: uix.announce }` — y eso NO COMPILA: las dos firmas no casan (bolsa de opciones vs posicional). Ahora ensena el adaptador de una linea que si compila, y declara que ninguna raiz cablea el canal hoy, asi que activarlo cae en el fallback que anade un SEGUNDO par de live regions. El arreglo de codigo queda sin tomar: es decision de diseno. - La cabecera de engine-sound.ts y la fila de arts/README seguian afirmando como MECANISMO que «`prefs.sound` mapea al bus ui», que la auditoria AU-4 ya habia corregido en el README del propio arte: lo garantizado por construccion es el NEGATIVO (ninguna politica de UI escribe el bus content). AMBIGUEDADES QUE NO RESUELVO PORQUE SON TUYAS, pero que dejan de estar escondidas: D.7 prohibe canonizar samples fuera de `signal` y `proof-of-human` los usa en dos eventos `commit`, aplastando la modulacion por intent que esa misma entrada existe para proteger —y el comentario del pack afirma literalmente lo contrario de lo que hace—; y D.8 declara que los packs deben respetar el `activeChannels` de la familia mientras sema.md prescribe lo contrario y el pack de dialog deja dos reglas inertes. Ambas quedan marcadas con su estado real y los dos caminos excluyentes, pendientes de tu firma. VERIFICADO: docs:check 0/616 · sonido + sema 244/244 · cero referencias vivas a las tres claves muertas (las que quedan en book-deviations son historia declarada como tal, y las de cronica no se reescriben) · prettier: los 6 docs con avisos ya estaban sucios en HEAD, no los toco. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
`sources.announce`) and this channel; its self-owned clear-then-set regions
are ONLY the no-uix fallback.
⚠️ **The two signatures do not meet, so the delegation needs an adapter.**
`AnnounceFn` takes the priority in an options bag and `uix.announce` takes it
positionally, so the literal `{ announce: uix.announce }` this section used to
prescribe does not compile:
```ts
announce: (message, { priority }) => uix.announce(message, priority);
```
And **no composition root wires it today** — neither `createActiveUix` nor
`attachActiveUix` passes `announce` to the engine, so enabling the channel
currently lands on the self-owned fallback, which adds a SECOND pair of
`role=status` / `role=alert` regions next to the shared one. Fixing this is a
code decision (adapter at the root, or reshape `AnnounceFn`) that has not been
taken — audited 2026-08-05.
- The soma `<Announce>` component is different in kind: an app-authored,
declarative region for app-level messages — not a vehicle for framework
event announcements.
Priority is ONE policy everywhere (SEM-1 — the runtime used to add a
`family === 'signal'` conjunct, making a threat-tinted commit announce
polite on one path and assertive on the other): the evaluative INTENT alone
— `threat` / `loss` → `assertive`, else `polite` — implemented identically
in `priorityForIntent` (`chans/announce.ts`) and the runtime's a11y step
(`runtime.svelte.ts`), pinned by tests on both sides
(`announce.test.ts` · `runtime.svelte.test.ts` SEM-1).
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 reductions — `engine.preferences`
`BK-REDUCTIONS` (ch. 32 §12 / ch. 33 §7): a symmetric reduction story per
channel. `SemaPreferences { sound?, haptic?: 'full' | 'reduce' | 'off' }` is read
at dispatch (back it with reactive state to make it live). Sound gains a
reduce/off path (attenuate master gain / no-op) symmetric to haptic; haptic
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
_also_ keeps honoring `prefers-reduced-motion`. For sound, `off` governs the
NETWORK as well as the output: no sample is downloaded while it holds (see
`preloadSamples` above). Visual-motion reduction stays in
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
eidos CSS; when a channel is `off`, meaning migrates via the table below.
### Migration table — `sema/migration.ts`
`BK-A11Y-MIGRATION` (ch. 33): _"cuando un canal falla, migra el significado, no
el adorno"_ — as **data**, not just prose. `SEMA_MIGRATION` gives per-modality
fallback surfaces (motion → state/focus/text; color → shape/icon/text; sound →
presence/text/liveRegion; haptic → motion/shape/state); `SEMA_MIGRATION_BY_FAMILY`
refines the cases the book calls out (signal-sound → live region; commit-color →
footprint). `resolveFallback(modality, family?)` is the accessor a11y / eidos
consult.
### Frame-intent guardrail (morfo)
`BK-FRAME-NO-INTENT` (Apéndice A): a structural family (`contact` / `emerge` /
`shift` / `sustain` / `delegate`) that declares a non-neutral intent must justify
it with `MorfoEventSemantic.intentRationale`, or `validateMorfo` rejects it. This
makes the "appearance that is the warning" exception ([book-updates
A-1](../audit/book-updates.md)) explicit and auditable rather than a silent
loophole. `handle` is exempt (the book puts intent on `handle.drop`);
`commit`/`signal` require intent anyway.
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
## Continuous components (drag, swipe, hold)
Most events are discrete: one gesture, one emit, one hold. Direct-manipulation
components — Slider, Drawer, and any future knob / swipe surface — are
**continuous**: the pointer moves at sample rate (dozens to hundreds of events
per second) while the value updates the whole time. Emitting one perceptual
signal per pointer sample would flood the channels and desynchronise the hold.
The rules below keep a continuous gesture perceptually legible without breaking
the emit contract.
### One door: `runtime.trigger(eventName, opts?)`
There is no `emitEvent`. Providers reach the engine through a single method,
`runtime.trigger(name)`, which resolves the morfo's compiled action, applies
polymorphic overrides, runs the handler in the declared `sequence`, awaits the
perceptual emit, and honours the event's a11y commitments. It returns a
`Promise` that represents **the whole occurrence, including the visual hold** —
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
the same sequential-strict window described in _Promise semantics_.
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
That Promise is the control the caller uses to opt into or out of the hold:
- `void runtime.trigger('handle-drag')` — **fire-and-forget**. The signal
starts; the caller does not wait for the hold. Correct for high-frequency
emits, where blocking the gesture loop on a ~240ms hold would be absurd.
- `await runtime.trigger('close', …)` — **blocking**. The caller waits for the
hold to finish before its next step. Correct when a structural change must
observe the resolved signal (e.g. a `close` whose element unmounts after the
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
pulse — see the `sequence: 'post'` note under _Hold_).
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
Slider and Drawer go through `trigger` exclusively — `handle-pick`,
`handle-drag`, `commit-set` on the slider; `drag-start`, `drag-progress`,
`drag-end` on the drawer. Every continuous one is `void`.
### `coincident` vs `post` for a moving value
A continuous gesture has two temporally distinct moments, and the morfo encodes
each with its `sequence`:
- **The move** (`handle-drag`, `drag-progress`) is `sequence: 'coincident'` —
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
the perceptual signal and the value update are indivisible. The user _is_ the
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
value changing, so the signal fires alongside the mutation, neither
anticipating nor trailing it. (`coincident` is emit-then-handler, like `pre`;
the two are just declared indivisible.)
- **The commit** (`commit-set`) is `sequence: 'post'` — the handler settles the
resolved value first, then the signal celebrates it. The move is felt
continuously; the commit is felt once, after. (`post` is handler-then-emit;
see the `sequence` comment in `runtime.svelte.ts` and the overlay note under
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
_Hold_.)
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
Slider is the worked example: its morfo declares `handle-pick` / `handle-drag`
as `handle` · `coincident` and `commit-set` as `commit` · `post`.
### Throttle continuous pointer emits; leave keyboard alone
`onpointermove` fires far faster than the eye or ear resolves. Continuous emits
are therefore **coalesced to animation frames** and floored to a
component-tuned minimum interval — both via `ActiveDom`'s `requestFrame` (the
sanctioned rAF vehicle; never a raw `requestAnimationFrame` or `setTimeout`):
- Slider's `queueHandleDrag` stores the pending value, schedules one
`requestFrame`, and inside it skips the emit when less than
`SliderProvider.DRAG_SIGNAL_MS` has elapsed since the last signal.
- Drawer's `flushDragProgress` does the same with `DRAG_PROGRESS_SIGNAL_MS`,
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
deliberately set _below_ rAF cadence (sparser than one signal per frame).
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
The concrete millisecond floors are perceptual tuning, not architecture — they
live at those constants in the providers, never copied here.
Keyboard is the opposite case and is **not** throttled: Slider's `onkeydown`
calls `commitValue()` → `trigger('commit-set')` on every arrow / Page / Home /
End press. OS key-repeat is already human-paced and every repeat is a discrete
committed value, so one emit per keydown is correct. Throttling is a property of
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
_sub-perceptual pointer sampling_, not of continuous components in general.
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
### The per-emit payload owns the primitives; the cascade only adds character
`handle-drag` carries a dynamic sound payload — pitch / gain / contour derived
from pointer position and velocity — passed per-emit through `trigger`'s
`overrides`. For that live payload to survive frame after frame, the
component's sema pack for the continuous event sets **only**
`channels: ['sound', 'haptic']` and overrides nothing else
(`sema/components/slider.ts`).
> A cascade rule for a continuous event MUST NOT set `pitch` / `gain` /
> `contour` (nor per-intent haptic `intensity`). Those are exactly what the
> per-emit `overrides` control; a rule that also set them would clobber the live
> payload on every frame and flatten the gesture to a constant tone. The rule
> enables the channels and adds character — it never fixes the evaluative
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
> primitives. (Same division as _Override layers_ and the canon rule "cascade
docs(sema): add "Continuous components" doctrine section (STUMBLES S2) The emit contract explains discrete events (one gesture → one emit → one hold) but says nothing about direct-manipulation surfaces (Slider, Drawer, future knob/swipe) where the pointer moves at sample rate while the value updates continuously. Add a `## Continuous components (drag, swipe, hold)` section after the emit contract, grounded entirely in the real Slider/Drawer code (adversarially fact-checked, zero discrepancies): - One door: `runtime.trigger()` is the sole path (there is no `emitEvent`). Its Promise represents the whole occurrence incl. the visual hold — so `void trigger()` is fire-and-forget (correct for high-frequency emits) and `await trigger()` blocks until the hold ends (correct when a structural change must observe the resolved signal, e.g. `close`). - `coincident` vs `post` for a moving value: the move (handle-drag, drag-progress) is `coincident` (signal + mutation indivisible); the commit (commit-set) is `post` (handler settles, then the signal celebrates). Slider is the worked example. - Throttle continuous POINTER emits, not keyboard: pointer emits are coalesced to animation frames + floored to a component-tuned interval via ActiveDom's requestFrame (Slider DRAG_SIGNAL_MS, Drawer DRAG_PROGRESS_SIGNAL_MS); keyboard commit-set fires once per keydown (already human-paced). Tuning ms point to the code, never hard-coded here. - The per-emit `overrides` payload owns pitch/gain/contour; a continuous event's cascade rule sets only `channels` and MUST NOT override those primitives, or it would clobber the live payload every frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
> rules add character, never the intent's evaluative profile".)
## The per-family intent policy — `SEMA_FAMILY_POLICY`
> **Canonical:** [`CANON.md`](../CANON.md) §4 is the single source for this
> policy. The shape below mirrors `SEMA_FAMILY_POLICY` in `types.ts`; if they
> ever disagree, the code + canon win.
The doctrine on when intent is mandatory lives in a const in
`src/uix/sema/types.ts`. Each family declares **two independent axes**:
```ts
export const SEMA_FAMILY_POLICY = {
contact: { intentRequirement: 'optional', intentGuidance: 'discouraged' },
commit: { intentRequirement: 'required', intentGuidance: 'expected' },
signal: { intentRequirement: 'required', intentGuidance: 'expected' },
handle: { intentRequirement: 'optional', intentGuidance: 'contextual' },
emerge: { intentRequirement: 'optional', intentGuidance: 'contextual' },
shift: { intentRequirement: 'optional', intentGuidance: 'contextual' },
sustain: { intentRequirement: 'optional', intentGuidance: 'contextual' },
delegate: { intentRequirement: 'optional', intentGuidance: 'contextual' }
} as const;
```
- **`intentRequirement`** (`'required' | 'optional' | 'forbidden'`) — the
compile-time gate that shapes the discriminated union. `'required'` (only
`commit` and `signal`) → `intent` is MANDATORY in `MorfoEventSemantic`.
`'forbidden'` is reserved; no family uses it today.
- **`intentGuidance`** (`'expected' | 'contextual' | 'discouraged'`) — a
doctrinal hint with no type effect. `commit`/`signal` are `'expected'`;
`contact` is `'discouraged'` (book ch. 22 §11: "strong intent should not
live in the contact"); the rest are `'contextual'`.
**How it is enforced:**
1. **Compile time** — `SemaEvent` and `MorfoEventSemantic` are discriminated
unions derived from the `intentRequirement` axis. Flipping a family from
`'optional'` to `'required'` forces every morfo of that family to declare
an intent or fail the typecheck.
2. **Runtime** — `validateSemaEvent` throws when an event of a family with
`intentRequirement: 'required'` is built without intent (a defense against
malformed morfos or external inputs).
**Why this policy replaced the valenced/transitional split:**
The original canonical doctrine assumed only valenced families (contact,
commit, signal, handle) could declare intent. Transitional ones (emerge,
shift, sustain) were intent-less by definition. UX reality contradicted it: a
Dialog opening to confirm a destructive delete carries threat in its very
appearance. The policy distinguishes by the practical NEED for intent, not by
taxonomic category.
The valenced/transitional distinction still exists as a classification, but
it no longer dictates the intent rules — the policy does.
## The canonical verb vocabulary (`SEMA_VERBS`)
> **Canonical:** [`CANON.md`](../CANON.md) §6 +
> [`verbs.ts`](../../src/uix/sema/verbs.ts) are the source of truth for the
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
> verb vocabulary. This section documents how sema _consumes_ it (validation,
> naming shapes).
Cross-component action verbs grouped by family. The canon lives in
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
[`verbs.ts`](../../src/uix/sema/verbs.ts) and reflects the book _Diseñando lo
que ocurre_ ch. 22–29 (families) + ch. 10 (intents).
`morfo.events[].semantic.verb` MUST be in this vocabulary;
`morfo.events[].name` should follow the `{family}-{verb}[-{variant}]` shape
so sema/sound/haptic can subscribe by verb and eidos can write transversal
selectors (`[data-event^="dismiss"]`).
docs(sema): la doctrina deja de mentir — deriva propia, copias podridas y dos leyes muertas Pasada de saneamiento documental tras los tres commits de sonido, con el informe de AUDIT-sema-2026-08-05 como mapa. Tres clases de defecto. 1) DERIVA QUE YO MISMO DEJE (lo mas urgente) El 05 renombre las claves silenciadoras a `commit.silent` / `emerge.silent` y el 06 las elimine del catalogo — dejando media docena de docs citando claves que ya no existen, y afirmando ademas comportamientos que ya no ocurren: - switch / toggle / toggle-group README: decian «silent-by-default (`commit.silent`)». Hoy es `commit.medium` (gain 0.1) y suenan. Corregidos con el nivel real y su razon (un tercio de una pulsacion de boton). - tooltip README + docblock del pack: decian que el tuning «resta el gain de la familia, asi que neutral emite a 0» y que «threat / fulfill siguen aflorando». Ambas cosas son falsas desde el 06: es `SILENT`, el resolver retira el canal y NADA aflora, porque no queda ganancia que subir. - toggle-group apuntaba a `LIBRO_VARIACIONES_Y_EXTENSIONES.md`, que es un stub movido; ahora apunta a book-deviations. - La nota del renombrado en D.5 decia «hoy es `commit.silent`»: una clave que vivio UN DIA. Marcada como tal, con la lista de las que siguen vivas. 2) COPIAS PODRIDAS — la ley del corpus es enlazar, no copiar, y estas dos entradas la incumplian - D.9 transcribia `SEMA_HOLDS_BY_INTENT` entera y se habia quedado atras: D.12 corrigio DOS valores el 2026-07-06 (`commit.fulfill` noticed→settled, `signal.loss` noticed→brief, ambos contra el texto del libro) y la copia siguio afirmando los viejos un mes. Sustituida por el puntero a holds.ts + el enumerado generado, con la leccion escrita en el sitio. - sema.md transcribia `SEMA_VERBS` y le faltaban DOS verbos vivos: `commit.unselect` y `handle.zoom`. Retirada; queda el puntero a verbs.ts y a vocabularies.md, que si se genera y tiene guard de frescura. 3) LEYES QUE LA PRACTICA YA HABIA DEROGADO, Y NADIE REGISTRO - El contrato `emit` publicaba `Promise<void>`; devuelve `Promise<string>` desde D.9. Corregido, y explicado que ese string es el id de la ocurrencia — el unico asidero para cerrar una senal persistente con `clear`. - channels.md fijaba «3 canales runtime» y D.8 cerraba la puerta a Announce («hoy no»). El `AnnounceChannel` existe desde el 2026-07-04: built-in, opt-in y exportado. channels.md pasa a 4 con la distincion que importa (visual/sound/haptic EXPRESAN; announce SUSTITUYE) y D.8 queda marcada PARCIALMENTE SUPERSEDED. - sema.md prescribia `{ announce: uix.announce }` — y eso NO COMPILA: las dos firmas no casan (bolsa de opciones vs posicional). Ahora ensena el adaptador de una linea que si compila, y declara que ninguna raiz cablea el canal hoy, asi que activarlo cae en el fallback que anade un SEGUNDO par de live regions. El arreglo de codigo queda sin tomar: es decision de diseno. - La cabecera de engine-sound.ts y la fila de arts/README seguian afirmando como MECANISMO que «`prefs.sound` mapea al bus ui», que la auditoria AU-4 ya habia corregido en el README del propio arte: lo garantizado por construccion es el NEGATIVO (ninguna politica de UI escribe el bus content). AMBIGUEDADES QUE NO RESUELVO PORQUE SON TUYAS, pero que dejan de estar escondidas: D.7 prohibe canonizar samples fuera de `signal` y `proof-of-human` los usa en dos eventos `commit`, aplastando la modulacion por intent que esa misma entrada existe para proteger —y el comentario del pack afirma literalmente lo contrario de lo que hace—; y D.8 declara que los packs deben respetar el `activeChannels` de la familia mientras sema.md prescribe lo contrario y el pack de dialog deja dos reglas inertes. Ambas quedan marcadas con su estado real y los dos caminos excluyentes, pendientes de tu firma. VERIFICADO: docs:check 0/616 · sonido + sema 244/244 · cero referencias vivas a las tres claves muertas (las que quedan en book-deviations son historia declarada como tal, y las de cronica no se reescriben) · prettier: los 6 docs con avisos ya estaban sucios en HEAD, no los toco. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 months ago
> **The literal table used to live here, and it rotted.** It was missing
> `commit.unselect` and `handle.zoom` — both live in `verbs.ts` and both used
> by real morfos. A copy of a closed set is a copy that goes stale; the
> generated enumeration in
> [`canon/vocabularies.md`](../canon/vocabularies.md#sema-verbs-by-family)
> is regenerated from the const by `npm run docs:vocabularies` and guarded for
> freshness by `npm run docs:check`. Removed 2026-08-06.
**Verbs that look like one family but belong to another** per the canon:
`select`/`toggle`/`acknowledge` are `commit` (they fix state); `edit` is
expressed as `shift.enter-mode` (it changes the regime — there is no `edit`
verb in handle).
Defined in [`verbs.ts:SEMA_VERBS`](../../src/uix/sema/verbs.ts).
### Naming shapes
A `morfo.events[].name` can take two canonical shapes:
```ts
// Shape 1: {verb}-{variant} — head is the verb, tail explains the nuance.
'dismiss'; // bare verb
'dismiss-outside'; // verb + variant
'close-cancel'; // verb (close) + variant (cancel)
// Shape 2: {family}-{verb} — head is the family, tail the canonical verb.
'commit-toggle'; // family=commit, verb=toggle
'commit-save'; // family=commit, verb=save
```
`validateEventName(name)` recognizes both shapes and returns `{ family,
verb, variant, matchesCanonical }`. Used by
`scripts/morfo-vocabulary-check.ts` (`npm run morfo:vocabulary`), which
hard-fails when a morfo's declared `semantic.verb` is not in its family's
canon, and soft-warns when the event NAME doesn't fit
`{family}-{verb}[-{variant}]` even though the verb is canonical. A temporary
allowlist in the script covers deliberate divergences.
```ts
import { validateEventName } from '$uix/sema';
validateEventName('commit-toggle');
// { name: 'commit-toggle', head: 'commit', matchesCanonical: true,
// variant: 'toggle', family: 'commit', verb: 'toggle' }
validateEventName('dismiss-outside');
// { name: 'dismiss-outside', head: 'dismiss', matchesCanonical: true,
// variant: 'outside', family: 'emerge', verb: 'dismiss' }
validateEventName('frob-glob');
// { name: 'frob-glob', head: 'frob', matchesCanonical: false,
// variant: 'glob', family: undefined, verb: undefined }
```
## Relationship with Morfo and Soma
- `Morfo` declares the component's semantic events in `morfo.events`
- `Provider` decides when they occur and calls `semantic.emit(...)`
- `SomaRuntime` orchestrates the `prewrite -> emit -> handler -> effects`
sequence
- `Sema` contributes the vocabulary, normalization and domain validation, and
publishes the occurrences
## Dependencies
- `EngineSemantic` writes no attributes directly. It orchestrates channel
hooks (`prepare`, `handle`, `cleanup`) without knowing the DOM attrs.
- Each channel manages its own modality:
- `VisualChannel.prepare()` projects `data-event-*` and then holds the
perceptual window.
- `DomSignalProjector` is the DOM writer used by the visual channel; it
writes through the `ActiveDom` received from `ActiveUix`.
- Other channels (sound, haptic) access their respective APIs
(`AudioContext`, `navigator.vibrate`, etc.).
- In normal use, `ActiveUix` injects the `ActiveDom` into `EngineSemantic`.
Using Sema directly outside `ActiveUix` must pass an explicit
`dom/projector` or choose a documented degradation.
## Architecture rule
`Morfo` authorizes the component's semantics.
`Sema` defines the canonical vocabulary and dispatches signals to channels.
`Provider` decides when to emit.
Each `Channel` materializes the signal in its modality.
See [`architecture/overview.md`](./overview.md) §2.bis for the cross-layer
view. The original Spanish API conventions (single-event for instantaneous
operations, intent ↔ visual token resolution, sound prepare-time priming)
survive as a historical seed in
[`guia-semantica-historica.md`](../decisions/guia-semantica-historica.md);
the ruling vocabulary is [`CANON.md`](../CANON.md).

Powered by TurnKey Linux.