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/theming/channels.md

269 lines
14 KiB

---
title: The channels as one system — synthesis
type: notes
audience: human + agent
authority: E3 — the capstone tying the per-channel RFCs to the book's thesis
status: current
source: migrated from src/uix/eidos/CHANNELS_SYNTHESIS.md (2026-07-02, docs-book F7.3)
---
# The channels as one system — synthesis ("Diseñando lo que ocurre")
> The **capstone** of the per-channel RFCs. The thesis of the book *Diseñando
> lo que ocurre*: an interaction **is not** color, or movement, or sound — it
> is **one perceptual occurrence spread across N channels**, under the
> **two-moment** model. This ties together [`rfc-color-engine`](../rfcs/rfc-color-engine.md) ·
> [`rfc-typography`](../rfcs/rfc-typography.md) · [`rfc-depth`](../rfcs/rfc-depth.md) ·
> [`rfc-shape`](../rfcs/rfc-shape.md) ·
> [`rfc-structure`](../rfcs/rfc-structure.md). Live demo: **`/temas/orquesta`** (the mixer).
## 1. Two moments, joined by a token: sema emits, eidos reads
An occurrence flows through **two moments**, connected by the **token**:
- **Sema's moment (emission)** — sema evaluates the occurrence and **emits**
it. Sound + haptics it **executes right there** (runtime channels); for the
visual, it **stamps it as tokens** `data-event-*` (family · intent ·
direction · phase). Sema **knows no DOM/CSS**.
- **Eidos's moment (materialization)** — eidos **reads** those tokens
(+ `data-state`) and **materializes** them in CSS (the visual channel). It
is the **sole visual owner**.
The **token is the contract**: sema writes, eidos reads — which is why the
layers decouple (sema DOM-agnostic, eidos free of semantic logic).
Over that producer → consumer axis runs the **temporal** axis (motion F1) —
*which* token:
| Token | Nature | Who writes it | Eidos reads it as |
|---|---|---|---|
| `data-state` | persistent — what the element **is** | soma / morfo | `presets` |
| `data-event-*` | transient — what **occurs** (during the `hold`) | sema (emits) | `signatures` |
`sequence` (`pre` / `coincident` / `post`) orders the two.
## 2. Expression channels (the book) vs sema channels (runtime)
The book has **8 expression channels** (perceptual dimensions). The framework
implements them with sema's **runtime channels**, which are **4** — and here
is the key that avoids the classic confusion:
| Sema channel (runtime) | Covers (the book's expression) | Who materializes it |
|---|---|---|
| **visual** (sema projects `data-event-*`) | movement · presence · depth · shape · color | **eidos** — CSS over `data-event-*` + `data-state` |
| **sound** | sound | sema (`chans/sound`) |
| **haptic** | haptics | sema (`chans/haptic`) |
| **announce** | — (accessible substitute, not a book channel) | sema (`chans/announce`) |
`announce` joined on 2026-07-04 as a built-in opt-in channel: it is the
accessible counterpart the book demands when a modality is unavailable
(`BK-SIGNAL-A11Y`), not a ninth perceptual dimension. `visual`, `sound` and
`haptic` are the three that express; `announce` is the one that substitutes.
> **Don't get confused**: **motion · depth · shape · color are NOT "eidos
> channels"** — they are sema's **`visual` channel**, which sema projects and
> **eidos materializes**. Eidos is the visual channel's *materializer*, not
> the owner of channels of its own. The eidos RFCs (`COLOR/DEPTH/SHAPE_ENGINE`)
> describe *how* eidos materializes each visual facet — not independent
> channels.
Plus **space** ([`rfc-structure.md`](../rfcs/rfc-structure.md)) — **structural,
not expressive**: it doesn't "occur", it is the stage (state-only).
## 3. The layer chain (each with its role)
An occurrence **crosses the layers** — it is not "all sema", and eidos is not
a shim:
1. **morfo** — **declares** the event and its semantics (family · intent ·
verb). The contract / DNA; pure TypeScript, no runtime.
2. **soma** — **fires** it (`runtime.trigger`): the sequence `prewrite →
sema.emit → handler →` state attrs. It writes `data-state` (the
state-moment).
3. **sema** — **emits** it: dispatches the signal to its channels —
**executes** `sound` + `haptic`; **projects** the visual channel by
stamping `data-event-*`. It knows no DOM/CSS.
4. **eidos** — **materializes** it: reads `data-state` (`presets`) +
`data-event-*` (`signatures`) and renders them with its **token engines**
(color · motion · depth · shape · space — the RFCs). It is the **complete
visual system** and the sole owner of the visual.
> Eidos does **not "make sema visible"**: sema contributes the **what**
> (family / semantic intent), eidos contributes the **how** (the visual
> vocabulary and its materialization). Co-layers, not one subordinate.
The split is load-bearing, and the `shift` repair of 2026-08-11 is the case
that shows it. `shift` had a sound (`slide`) and no visual signature at all —
the *shift invisible* antipattern (book ch. 34 §14) inside the framework that
names it. The token was never the problem: sema was already stamping
`data-event-family='shift'`. What was missing was on eidos's side of the line,
and the fix stayed there — two entries in `BUILTIN_SIGNATURES`. Sema's only
addition was a new token, `data-event-direction` (`forward` | `backward`), the
SENSE of the crossing; eidos is what decides that a sense means the inline axis
and flips it under `:dir(rtl)`. Sema still knows nothing about left and right,
which is exactly the property that lets it stay DOM-agnostic.
What `shift` has now are two signatures keyed on that token — `shift-forward`
and `shift-backward` (they name the `shift-cross-*` keyframes): the frame
arrives displaced one `--motion-distance-xl` (30 px) along the **inline** axis
and settles in 320 ms on the `emphasized` curve. An emission that stamps no
direction matches neither and stays visually silent — deliberate, since there
is no neutral sense of travel to draw. Geometry, the `--motion-shift-sign`
pair and the selector shape: [`motion.md`](./motion.md) §8.
And the misreading that delayed the repair, because the structure invites it:
`SEMA_MAP.families.shift.activeChannels` is `['sound']`, which looks like a
family declared mute everywhere else. It is not. **"Mute by doctrine" is a
statement about a CHANNEL, never about a family** — `activeChannels` governs
only the two channels sema *executes*. `delegate` is the proof: its
`activeChannels` is literally `[]`, and it still carries two visual signatures
(`delegate-return-fulfill`, `delegate-return-loss`). No field of `SEMA_MAP`
could say otherwise, because eidos is the sole visual owner (§1) — the visual
channel is not in the map to be switched off, which is also why motion does not
live there. Read an empty `activeChannels` as "silent", and `shift` files
itself beside `sustain`, whose continuity is genuinely carried by persistent
state and not by a firma.
(The full canonical narrative lives in `CLAUDE.md` → "Sema: open channel
registry".)
## 4. The composition — one event, N channels
The **signature** (`BUILTIN_SIGNATURES` + `BUILTIN_KEYFRAMES`) is
**cross-modal**: a single keyframe carries several modalities. A real example
(`press-squeeze`, family `contact`):
```
contact · press → scale 0.96 (motion)
+ box-shadow → flat (depth recede)
+ --shape-smoothing 2→3 (shape: the corner firms up)
+ tick (sema sound)
+ vibration (sema haptic)
```
One `engine.emit(...)` stamps `data-event-*` (the **visual** channel reacts —
eidos materializes it as color/motion/depth/shape) **and** fires sound +
haptic — from the **same** event. There are no systems coordinating by hand;
there is one occurrence expressing itself through sema's channels.
## 5. Runtime builders — the sextet (an open cage)
Every axis retunable at runtime, same pattern (seed → managed block), opt-in
over the authored scale:
`applyColorScheme` · `applyTypeScale` · `applyDepth` · `applyShape` ·
`applySpacing` · `applyGradients`
And the **capstone composing them**: **`applyTheme(seed)`** — a single seed
(`{ color?, type?, depth?, shape?, space?, gradient? }`) composes the six
axes in **one managed write** (vs six loose `apply*`), **atomically**: the
axes you pass are applied, the ones you omit revert to the authored
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
`tick` the way it ships its own colour ramp.
**The gap, and its closing (2026-08-06).** The table above used to have a
second failure hiding behind the first: not only was there no `sound` axis in
the seed, the two halves had different SHAPES. Eidos retunes live, atomically,
revertibly; sema read `overrides.runtime` ONCE in the engine constructor and
never again. A single `applyTheme` built over that would have been worse than
two honest doors — one call where the colour changes now and the sound changes
never, because it had already booted.
So the substrate was made symmetric first:
- **The catalogue moved INTO `SEMA_MAP`** (`families · intents · sounds`). It
is data, and the map is the structure a theme addresses. While it sat outside
as a module const, the VOCABULARY was the one perceptual axis a product could
not re-voice at any time.
- **`applyMap(seed)` / `applySounds(seed)` / `clearMap()`** — live and
revertible, rebuilt from the authored map so applying twice is applying once.
Same shape as every `applyX` / `clearX` on the visual side.
- **A bad path now THROWS** (`SemaConfigError`). It used to create the branch:
`families.commmit.…` left the real value untouched and grew a phantom beside
it, in silence (audit S-09). A theming API that swallows a typo is worse than
no API — the symptom is «the sound did not change», with nothing to point at.
What a theme can reach today, measured in `theming.test.ts`:
| Target | Path |
| --- | --- |
| a family's base | `families.commit.base.sound.gain` |
| an intent's delta | `intents.fulfill.deltas.sound.gain` |
| **what a NAME sounds like** | `sounds.tick.gain`, or `applySounds({ tick: { gain: 0.1 } })` |
**What stays closed on purpose**: the vocabulary. `SoundName` is
`keyof SOUND_CATALOGUE`, so a theme changes what a name sounds like and cannot
invent one no component could reference. The voice is open; the words are not.
**One door, and it is `applyTheme`.** The seed gained a seventh axis:
```ts
eidos.applyTheme({ color: '#3b5bdb', sound: { tick: { gain: 0.1 } } });
eidos.clearTheme(); // reverts all seven, visual and perceptual
```
It lives on `ActiveEidos` and not on the composition root, which corrects the
first reading of this section: **the dependency runs eidos → uix**, not the
other way. `ActiveEidos` already holds `uix` and drives it (it registers motion
presets through `uix.motion`); `ActiveUix` does not hold eidos, so a composed
theme could not live there without inverting the direction. An axis you pass is
applied, an axis you omit reverts — the same atomic reading the six visual axes
already had, now over seven. Without a `uix` (a headless render) the sound axis
is skipped and the visual ones still apply: a theme that half-applies beats one
that throws.
**And the vocabulary is complete**: the haptic `kind → pattern` table moved into
the map too (`haptics.success.pattern`), so no perceptual axis is left that a
theme cannot reach. Leaving haptic behind would have swapped one asymmetry for
another.
`masterGain` is applied as well: passing it to a shared engine used to be
dropped in silence (audit S-05 / S-32); it now reaches `engine.master.setGain`,
and the options that genuinely cannot apply to a shared engine are warned about
instead of vanishing.
## 6. Position vs the references
No reference system gathers the channels under **one semantic model**.
Material has shape morph + motion (ad-hoc, without integrated
sound/haptics); Apple, continuity + materials (platform-bound); everyone,
color. **UIX gathers them** — the visual channel (sema → eidos) + sound +
haptic (sema) — over the two-moment model, fired from one event, with
runtime builders and an open registry. That is the framework, not the sum of
its parts.
## 7. Demo
`/temas/orquesta` — the mixer: press a control and mute each facet to hear
its part. The **visual channel's** 4 facets materialize with eidos tokens
(toggleable so each can be muted one by one); **sound + haptic** are fired by
the real sema engine (`EngineSemantic.emit`, filtered channels). One event,
sema's channels.

Powered by TurnKey Linux.