From 1302f28a5d4b8bb960f6409bb373eb8f5a60c2ff Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 6 Aug 2026 15:55:54 +0200 Subject: [PATCH] docs(sema,theming): el orden real, como se autora cada canal, y el hueco del theming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/CANON.md | 23 +++++++++++++++++++++ docs/architecture/sema.md | 32 +++++++++++++++++++++++------ docs/theming/channels.md | 42 +++++++++++++++++++++++++++++++++++++++ src/uix/sema/engine.ts | 4 ++++ src/uix/sema/resolver.ts | 21 +++++++++++++++----- 5 files changed, 111 insertions(+), 11 deletions(-) diff --git a/docs/CANON.md b/docs/CANON.md index 190e3edb8..c26971667 100644 --- a/docs/CANON.md +++ b/docs/CANON.md @@ -220,6 +220,29 @@ The framework **splits these channels by owner** (decision recorded in - **eidos** materializes the visual channels (motion / presence / depth / shape / color) by reacting in CSS to `data-event-*` + the morfo's state attrs. +**How each channel is AUTHORED** — the owner split says who materializes a +channel; this says what a component actually WRITES, which is the part authors +get wrong: + +| Channel | The component writes | Where the values live | +| --- | --- | --- | +| visual (motion / color / presence / depth / shape) | nothing — it declares the EVENT; eidos reacts in CSS to `data-event-*` | eidos recipes + the token engines | +| `sound` | **one NAME**: `sound: 'soft'` — a key of `SOUNDS`, or `SILENT` | [`sound-names.ts`](../src/uix/sema/sound-names.ts), the single catalogue | +| `haptic` | a categorical `kind` (`tick` / `tap` / `pulse` / `success` / …) | `HapticChannel` maps kinds to patterns | + +The rule is the same in all three rows and it is the point: **a component says +WHAT occurs, never how loud, how bright or how long.** The sound row was the +exception until 2026-08-06 — packs carried an open bag of eight continuous +axes, 43 % of the rules said nothing but a volume, and 69 of them erased the +contour the intent had just set. It is now a word, enforced by the type. + +Adding a sound means **adding a NAME to the catalogue**, never widening a rule: +made once, in one file, where the next component reuses it. Detail and the +resolution order in [architecture/sema.md](./architecture/sema.md); what a +`.wav` can and cannot carry (the intent only moves its LEVEL — physics, not +policy) in [decisions/book-deviations.md](./decisions/book-deviations.md) D.7, +retired but kept for that measurement. + So the book's 8 channels are conceptual; at runtime they are owned by different layers. `hold` (the registration FLOOR — a minimum, never a ceiling: the expression completes before unstamping, capped only by the channel's diff --git a/docs/architecture/sema.md b/docs/architecture/sema.md index 9228fc53a..7a0945954 100644 --- a/docs/architecture/sema.md +++ b/docs/architecture/sema.md @@ -176,19 +176,37 @@ On every `emit`, the engine: ``` 1. FAMILY base — SEMA_MAP.families[signal.family].base sound / haptic + activeChannels + hold +1.5 THE SOUND — the catalogue NAME the morfo event and the cascade + select (sound-names.ts), in that relative order. + HOISTED above the intent — see below. 2. INTENT deltas — SEMA_MAP.intents[signal.intent] when present; numbers ADD by default — they are modifiers -3. MORFO overrides — signal.overrides + signal.channels +3. MORFO overrides — signal.overrides + signal.channels, minus its `sound` (numbers REPLACE by default — they are set values) 4. RUNTIME overrides — engineOpts.overrides.runtime (path-based globals; baked into the map in the constructor; numbers REPLACE) -5a. PACK cascade — engineOpts.components (per-component packs) +5a. PACK cascade — engineOpts.components (per-component packs), minus + their `sound`: only channels / haptic / hold land here 5b. APP cascade — engineOpts.overrides.cascade (appended after the packs; wins specificity ties by declaration order). CSS-like selectors against signal.target with the data-event-* already stamped; numbers REPLACE. ``` +**Why 1.5 exists, and why it is not a wart.** Everything that AUTHORS a sound +runs there, keeping its old relative precedence (morfo event first, then the +cascade, which still wins). Only its POSITION with respect to the intent +changed. Before 2026-08-06 the sound was authored at 5a/5b — after the intent +and in replace mode — so a component's chosen sound erased the evaluative +profile that had just been applied: a dialog with `threat` sounded exactly like +a neutral one. Three mechanisms existed to police that inversion (D.7, the S-07 +finding and a dedicated guard); all three are gone, because with the sound +underneath there is no way to express the inversion. + +Everything else keeps the CSS-like «last match wins» reading, because +`channels` / `haptic` / `hold` are not evaluative axes: the intent has nothing +to say about them that a later layer would be overruling. + 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`. @@ -343,10 +361,12 @@ canonized at the component-audit checkpoint (verdicts S3a/S11 — historical record in [`docs/audit/components/_veredictos.md`](../audit/components/_veredictos.md)): four legitimate stances, each with shipped exemplars — -1. **`'pack'`** — a cascade in `sema/components/{kebab}.ts` tunes the - signature per event. Justified when the pattern ADDS meaning beyond - family + intent deltas (date-field's composed tunings; menubar's in-place - note: "subtle commit + tap haptic for high-frequency top-level triggers"). +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. 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 diff --git a/docs/theming/channels.md b/docs/theming/channels.md index 317dde175..96bd2c17e 100644 --- a/docs/theming/channels.md +++ b/docs/theming/channels.md @@ -130,6 +130,48 @@ foundation. `clearTheme()` reverts everything. For surgical per-axis tweaks, the individual `apply*` remain. No reference system gathers the six perceptual axes under a single runtime theme builder. +## 5b. How each channel is AUTHORED — and the asymmetry that is left + +The sextet above retunes the **visual** channel at runtime. Sound and haptic +have no equivalent, and after the 2026-08-06 rework that gap is worth naming +precisely, because the reason it existed is gone. + +| Channel | Where a component AUTHORS it | Where a THEME retunes it | +| --- | --- | --- | +| visual — color · motion · depth · shape · space | eidos recipes, reacting in CSS to `data-event-*` | `applyTheme(seed)` + the six `apply*` (runtime, atomic) | +| **sound** | **names one entry of `SOUNDS`** (`src/uix/sema/sound-names.ts`) — a word, never a parameter | **nothing** — see below | +| **haptic** | a categorical `kind` (`tick` / `tap` / `pulse` / …) | **nothing** | + +**What the sound rework did NOT change.** Nothing in eidos. The visual channel +is projected by stamping `data-event-*`, and that path was untouched: no +recipe, no token, no selector moved. Eidos does not know sound exists and did +not need to. The rework changed WHERE the sound is authored (a catalogue +instead of 71 pack files) and WHEN it is applied (before the intent instead of +after it) — both entirely inside sema. + +**What it did change, and it is the useful part.** Until then, "theming the +sound" was not a coherent idea: 214 cascade rules authored 33 hand-written +signatures across 71 files, and there was no object to retune. Today there +is exactly one — sixteen names in one const — so a `sound` axis in `ThemeSeed` +would be the same shape as the other six, and a brand could ship its own +`soft` the way it ships its own colour ramp. + +**The measured surface today, for anyone who tries.** Three doors, none of +which reaches the catalogue: + +- `overrides.runtime` — path-based mutations of `SEMA_MAP`, so it reaches + family bases and intent deltas but **not** the catalogue, which is a module + const outside the map. It also swallows path typos in silence (audit S-09). +- `overrides.cascade` — app-level rules; the type stays open on purpose so an + app CAN author a raw signature. But it is per-selector, not a theme: you + restyle one occurrence, not the word `soft` everywhere. +- `masterGain` via `createActiveUix` — **discarded in silence** (audit S-05 / + S-32): the engine filters the key before the channel is built. + +So: **a product can silence sound, and can override one occurrence, but cannot +re-voice the system.** That is a real gap, it is now cheap to close, and it is +not closed — recorded here rather than left as folklore. + ## 6. Position vs the references No reference system gathers the channels under **one semantic model**. diff --git a/src/uix/sema/engine.ts b/src/uix/sema/engine.ts index 1608c7ffa..3dd41a3ab 100644 --- a/src/uix/sema/engine.ts +++ b/src/uix/sema/engine.ts @@ -12,6 +12,10 @@ * * Override layers (ver `resolver.ts`): * 1. family base — SEMA_MAP.families + * 1.5 the SOUND — the catalogue name selected by the morfo + * event and the cascade (sound-names.ts), + * applied BEFORE the intent so the + * evaluative profile always survives it * 2. intent deltas — SEMA_MAP.intents (any family with intent) * 3. morfo per-event — signal.overrides + signal.channels * 4. runtime path overrides — engineOpts.overrides.runtime diff --git a/src/uix/sema/resolver.ts b/src/uix/sema/resolver.ts index 187165d11..f10a74a7b 100644 --- a/src/uix/sema/resolver.ts +++ b/src/uix/sema/resolver.ts @@ -4,10 +4,19 @@ * * Resolution cascade (each layer overrides the previous): * - * 1. FAMILY base — `SEMA_MAP.families[signal.family].base` - * 2. INTENT deltas — `SEMA_MAP.intents[signal.intent]` when present - * 3. MORFO overrides — `signal.overrides` / `signal.channels` - * (from the morfo event, copied by SomaRuntime) + * 1. FAMILY base — `SEMA_MAP.families[signal.family].base` + * 1.5 THE SOUND — the NAME the morfo event and the cascade select, + * resolved through `sound-names.ts`. Hoisted ABOVE the + * intent on 2026-08-06: everything that AUTHORS a + * sound runs here, keeping its old relative precedence + * (morfo event first, then cascade), so the intent is + * always the last word on the evaluative axes. That is + * what made D.7 / S-07 inexpressible rather than + * merely forbidden. + * 2. INTENT deltas — `SEMA_MAP.intents[signal.intent]` when present + * 3. MORFO overrides — `signal.overrides` / `signal.channels` + * (from the morfo event, copied by SomaRuntime; + * its `sound` already ran at 1.5) * 4. RUNTIME overrides — `options.runtimeMap` already has * `engineOpts.overrides.runtime` baked in by the * engine via `applyMapOverrides()` at construction @@ -19,7 +28,9 @@ * against `signal.target` AFTER the engine has * stamped `data-event-*` on it (see `stamp.ts`). * Rules are flat: `{ selector, priority?, - * channels?, sound?, haptic? }`. The selector is + * channels?, sound?, haptic? }`, and their `sound` is + * a catalogue NAME applied at 1.5 — only `channels` / + * `haptic` / `hold` land here. The selector is * matched natively via `target.matches()` / * `target.closest()`. *