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/motion.md

802 lines
42 KiB

---
title: Eidos Motion — Design and Architecture
type: reference
audience: human + agent
authority: E1/E3 — the two-moment motion model and the engine architecture
status: current
source: migrated from src/uix/eidos/eidos-motion.md (2026-07-02, docs-book F7.3)
---
# Eidos Motion — Design and Architecture
> **Status: the two-moment model — F1–F7 implemented (2026-06-04).**
>
> This document describes Eidos's animation system. The model, anchored in
> the canon (the historical seed `GUIA_IMPLEMENTACION_SEMAUIX.md` §4.3, §7,
> §11), is:
>
> **An interaction has TWO moments, and EACH may carry animation — one, the
> other, or both:**
>
> - **The `--event` moment** (`data-event-*`): the perceptual flourish during
> a sema signal's *hold*. It is the `motion` channel of the **perceptual
> signature** (§11), defined **per event** (`family`/`intent`/`event`).
> Transient.
> - **The `--state` moment** (`data-state`): the **transition** to/from a
> persistent condition. Defined **per component** (the `motion` prop).
> Persistent.
>
> This is what makes UIX different from **every** current framework: the
> others collapse presence animation onto a single axis (Chakra: only
> `data-state` + `Presence`). UIX animates both moments and integrates them
> with the perceptual signature — more coherent and richer.
>
> **Implemented (F1–F6):** the two moments (keyframes + `signatures` +
> `presets` + generation + the `EngineMotion` engine + validation + tests +
> the `/temas/animations` demo); the **signature migrated** from `events.css`
> into the registry (`signatures`, F2); the **recipes migrated** to presets
> with per-component duration/easing overrides (F3); the **JS drivers**
> `spring` (physics) / `waapi` / `rect` (FLIP) + `Presence.motion` + a real
> overlay (Dialog) bouncing (F4); **choreography** — stagger + the Material
> `shared-axis`/`fade-through` presets (F5); **token rigor** — the raw
> signature tokenized (long perceptual holds `slower`/`deliberate`/
> `emphatic`/`sustained`), shared-axis on `--motion-distance-xl`, the
> `emphasized` easing, the `[data-motion-set='expressive']` set (F6); the
> **extensible typegen** of preset names — the augmentable registry
> `EidosMotionPresets` (F7). **Roadmap F1–F7 complete.** `data-motion-ref`
> and the "TSC `event:*` scope" are obsolete (THEMING §13/§14, no real use).
>
> **Refactor (post-F5): the motion engine is a SERVICE.** The runtime no
> longer lives in eidos: it was relocated to **`src/arts/motion`** (an *art*
> — a pure runtime artifact with no UI or cross-art dependencies), exposed as
> **`uix.motion`** and consumed by **both** layers — soma (`Presence`
> receives `motion: EngineMotion` and calls `motion.run(node, phase)`) and
> eidos (delegates via `eidos.motion` + registers its `css` presets in the
> service at boot). This **dissolves the soma→eidos coupling**: the
> `DialogProps.runMotion` prop and `eidos.motionRunner` disappear — the
> *bridge* is now `EngineMotion.run`, which reads the node's
> `data-animation-style`. Eidos keeps the **CSS generation**
> (`lib/render-css.ts`) + the presets/keyframes/signatures **DATA**
> (`lib/motion/presets/css.ts`); the types, the engine and the drivers
> (`spring`/`waapi`/`rect`) live in `$motion`. See
> [`src/arts/motion/README.md`](../../src/arts/motion/README.md). (Sections
> §5/§6/§9 + the file tree below already reflect the new home.)
> **USAGE extension — 3 domains (2026-06-21).** This document describes the
> ENGINE, which has **two moments** (event/state). At the USAGE level the
> `motion` prop covers **three** domains: **event** (the signature) ·
> **state** (the per-component transition) · **content** (the third: content
> entering/leaving/looping, via `motionAttrs` / `<Motion>` / the
> `spin`/`pulse`/… loops — a USAGE layer **on top of** the state-preset
> machinery, NOT a third engine moment). The dedicated state-domain
> (`data-motion-state`, `<Card>` pilot), the container-driven cascade in both
> directions and the `[data-debug-stagger]` affordance are post-F7. **For the
> task-oriented guide** (recipes · preset catalog · loops · state-domain ·
> staggered cascade · reduced-motion · debug) →
> [`motion-guide.md`](./motion-guide.md). For decisions and history (incl.
> the "coordinated" engine retired in Plan A) →
> [`MOTION_SERVICE_RFC.md`](../../src/uix/eidos/MOTION_SERVICE_RFC.md).
**TL;DR**:
- Two animatable moments: **`--event`** (`data-event-*`, the perceptual
signature, `signatures`) and **`--state`** (`data-state`, the
per-component transition, `presets`). One, the other, or both; they
compose in sequence (`sequence`).
- One registry (`EidosConfig.motion`) with three maps: `keyframes`,
`signatures` (the event-moment), `presets` (the state-moment).
- `data-state` belongs to **soma**; `data-event-*` to **sema**; **eidos
reads both and animates** (§7: the rule is not to OVERWRITE the other
layer's attribute, not "one axis only").
- Usage: the state-moment via the prop `motion="scale-fade"` →
`data-animation-style`; the event-moment is automatic when the event fires
(the signature).
- Drivers: `css` (the floor, Chakra parity) + JS (`waapi`/`spring`/`rect`/
`svelte` — physics/FLIP/genie/orchestration, beyond Chakra), for either
moment.
---
## Table of contents
1. [Thesis and positioning](#1-thesis-and-positioning)
2. [The two moments](#2-the-two-moments)
3. [Motion across the 4 layers](#3-motion-across-the-4-layers)
4. [Architecture — the two-surface registry](#4-architecture--the-two-surface-registry)
5. [The types](#5-the-types)
6. [The `EngineMotion` API](#6-the-enginemotion-api)
7. [The drivers](#7-the-drivers)
8. [The DOM contract](#8-the-dom-contract)
9. [Soma integration (`Presence`)](#9-soma-integration-presence)
10. [Reduced motion](#10-reduced-motion)
11. [Primitives and keyframes](#11-primitives-and-keyframes)
12. [Initial content (signatures + presets)](#12-initial-content-signatures--presets)
13. [Per-component defaults](#13-per-component-defaults)
14. [Where the code lives](#14-where-the-code-lives)
15. [The event-moment: from `events.css` to `signatures`](#15-the-event-moment-from-eventscss-to-signatures)
16. [Comparison with Chakra UI v3](#16-comparison-with-chakra-ui-v3)
17. [Naming decisions](#17-naming-decisions)
18. [Implementation phases](#18-implementation-phases)
19. [Out of scope / deferred](#19-out-of-scope--deferred)
---
## 1. Thesis and positioning
An interaction does not have **one** animatable moment; it has **two**, and
they are of different natures:
1. The **perceptual occurrence** — "something just happened" — transient,
with a `hold`, an `intent` and a `sequence`. **Sema** writes it as
`data-event-*`.
2. The **state change** — "this is now open" — persistent, the source of
truth. **Soma** writes it as `data-state`.
The canon makes it explicit (GUIA §4.3): `emerge` = "something enters or
leaves the perceptual field" → it is an **event**, not a state. GUIA §11
defines the **per-event perceptual signature**, where `motion` is one of the
channels (alongside sound/color/presence/haptic): the exit movement of a
`commit.delete + loss` is "withdrawal/descent"; that of a
`signal.alert + threat` is "protruding entrance". Movement is defined **per
event**.
> **UIX's differentiator.** Every current framework animates presence on
> **one axis** (Chakra: `data-state` + `Presence`; Radix/Ark: same). UIX
> distinguishes the two moments and **animates both**, integrating the
> event-moment with the perceptual signature (sound/haptic included). That
> is what makes it more coherent (one model, two sharp moments) and richer
> (the animation of "what happened" is never confused with "what state we
> are in").
**What eidos owns**: the typed DATA registry (`EidosConfig.motion` =
`keyframes` + `signatures` + `presets`), the CSS generation for both
surfaces, the `motion` prop and the `data-animation-style` attr. The
**execution engine** (`EngineMotion`) is NOT eidos's: it lives in `$motion`
and is consumed via `uix.motion` (eidos delegates + registers its `css`
presets there).
**What it does NOT own**: emitting the signal and its semantic signature →
**sema**; the mount/unmount lifecycle and `data-state` → **soma
`Presence`**; the attrs animated over (`data-state`, `data-side`,
`data-starting/ending-style`) → declared in **morfo**; the `allow`/`reduce`
pref → **`ActivePrefs`**.
---
## 2. The two moments
| | The **`--event`** moment | The **`--state`** moment |
|---|---|---|
| **Attribute** | `data-event-*` | `data-state` |
| **What it is** | the perceptual occurrence (the signal) | the transition to/from a persistent condition |
| **Its animation** | the **signature's flourish** (settle, pulse, intent-tinted withdrawal, a `present`'s entrance…) | the **presence/layout transition** (scale, slide, grow to the open height…) |
| **Granularity** | **per event** (`family`/`intent`/`event`) — generic, consistent system-wide | **per component** (the `motion` prop) |
| **Attr owner** | sema (stamps it during the `hold`) | soma (effects) |
| **Nature** | transient (lives the `hold`) | persistent |
| **In the registry** | `signatures` | `presets` |
| **Canon** | §11 (perceptual signature), §4.3 (emerge), §7.1 | §7.2, §5.2 (`sequence`) |
**Each moment may carry animation — one, the other, or both:**
- `press` (a button): only the **event** moment (`contact.press` → squeeze).
No state transition.
- a `dialog` opening: the **event** flourish of `emerge.present` **and** the
**state** transition to `open`.
- a `collapse`: the height transition (**state**) **and/or** the `expand`
**event** flourish.
**They compose in sequence** (GUIA §5.2): `sequence: 'pre'` runs the event's
flourish **before** the state changes (e.g. animate the exit before
closing); `'post'`, after (celebrate after the real result). The
architecture chapter describes it in the causal chain
([`active-architecture.md`](../architecture/active-architecture.md) §5): the
event's animation runs during the `hold`, then the state takes over.
**`data-state` and `data-event-*` never mix** (§7.3): the rule is that
**sema does not overwrite** state attributes and vice versa (ownership). It
does NOT say only one may animate — **eidos reads both and animates both**.
What is forbidden is stepping on the other's name, not animating over both
axes.
test(motion): pin firma-vs-stagger cascade precedence (F4-D motion decision) Workflow analysis (state map + 4 dispositions + adversarial stress) resolved the direction dispute: the stagger/coordinated preset winning the (0,3,0) tie over the generic commit-affirm firma is the DOCTRINE-CORRECT outcome (RFC B.2 — the coordinated owns the visual axis, the generic firma is silenced). The earlier "flip to firma" reading was backwards; that flip contradicts B.2 and its channels:[] safety net is dormant; @layer sinks as a front-line fix (a semantic ownership question answered with a global cascade tool + a reduced-motion !important-inversion a11y risk). No shipped component shows a defect (dropdown select feedback is sound+haptic only; the item is fading out at select). The only real issue: the correct outcome rides on an ACCIDENTAL source-order tiebreak (the explicit neutralization was retired), undocumented + untested. Execute the proportionate fix: - Regression test (motion.test.ts) pinning the (0,3,0)==(0,3,0) tie + the signatures-before-presets emission order + the absence of a (0,4,0) [data-event^='commit-select'] signature — a HAZARD MONITOR, so a generator reorder or a widened signature fails loudly, not silently. - Load-bearing comment at the render-css emission order. - Doctrine note in motion.md: a known-fragile accident, not a contract; the structural endpoint ("una firma por evento") + @layer are deferred/tracked. Zero behavior change; base.css unchanged. Tests 28/28; npm run check src/ = 9 baseline, 0 new. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
> **When both write `animation` on ONE node (KNOWN-FRAGILE).** Eidos animates
> both axes, but a firma and a `--state`/stagger preset both set the single
> `animation` shorthand — only one applies. On a shared node (a `DropdownMenu`
> item that staggers on open/close AND receives a `commit-select` event) both
> selectors compute `(0,3,0)`, so **CSS source order** decides: the generator
> emits all `signatures` before all `presets`, so the later stagger wins and the
> generic firma is masked — directionally the intended outcome (the coordinated
> owns the visual axis, RFC B.2), but produced **accidentally by emission order**,
> NOT by an opt-out (the `channels:[]` / `expression:'none'` source-silence is
> dormant; the old `animation: none !important` neutralization is retired). No
> shipped component shows a defect (e.g. dropdown select feedback is sound+haptic
> only; the masked pulse is the generic fallback and the item is fading out at
> select). This is a **provisional accident, not a contract** — pinned by
> `motion.test.ts` ("pins the firma-vs-stagger cascade precedence") so a generator
> reorder or a future `(0,4,0)` `[data-event^='…']` signature fails loudly. The
> structural fix ("una firma por evento") is
> [RFC §D.4-B](../../src/uix/eidos/MOTION_SERVICE_RFC.md). `@layer` (order-
> independent precedence) is a deferred, tracked end-state — triggered only when a
> coordinated component gains a visual commit channel, `(0,4,0)` signatures ship,
> or `@layer` is adopted for other reasons.
> **Child coordination (stagger / cascade) — CLOSED model, RFC §D.11.**
> There was a third "coordinated" axis (`PresenceGroup` / `cascade-*` presets
> over `data-starting/ending-style`); it was **retired** on 2026-06-19. The
> final model: the cascade is NOT a separate system — it is the **`--state`
> moment** (a declared preset) + the **stagger that already existed**
> (`index × --motion-stagger-each`, parallel = 0, cascade = N) + **one
> foundation rule** that writes the index from structure
> (`[data-stagger] > *:nth-child → --motion-stagger-index` /
> `:nth-last-child → -rev`, generated in `lib/render-css.ts`). The event's
> signature (`emerge` → `present-rise`) is only the **container's** flourish
> (generic, over `data-event-*`); the children's timing is **per-component
> realization**, not a sema channel. Three orthogonal axes (sema emits ·
> motion is the engine · eidos materializes) + the full lifecycle:
> [RFC §D.11](../../src/uix/eidos/MOTION_SERVICE_RFC.md).
> **The UNIVERSAL `motion` prop — one selector, three domains (design
> framing, [RFC §D.12]).** After retiring the coordinated axis, ONE system
> remains → **a single `motion` prop** (today `<Cascade>` still uses
> `animation`; it gets unified). The goal: **any** component (not just
> overlays) can receive `motion="X"` from a type-safe registered catalog.
> What the prop means is decided by the **discriminant** "does the animation
> realize a perceptual event?": **event** → the signature rules, the prop is
> an override/violation (types/lint); **state** → it selects the
> state-preset (`data-state`); **content** (no event) → the prop is the
> **primary** path. The sound/haptic coupling applies only to the event
> domain — content ones stay in clean parity. Detail + plan (a)+(b):
> [RFC §D.12](../../src/uix/eidos/MOTION_SERVICE_RFC.md).
> **Structural constraints of the CSS model — [RFC §D.13].** The contracts
> the declarative approach demands: (1) **structure** — `[data-stagger]` ↔
> DIRECT children; `:nth-child` ignores comments/`{#if}` (hardened), but an
> intermediate wrapper element breaks the count → a cascading grouper
> becomes its own scope (`inherits:false` isolates the index);
> `display:contents` without re-scoping = a dead zone. (2) **exit** —
> unit-exit via `Presence` (retain + await; the parent's `opacity` drags the
> children) works today; per-child staggered-exit requires lifecycle JS
> (`PresenceGroup`, deferred) → the container-driven rule is ENTER-ONLY;
> **never raw `{#if}`** on an animated surface. (3) **debug** — the index is
> `inherits:false` (no ancestor can stomp it); everything lives typed in
> Computed; the opt-in `[data-debug-stagger]` mode materializes it (an
> `::after` badge per child with its index, via a counter mirroring
> `:nth-child - 1`).
---
## 3. Motion across the 4 layers
| Layer | What it contributes | Moment |
|---|---|---|
| **Morfo** | Declares the attrs: `data-state` + states (`open`/`closed`), the **events** (`emerge`/`commit`/`signal` + `sequence`/`persistence`/`intent`), `data-side`/`data-align`, `data-starting/ending-style`. | both |
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho componentes y `emerge-open` en tres. No era estetica — un preset de movimiento engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna firma y simplemente no animaba, sin romper una sola prueba. Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos, 256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran 40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y `handle-drop` ya existian en 10 y 4 componentes. `validateMorfo` cierra la puerta: un `events[].name` que no empiece por su familia ahora lanza. Visto fallar antes con un nombre pelado inyectado. Lo que el renombrado destapo, y va aqui tambien: - La receta del splitter enganchaba `commit-resize`, muerto desde `bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el catalogo de morfos — el guard que lo habria cazado en su dia. - La familia `shift` era muda en el canal visual, contra su propia doctrina (c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora tiene firma direccional: sexto atributo del sello (`data-event-direction`, `forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por `:dir()`. Medido: LTR -30px/+30px, RTL los invierte. - El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues —media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze` moria sin pintar un fotograma. Una superficie, una ranura (A-36). - 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de decision, el componente vivo). Las docs desfasadas, corregidas; los nueve DEFECTOS de codigo obsoleto quedan abiertos y sin tocar. - `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y habia tres cosas distintas deletreadas «direction». check en su linea base con 0 errores nuevos por diferencia de conjuntos · docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador medidas con raton real y rAF vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| **Sema** | Stamps `data-event-*` (`family`/`intent`/`direction`/`phase`/`id`) during the `hold`; resolves the signature (sound/haptic are runtime channels; **`motion`/`color`/`presence` are materialized by eidos** reading `data-event-*`). | `--event` |
| **Soma** | Writes `data-state` via effects; `Presence` keeps the node during the exit and awaits the animation (`getAnimations()` + `Promise.all(finished)`). Fires the event with its `sequence`. | `--state` (+ fires the event) |
| **Eidos** | `keyframes` + `signatures` (event-moment, over `data-event-*`) + `presets` (state-moment, over `data-state`) + **CSS generation**; delegates the JS engine to **`uix.motion`** (the service) and registers its `css` presets there at boot. **Reads both axes and animates.** | both |
Reduced-motion is already wired: `MotionEffective = 'allow' | 'reduce'`
(`libs/motion`), projected as `data-motion` by `ActivePrefsDomProjection`,
tracked by `ReducedMotionTracker` (`arts/adom`), exposed on
`ActiveDom.prefersReducedMotion.matches`.
---
## 4. Architecture — the two-surface registry
```
EidosConfig.motion
├── keyframes: { 'fade-in': {...}, 'scale-in': {...}, ... } registered @keyframes
│
├── signatures: { ← the --event MOMENT (the signature, generic per event)
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho componentes y `emerge-open` en tres. No era estetica — un preset de movimiento engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna firma y simplemente no animaba, sin romper una sola prueba. Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos, 256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran 40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y `handle-drop` ya existian en 10 y 4 componentes. `validateMorfo` cierra la puerta: un `events[].name` que no empiece por su familia ahora lanza. Visto fallar antes con un nombre pelado inyectado. Lo que el renombrado destapo, y va aqui tambien: - La receta del splitter enganchaba `commit-resize`, muerto desde `bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el catalogo de morfos — el guard que lo habria cazado en su dia. - La familia `shift` era muda en el canal visual, contra su propia doctrina (c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora tiene firma direccional: sexto atributo del sello (`data-event-direction`, `forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por `:dir()`. Medido: LTR -30px/+30px, RTL los invierte. - El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues —media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze` moria sin pintar un fotograma. Una superficie, una ranura (A-36). - 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de decision, el componente vivo). Las docs desfasadas, corregidas; los nueve DEFECTOS de codigo obsoleto quedan abiertos y sin tocar. - `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y habia tres cosas distintas deletreadas «direction». check en su linea base con 0 errores nuevos por diferencia de conjuntos · docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador medidas con raton real y rAF vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
│ 'emerge-present': { family:'emerge', event:'emerge-present', keyframes:['fade-in'], ... },
│ 'commit-settle': { family:'commit', keyframes:['settle'], ... },
│ 'announce-threat':{ family:'signal', intent:'threat', keyframes:['pulse'], ... }
│ } → generates [data-event-*][data-event-phase='active'] rules
│
└── presets: { ← the --state MOMENT (per-component transition)
'scale-fade': { driver:'css', enter:{...}, exit:{...} },
'slide-fade': { driver:'css', enter:{ bySide }, exit:{ bySide } },
'genie': { driver:'rect', enter, exit } (P3, JS)
} → generates [data-animation-style][data-state] rules
│
▼
uix.motion : EngineMotion (service · arts/motion — resolves, runs JS drivers, honors reduce)
▲ eidos.motion delegates here + registers the css presets at boot
│
▼
Eidos reads data-event-* (signature) + data-state (transition) → animates
```
- **`signatures`** is the generic signature: a `commit` settles the same way
system-wide; an `emerge.present` enters the same way. Sema's packs
(`sema/components/*.ts`) fine-tune a concrete component's signature. It is
what `events.css` used to do by hand (§15).
- **`presets`** is the per-component transition, chosen with the `motion`
prop.
- Theme/app can add or override in both maps.
---
## 5. The types
`$motion` (`src/arts/motion/types.ts`) — relocated to an *art*.
`duration`/`ease` are `string` (decoupled from `DurationKey`/`EaseKey`:
eidos resolves the tokens in its generation layer; the art doesn't know the
scale):
```ts
type ReducePolicy = 'instant' | 'opacity-only' | 'none'
type MotionSide = 'top' | 'right' | 'bottom' | 'left'
type KeyframeName = string // key into motion.keyframes
type MotionPresetName = string // the `motion` prop's value; 'none' disables
// One CSS phase: composed keyframes + tokens + side-awareness.
interface CssPhase {
keyframes: KeyframeName | KeyframeName[] // comma-composed: ['scale-in','fade-in']
duration?: string // token key ('moderate'…) or raw ('600ms')
ease?: string
transformOrigin?: string // e.g. 'var(--floating-transform-origin)'
bySide?: Partial<Record<MotionSide, KeyframeName | KeyframeName[]>>
}
// ── The --event moment: the perceptual signature (generic per event) ──
interface EventSignature {
family?: string // data-event-family ('emerge' | 'commit' | 'signal' | …)
intent?: string // data-event-intent (valenced families)
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho componentes y `emerge-open` en tres. No era estetica — un preset de movimiento engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna firma y simplemente no animaba, sin romper una sola prueba. Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos, 256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran 40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y `handle-drop` ya existian en 10 y 4 componentes. `validateMorfo` cierra la puerta: un `events[].name` que no empiece por su familia ahora lanza. Visto fallar antes con un nombre pelado inyectado. Lo que el renombrado destapo, y va aqui tambien: - La receta del splitter enganchaba `commit-resize`, muerto desde `bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el catalogo de morfos — el guard que lo habria cazado en su dia. - La familia `shift` era muda en el canal visual, contra su propia doctrina (c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora tiene firma direccional: sexto atributo del sello (`data-event-direction`, `forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por `:dir()`. Medido: LTR -30px/+30px, RTL los invierte. - El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues —media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze` moria sin pintar un fotograma. Una superficie, una ranura (A-36). - 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de decision, el componente vivo). Las docs desfasadas, corregidas; los nueve DEFECTOS de codigo obsoleto quedan abiertos y sin tocar. - `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y habia tres cosas distintas deletreadas «direction». check en su linea base con 0 errores nuevos por diferencia de conjuntos · docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador medidas con raton real y rAF vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
event?: string | string[] // data-event name(s)/prefix(es) (['emerge-present','emerge-open'], …)
direction?: string // data-event-direction ('forward' | 'backward') — sense of travel,
// decided per emit. A REFINEMENT, never a matcher on its own.
keyframes: KeyframeName | KeyframeName[]
duration?: string // token key OR raw hold ('600ms', outside the scale)
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
ease?: EaseKey
fill?: 'none' | 'forwards' | 'backwards' | 'both'
reduce?: ReducePolicy
}
// ── The --state moment: per-component preset (the `motion` prop) ──
interface CssStatePreset {
driver: 'css'
enter?: CssPhase // [data-state='open']
exit?: CssPhase // [data-state='closed']
reduce?: ReducePolicy
}
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
interface JsStatePreset { // implemented (waapi/spring/rect/svelte)
driver: 'waapi' | 'spring' | 'rect' | 'svelte'
enter?: MotionRun; exit?: MotionRun
requires?: ('sourceRect' | 'targetRect' | 'placement')[]
reduce?: ReducePolicy
fallback?: CssStatePreset // declared; NOT auto-applied today
// (the spring driver honors ctx.reduced itself)
}
type StatePreset = CssStatePreset | JsStatePreset
// ── The registry ──
interface MotionConfig {
keyframes?: Record<KeyframeName, KeyframeStops>
signatures?: Record<string, EventSignature> // the --event moment
presets?: Record<string, StatePreset> // the --state moment
}
```
Structural decision: **the event-moment is generic per event**
(`signatures`, by `family`/`intent`), not packaged per preset — because the
signature (§11) is defined per event and must be consistent across
components. Per-component fine-tuning of the event-moment goes in **sema's
packs**. The `motion` prop only chooses the **state preset**.
---
## 6. The `EngineMotion` API
Lives in `$motion` (the `uix.motion` service; `eidos.motion` and
`soma.motion` expose it — the same instance). Registration + the declarative
CSS path + **JS driver execution**. The caller passes `MotionRunOptions`
(`dom`, `reduced`, `side`, `sourceRect`/`targetRect`, `duration`/`ease`) for
the drivers that need them.
```ts
interface EngineMotion {
register(name: string, preset: StatePreset): void
resolve(name: string): StatePreset | undefined
has(name: string): boolean
list(): string[]
// css: declarative (settled handle); js: builds the MotionContext, runs the
// driver, normalizes the return into a handle and tracks it per element.
enter(el: HTMLElement, name: string, opts?: MotionRunOptions): MotionHandle
exit(el: HTMLElement, name: string, opts?: MotionRunOptions): MotionHandle
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// Presence's bridge (replaces the old `runner`): reads the node's
// `data-animation-style` and delegates to enter/exit (css → settled handle;
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
// js → runs the driver).
run(el: HTMLElement, phase: 'enter' | 'exit', opts?: MotionRunOptions): MotionHandle
cancel(el: HTMLElement): void // cancels active JS handles
pending(el: HTMLElement): Promise<void> // combined finished (for Presence)
dispose(): void // cancels everything (service cleanup)
}
```
- A **`css` state preset** is declarative: the wrapper sets
`data-animation-style` and the `[data-animation-style][data-state]` CSS
rule + soma's `Presence` do the rest. The runtime doesn't "run" it.
- A **JS state preset** runs the driver (`spring`/`waapi`/`rect`/`svelte`),
normalizes the return (`Animation` | `Animation[]` | `MotionHandle`) into
one handle, tracks it per element (`cancel`/`pending`), and honors the
`ReducePolicy`.
- The **event-moment** (signatures) does not go through `enter/exit`: it is
generated CSS reacting to `data-event-*`.
### 6.1 — Cleanup policy (WAAPI / spring)
`fill: forwards` leaves `Animation`s dangling in `getAnimations()` → it
would break `Presence`'s exit wait. Discipline: rest states live in the
`[data-state]` CSS; `waapi` presets animate **without `fill: forwards`** and
the runtime `cancel()`s on finish; for rest states not expressible in CSS
(measured FLIP), `commitStyles()` + `cancel()`.
---
## 7. The drivers
They apply to **either moment** (a signature or a state preset).
| Driver | Substrate | For | Beyond CSS |
|---|---|---|---|
| **`css`** | `@keyframes` + tokens (generated) | fade / scale / slide / collapse / the signature | — (the Chakra floor) |
| **`waapi`** | `el.animate()` | runtime-computed keyframes | measured distance/size, cancelable |
| **`spring`** | a semi-implicit Euler integrator (RAF via `ActiveDom`) | overshoot/settle, drag, snap-back | real physics — what no cubic-bezier expresses |
| **`rect`** | rect measurement (via `ActiveDom`) → WAAPI | FLIP, shared-element, genie | animating between two real positions |
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| **`svelte`** (opt-in) | `transition:` / `animate:flip` | `{#each}` list reorders | Svelte's declarative |
**Bundle**: `css` (generated) is the floor. `spring` is a **self-contained**
integrator (no dependency — one independent spring per property, stepped by
`ctx.dom.requestFrame`); `waapi` wraps `el.animate` (a browser API, already
visible to `getAnimations()`); `rect` (FLIP) composes measurement + WAAPI.
The helpers live in `lib/motion/presets/js.ts` (`spring()` / `waapi()` /
`rect()`). `svelte` (opt-in) is reserved for `animate:flip` in `{#each}`. An
external adapter (`motion-one`) is opt-in.
**The `svelte` nuance**: `{#if}` transitions would fight soma's `Presence`;
the core path is WAAPI/spring (composes with `getAnimations`). `svelte` is
reserved for `animate:flip` in `{#each}` (Svelte owns that lifecycle).
**`rect` batching**: FLIP with N elements does its pass in two phases over a
single `requestFrame` (measure all sources → mutate → measure all targets →
animate). `ActiveDom` already provides `requestFrame`; the batching belongs
to the driver, not to `ActiveDom`.
---
## 8. The DOM contract
**The `--event` moment** (sema writes it during the hold; eidos reacts):
```css
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho componentes y `emerge-open` en tres. No era estetica — un preset de movimiento engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna firma y simplemente no animaba, sin romper una sola prueba. Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos, 256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran 40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y `handle-drop` ya existian en 10 y 4 componentes. `validateMorfo` cierra la puerta: un `events[].name` que no empiece por su familia ahora lanza. Visto fallar antes con un nombre pelado inyectado. Lo que el renombrado destapo, y va aqui tambien: - La receta del splitter enganchaba `commit-resize`, muerto desde `bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el catalogo de morfos — el guard que lo habria cazado en su dia. - La familia `shift` era muda en el canal visual, contra su propia doctrina (c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora tiene firma direccional: sexto atributo del sello (`data-event-direction`, `forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por `:dir()`. Medido: LTR -30px/+30px, RTL los invierte. - El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues —media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze` moria sin pintar un fotograma. Una superficie, una ranura (A-36). - 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de decision, el componente vivo). Las docs desfasadas, corregidas; los nueve DEFECTOS de codigo obsoleto quedan abiertos y sin tocar. - `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y habia tres cosas distintas deletreadas «direction». check en su linea base con 0 errores nuevos por diferencia de conjuntos · docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador medidas con raton real y rAF vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
[data-event='emerge-present'][data-event-phase='active'] { animation: fade-in …; }
[data-event-family='commit'][data-event-phase='active'] { animation: settle …; }
[data-event-family='signal'][data-event-intent='threat'][data-event-phase='active'] { animation: pulse …; }
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho componentes y `emerge-open` en tres. No era estetica — un preset de movimiento engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna firma y simplemente no animaba, sin romper una sola prueba. Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos, 256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran 40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y `handle-drop` ya existian en 10 y 4 componentes. `validateMorfo` cierra la puerta: un `events[].name` que no empiece por su familia ahora lanza. Visto fallar antes con un nombre pelado inyectado. Lo que el renombrado destapo, y va aqui tambien: - La receta del splitter enganchaba `commit-resize`, muerto desde `bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el catalogo de morfos — el guard que lo habria cazado en su dia. - La familia `shift` era muda en el canal visual, contra su propia doctrina (c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora tiene firma direccional: sexto atributo del sello (`data-event-direction`, `forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por `:dir()`. Medido: LTR -30px/+30px, RTL los invierte. - El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues —media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze` moria sin pintar un fotograma. Una superficie, una ranura (A-36). - 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de decision, el componente vivo). Las docs desfasadas, corregidas; los nueve DEFECTOS de codigo obsoleto quedan abiertos y sin tocar. - `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y habia tres cosas distintas deletreadas «direction». check en su linea base con 0 errores nuevos por diferencia de conjuntos · docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador medidas con raton real y rAF vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
[data-event-family='shift'][data-event-direction='forward'][data-event-phase='active'] { animation: shift-cross-forward …; }
```
feat(morfo,sema,eidos): todo evento dice de que familia es, y el cruce por fin se ve El framework llamaba a la misma cosa de dos maneras: `open` pelado en ocho componentes y `emerge-open` en tres. No era estetica — un preset de movimiento engancha el nombre con `^=`, asi que el dialecto pelado no casaba con ninguna firma y simplemente no animaba, sin romper una sola prueba. Los 40 nombres sin prefijo pasan a `{familia}-{verbo}[-{matiz}]`: 256 eventos, 256 con prefijo, 0 ambiguos. El plan decia 36 y decia `handle-drag-start`; eran 40, y el canon (c25) dice que esos verbos son `pick` y `drop` — `handle-pick` y `handle-drop` ya existian en 10 y 4 componentes. `validateMorfo` cierra la puerta: un `events[].name` que no empiece por su familia ahora lanza. Visto fallar antes con un nombre pelado inyectado. Lo que el renombrado destapo, y va aqui tambien: - La receta del splitter enganchaba `commit-resize`, muerto desde `bd2e40366`. No casaba desde mayo y nadie chillo. Reescrita por FAMILIA, como slider y knob, y `eidos-lint` valida ahora el VALOR de `data-event*` contra el catalogo de morfos — el guard que lo habria cazado en su dia. - La familia `shift` era muda en el canal visual, contra su propia doctrina (c27: el cruce debe percibirse; c34 tipifica el «shift invisible»). Su mapa ya describia la firma que le faltaba y su sonido por defecto es `slide`. Ahora tiene firma direccional: sexto atributo del sello (`data-event-direction`, `forward`|`backward`, por emision) y deslizamiento de 320ms RTL-safe por `:dir()`. Medido: LTR -30px/+30px, RTL los invierte. - El sello de `shift-navigate` pasa del BOTON al `grid` en los cuatro calendarios. Medido: el boton recibia `contact-activate` y 8,5 ms despues —media trama— el `shift-navigate` pisaba la misma ranura y el `press-squeeze` moria sin pintar un fotograma. Una superficie, una ranura (A-36). - 101 contradicciones docs<->morfo adjudicadas con evidencia (git log, docs de decision, el componente vivo). Las docs desfasadas, corregidas; los nueve DEFECTOS de codigo obsoleto quedan abiertos y sin tocar. - `SoundDirection` -> `SoundContour`: era un contorno de tono, no un sentido, y habia tres cosas distintas deletreadas «direction». check en su linea base con 0 errores nuevos por diferencia de conjuntos · docs:check 0/0 · eidos-lint invalid 0 · el censo y las escenas de navegador medidas con raton real y rAF vivo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`data-event-direction` (`forward` | `backward`) is the sixth attr of the stamp
and the only one decided PER EMIT rather than declared in the morfo: the event
name already says `shift-enter-mode` vs `shift-exit-mode`, so the attr carries
only the sense of travel. The `shift` crossing is the first firma to read it —
its keyframes travel the inline axis via `--motion-shift-sign` (+1 `:dir(ltr)` /
−1 `:dir(rtl)`), never a physical `translateX`.
**The `--state` moment** (soma writes it; eidos reacts). The wrapper sets
`data-animation-style` (the `motion` prop); the rest state comes from the
recipe over the same `data-state`:
```css
[data-animation-style='scale-fade'][data-state='open'] { animation: scale-in, fade-in …; }
[data-animation-style='scale-fade'][data-state='closed'] { animation: scale-out, fade-out …; }
[data-animation-style='slide-fade'][data-side='top'][data-state='open'] { animation: slide-from-bottom, fade-in …; }
```
Public prop: `motion?: MotionPresetName | 'none'` (per-component default;
`'none'` disables). Placement-aware: it reads `data-side`/`data-align`
(morfo) + `--floating-transform-origin` (soma floating).
**NOT `data-motion`**: that name already has two owners (the
`allow`/`reduce` pref + NavMenu's direction). The engine's attr is
`data-animation-style` (mirroring Chakra's `animationStyle`). See §17.
---
## 9. Soma integration (`Presence`)
`soma/layers/presence.svelte.ts` already does the lifecycle: on close it
keeps the node, awaits `node.getAnimations()` + `Promise.all(finished)`
(`runId` guard, `enabled` flag), and unmounts. CSS animations (both moments)
and `el.animate()` ones (JS drivers) appear in `getAnimations()` → **they
are awaited with no new plumbing**.
**`Presence.motion`** (F4 · the service refactor) — the hook for drivers NOT
in `getAnimations()` (the `spring`, pure RAF): `Presence` receives
`motion: EngineMotion` and calls **`motion.run(node, phase)`** on
enter/exit; `run` starts the JS animation and returns a `MotionHandle`,
whose `finished` `Presence` awaits ALONGSIDE `getAnimations()`. For a `css`
preset (or a node without `data-animation-style`), `run` returns an
already-settled handle → the declarative path is unchanged. Soma **does NOT
import eidos**: it consumes the service via `soma.motion` (= `uix.motion`),
the same instance as `eidos.motion`. Wired in Dialog's provider
(`contentPresence`/`overlayPresence` receive `motion: this.soma.motion`);
the same pattern fits any overlay — just pass `motion` to its `Presence`,
**no prop and no coupling** (the old `DialogProps.runMotion` /
`eidos.motionRunner` disappeared in the refactor).
**Child orchestration** (pending, being redesigned): it hangs off the
event's signature (sema) materialized by eidos, not off a parallel engine —
see Appendix D of
[`MOTION_SERVICE_RFC.md`](../../src/uix/eidos/MOTION_SERVICE_RFC.md).
### 9.1 — SSR / hydration
Overlays mount on open, client-side; motion fires on transitions, not on
first mount (a mounted-open element suppresses the enter). No in-flight
SSR→JS handoff; the CSS `fallback` covers no-JS.
### 9.2 — Exit + a11y: `inert` (soma's)
During the exit the node stays mounted: it must be `inert`/`aria-hidden` and
unfocusable. The morfo's `focus.return` already returns focus; `inert`
during the exit window is a `Presence`/`Dismissal` improvement in soma (the
pattern is already used in `color-field`).
---
## 10. Reduced motion
- **CSS** (both moments): `[data-motion='reduce'] …` rules + `@media
(prefers-reduced-motion)` per policy (`instant` → `animation: none`;
`opacity-only` → fade only; `none` → untouched).
- **Runtime (JS drivers)**: `MotionContext.reduced` from
`ActiveDom.prefersReducedMotion`; the `ReducePolicy` applies before
running.
- Morfo declares `a11ySemantic.reducedMotionFallback` per event; soma
silences the signal when it applies. The engine is coherent with that
decision.
---
## 11. Primitives and keyframes
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Existing tokens (`STATIC_MOTION`, generated, read by css and js):
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Token | Values |
|---|---|
| `--duration-{k}` | `instant 0` · `fast 120` · `normal 180` · `moderate 240` · `slow 320` ms |
| `--ease-{k}` | `default` · `out` · `in` · `spring` · `alert` · `symmetric` |
| `--motion-distance-{k}` | `xs 2` · `sm 4` · `md 8` · `lg 16` px |
| `--motion-scale-{k}` | `enter 0.985` · `press 0.97` · `through 0.92` (fade-through) · `lift 1.02` (drag pickup — the only `>1`, see §Draggable surfaces in THEMING) |
**Override vars (runtime, NOT theme tokens)** — a component sets them on its
element; the preset reads them with a token fallback:
| Var | For | Notes |
|---|---|---|
| `--motion-duration-{enter,exit}` | per-component timing (F3) | `@property inherits:false` |
| `--motion-ease-{enter,exit}` | per-component curve (F3) | `@property inherits:false` |
| `--motion-slide-leave` | `slide-full`'s extra travel for inset panels (drawer) | default `0px` |
| `--height` / `--collapsed-height` | the `collapse`'s measured height (soma writes it) | — |
| `--motion-stagger-each` | the stagger rhythm (on the container, inherits) | default `0ms` → **parallel**; N → cascade |
| `--motion-stagger-index` / `-index-rev` | the per-item index **from structure** — `[data-stagger] > *:nth-child` (fwd, enter) / `:nth-last-child` (rev, exit); generated in `render-css.ts`, **nobody writes it** | `@property inherits:false`, `<integer>` |
Keyframes (`EidosConfig.motion.keyframes`, parity with Chakra's
`theme.keyframes`): individual `translate`/`scale` (they compose without
stomping each other); parameterized by CSS var for dynamic sizes/distances
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
(`var(--height)`, `var(--collapsed-height,0)`). Built-in: `fade-in/out`,
`scale-in/out`, `slide-from/to-{side}[-full]`, `expand/collapse-height`; the
**signature** (`announce-pulse-*`, `commit-settle`, `press-squeeze`,
`dismiss-fade`, `present-rise`); **Material**
(`slide-axis-{x,y}-{in,out}`, `scale-from-92`).
---
## 12. Initial content (signatures + presets)
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**`presets` (the `--state` moment)** — built-in (`css`):
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Name | Notes |
|---|---|
| `fade` · `scale-fade` | enter+exit; scale-fade with `--floating-transform-origin` |
| `slide-fade` | side-aware (`data-side`): slide + scale 0.985 + fade |
| `slide-full` | edge drawer, inset-aware (`--motion-slide-leave`) |
| `collapse` | measured height (`var(--height)`) |
| `shared-axis-x/y` · `fade-through` | Material 3 (F5) |
The **JS drivers** (`spring`/`waapi`/`rect`, `presets/js.ts`) are registered
per app/demo. The first **named built-in JS** preset is `spring-pop`
(`driver:'spring'`, `presets/js.ts` → `BUILTIN_JS_PRESETS`), registered
directly on `ActiveEidos` — not via the serializable
`EidosConfig.motion.presets`, which cannot carry its `MotionRun` functions
(`structuredClone` fails). Demos additionally register `panel-spring`,
`dialog-spring`, `flip`.
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**`signatures` (the `--event` moment)** — built-in, migrated from
`events.css` (F2, §15): `present`/`dismiss`, `commit` (+ the
`fulfill`/`affirm`/`threat` intents), `press` (`contact`), `announce`
(+ 5 intents). Generic per `family`/`intent`/`event`.
---
## 13. Per-component defaults
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
The per-component **state-moment** (the `motion` prop, the wrapper's
default):
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Component | Part | Default `motion` |
|---|---|---|
| Dialog | overlay / content | `fade` / `scale-fade` |
| Popover · Tooltip · Dropdown · Select · Menubar · ContextMenu | content | `slide-fade` |
| Drawer | content | `slide-full` |
| Accordion · Collapsible | content | `collapse` |
| Toast | item | `slide-fade` |
The **event-moment** is automatic: when the event fires (e.g. `commit`,
`emerge.present`), the corresponding `signatures` entry reacts — no prop.
---
## 14. Where the code lives
```
arts/motion/ ← THE SERVICE (a pure art: no UI or cross-art deps)
types.ts ✓ MotionDom (structural port), MotionConfig, EventSignature, StatePreset, MotionHandle…
engine-motion.ts ✓ createEngineMotion → EngineMotion (register/resolve/enter/exit/run/cancel/pending/dispose)
drivers.ts ✓ JS drivers: spring() (physics) / waapi() / rect() (FLIP)
index.ts ✓ the `$motion` barrel (named re-exports) + README.md
arts/active-app/service-factories/motion.ts ✓ defineEngineMotion (factory; coreDep 'dom')
eidos/lib/motion/presets/css.ts ✓ DATA: keyframes + state presets + signatures (BUILTIN_*) + Material (F5)
eidos/lib/config-types.ts ✓ EidosConfig.motion ($motion's MotionConfig type)
eidos/lib/render-css.ts ✓ GENERATES CSS: presets ([data-state]) + signatures ([data-event-*]) + overrides + stagger
eidos/lib/config.ts ✓ validates presets (css + js) + signatures
eidos/lib/themes/base.ts ✓ built-in DATA (keyframes + signatures + presets)
eidos/active-eidos.svelte.ts ✓ eidos.motion → delegates to uix.motion + registers the css presets at boot
eidos/components/dialog/ ✓ wrapper: motion prop → data-animation-style (no runMotion)
active-uix/active-uix.svelte.ts ✓ uix.motion : EngineMotion (createActiveUix creates it; attach reads it from the app)
soma/core/soma.svelte.ts ✓ soma.motion → uix.motion
soma/layers/presence.svelte.ts ✓ `motion: EngineMotion` opt → motion.run(node, phase) (JS gating) + tests
```
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
(`✓` done. Relocated to `arts/motion` in the service refactor. Pending:
passing `motion` to more overlays (drawer/popover/…); named built-in JS
drivers; F6/F7.)
---
## 15. The event-moment: from `events.css` to `signatures`
`events.css` WAS "the hand-implemented `--event` surface". In **F2 it
migrated to `signatures`**: the 9 keyframes + 12 reactions (announce pulse
per intent, commit settle, dismiss fade, press squeeze, present rise) now
live in `EidosConfig.motion.{keyframes,signatures}` (`presets/css.ts` →
`BUILTIN_SIGNATURES`) and are generated into `generated/base.css` —
theme-extensible. `events.css` was reduced to two global concerns: the
compositor hint (`will-change`) and the reduced-motion cap (the signature's
`motion` is silenced under reduce, but sound + haptic keep communicating —
the cross-modal advantage). `archetypes.css` (4 baseline hover/focus
transitions) stays as the transversal micro-interaction.
---
## 16. Comparison with Chakra UI v3
| Capability | Eidos | Chakra v3 |
|---|---|---|
| The `--state` moment (`data-state` + named presets) | ✅ `presets` + the `motion` prop | ✅ `animationStyle` + `data-state` |
| `Presence` (hold-through-exit) | ✅ soma (`getAnimations`, covers transitions) | ✅ (`animationend`) |
| keyframes registry + tokens | ✅ | ✅ |
| placement-aware | ✅ `data-side` | ✅ `data-placement` |
| **The `--event` moment (perceptual signature)** | ✅ `signatures` + sema (intent/hold/sequence) | ❌ **doesn't exist** |
| **Both moments integrated** | ✅ | ❌ (only `--state`) |
| physics / FLIP / orchestration (JS drivers) | ✅ `spring` / `rect` / `waapi` | ❌ |
The **`--event` moment integrated with the perceptual signature** (and with
sound/haptic) is what no current framework has. Chakra animates the state;
UIX animates the state **and** the occurrence, keeping them distinct.
---
## 17. Naming decisions
- The state-moment attr: **`data-animation-style`** (NOT `data-motion`,
which is reserved for the reduce-motion pref `allow`/`reduce`).
- The event-moment attrs: the `data-event-*` sema already stamps.
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Cleanup (F7)** ✓: NavMenu's directional `data-motion` (a planned rename)
**does not exist** in the code — moot. `data-motion-ref` and the "TSC
`event:*` scope" are discarded (no real use).
---
## 18. Implementation phases
The "beat every framework" roadmap. **F1–F6 implemented:**
- **F1 — The two-moment foundation** ✓: the types (`MotionConfig`
{keyframes, signatures, presets}, `EventSignature`, `StatePreset`) +
generation (both surfaces) + the `EngineMotion` engine (the `uix.motion`
service, relocated to `$motion` in the refactor) + validation + tests.
- **F2 — The cross-modal signature** ✓ (beats SwiftUI): `events.css`
migrated to `signatures` (§15); the signature's `motion` + sound + haptic
come from the SAME event (a coupling SwiftUI's `.sensoryFeedback` leaves
loose).
- **F3 — Polished presence** ✓ (matches Base UI): Dialog/Popover/Drawer/
Accordion migrated to presets; **per-component duration + easing**
overrides (`--motion-duration/ease-{enter,exit}` + `@property
inherits:false`), inset-aware slide (`--motion-slide-leave`), the
measured-height bridge (`--height`). Zero timing regression.
- **F4 — The mechanical JS engine** ✓ (matches Framer): the `spring` (real
physics) / `waapi` / `rect` (FLIP) drivers; `Presence.motion` →
`motion.run(node, phase)` (the engine as the `uix.motion` service,
consumed by soma and eidos **with no coupling**); a real Dialog bouncing.
- **F5 — Choreography** ✓ (matches Material 3): declarative stagger
(`--motion-stagger-{index,each}`), the `shared-axis-x/y` + `fade-through`
presets; container-transform via the `rect` driver.
- **F6 — Token rigor** ✓ (matches Carbon): the raw signature **tokenized** —
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 duration scale gains the long stretch (`slower` 400 · `deliberate` 600 ·
`emphatic` 800 · `sustained` 1000ms). *(Corrected 2026-07-06: F6 originally
set the neutral/affirm/fulfill announces to `deliberate` claiming alignment
"with the book's holds-by-intent" — that aligned with the DRIFTED runtime
table, not the book. Per the book's regions (TABLA 32.2): neutral/affirm =
`moderate` ("breve"), fulfill = `slower` ("breve-media"), and the long end
(`emphatic`/`sustained`) belongs to risk/threat. The hold never truncates a
signature — the visual channel awaits the expression, capped by
`MAX_EXPRESSION_WAIT_MS`; see decisions/book-deviations.md D.12.)*
Shared-axis travels the canonical
`--motion-distance-xl` (30px); fade-through uses `--motion-scale-through`
(0.92); `press`/`commit` snap to the existing scale (sub-perceptible). The
**`emphasized`** easing (M3 emphasized-decelerate). **Productive/
expressive sets** (Carbon): `primitives.motion.expressive` emits a
`[data-motion-set='expressive']` scope remapping the easing — productive
is the default. Distance-scaled duration stays a **pairing convention**
(distance token ↔ duration token), not a runtime formula (no CSS consumer
today — the `rect` driver would do it by measurement if a consumer asks).
- **F7 — Extensibility + typegen** ✓ (beyond everyone): the `--state`
moment's preset names are **type-safe + app-extensible** via an
augmentable registry — `EidosMotionPresets` (mirroring
`SemaChannelSignatures`), with `MotionPresetName = keyof
EidosMotionPresets | 'none' | (string & {})`. An app adds type-safe
presets with `declare module '$uix/eidos' { interface EidosMotionPresets
{ … } }`: the `motion` prop autocompletes them and a typo is a compile
error. The engine (`$motion`/`uix.motion`) stays `string` (open at
runtime) — the registry is compile-time ergonomics over the props; a test
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
pins the built-in set against it. NavMenu's directional `data-motion` no
longer exists (the rename is moot). `transition` with named groups stays
**deferred** (no consumer; `getAnimations({ subtree: true })` would cover
it).
**Roadmap F1–F7 complete.** Every phase kept **zero regression** for anyone
not using `motion` or firing new events.
---
## 19. Out of scope / deferred
- A `motion-one` adapter or another external JS engine (opt-in, never a base
dependency).
- Exotic drivers (complex timelines) — only with a real consumer.
- Widening durations to 7 steps (Chakra-style) — only if 5 falls short.
---
**Revision notes**: 2026-06-21 — closure of the `motion` prop's domain
(§D.12): the **state domain** (the dedicated `data-motion-state` attr + the
`select-pop` emphasis preset, `<Card>` pilot), **content loops**
(`spin`/`pulse`/`ping`/`bounce`, un-gated infinite rules) + the `<Motion>`
exit fix (`tick` no-op), the `[data-debug-stagger]` debug affordance, and
the first **named built-in JS** preset `spring-pop` (the `spring` driver's
debut, registered on `ActiveEidos`; demonstrated on
`Popover.Content motion="spring-pop"`). Loops+lifecycle demo at
`web/routes/demos/motion`. Earlier (2026-06-04): F1–F7 implemented + the
engine relocated to `arts/motion` (the `uix.motion` service).
If the code diverges, the code wins; open an issue. References: the
historical seed `GUIA_IMPLEMENTACION_SEMAUIX.md` (§4.3, §7, §11 — the
event/state/signature model),
[`active-architecture.md`](../architecture/active-architecture.md) §5/§6
(the causal chain, the attrs), THEMING (tokens),
[`soma-architecture.md`](../architecture/soma-architecture.md) (Presence),
[`sema.md`](../architecture/sema.md) (signature/signals),
`arts/adom/README.md` (reduced-motion).

Powered by TurnKey Linux.