The ergonomic companion to `motionAttrs`: wrap any content to animate it IN and
OUT. `<Motion motion="scale-fade">…</Motion>` plays the preset's enter on mount
and its exit on removal (Svelte `out:` flips to `data-state='closed'` + retains
the node for the eidos-declared duration, like `<Cascade.Item>`). Rides the
existing state-presets — no new mechanism, no JS engine.
- morfo `motion.ts` (scope ['eidos']): one Provider part declaring `data-state`.
- eidos `motion/`: component (spreads `motionAttrs` + `out:` retention) + types +
index + README (incl. the `<Motion>` vs `motionAttrs` table + the content-domain
boundary).
- badge demo: a `<Motion>` show/hide example next to the prop showcase.
Caught + fixed a real collision: `data-motion` is already the reduced-motion prefs
attr (`[data-motion='reduce']`), so the wrapper emits NO part-presence marker (a
prefs `closest('[data-motion]')` would have matched it) — only the functional
`data-animation-style` + `data-state` from `motionAttrs`.
Verified at runtime: enter (data-state=open → `scale-in, fade-in`, no data-motion)
and exit (Hide → retained with data-state=closed → `scale-out, fade-out`). `check`
clean for the morfo + component + demo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First real consumer of `motionAttrs`: `<Badge>` gains a `motion` prop so a
designer can apply any registered preset as the badge's entrance — `<Badge
motion="scale-fade">` plays the preset's enter on mount, zero per-component
machinery (the component just spreads `{...motionAttrs(motion)}`).
Badge is the clean content-domain pilot: pure eidos primitive, no morfo events,
no own `data-state` (so the helper's presentation `data-state="open"` doesn't
clash — unlike Card, which has selected/idle + a built-in emerge). Additive +
non-breaking: `motion` defaults to undefined (no animation).
- badge/types.ts: `motion?: MotionPresetName`.
- badge/badge.svelte: spread `motionAttrs(motion)` on the span.
- badge demo: a "Motion" showcase (fade / scale-fade / slide-fade + Replay) —
the prop demonstrated per the testbed doctrine.
Verified at runtime: the three motion badges render `data-animation-style` +
`data-state="open"` with the right enter `animation-name` (fade-in / scale-in,
fade-in / slide-from-bottom, scale-in, fade-in). `check` clean for badge. (A
fresh badge screenshot was blocked by preview-infra flakiness this round; the
enter keyframes themselves were confirmed visually earlier via the cascade.)
Establishes the pattern: any component becomes animation-selectable by accepting
`motion` + spreading the helper. RFC §D.12 (b).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The universal selector for the CONTENT domain (RFC §D.12). `motionAttrs(preset)`
returns the attrs that make ANY element play a registered preset's enter on mount:
<div {...motionAttrs('scale-fade')}>…</div> // fades + scales in on mount
Resolves the open "how does content fire" decision — and reverses the §D.12 plan's
guess. Instead of a new `data-motion-play` trigger (which would need a generator
change + a base.css regen, currently entangled with an unrelated `press`/chronos
WIP), it reuses a constant presentation `data-state="open"` — the same mechanism
`<Cascade.Item>` already uses for content. The event/state/content line is about
who OWNS the animation (firma / state-machine / the prop directly), not about
whether the CSS trigger is `data-state`: content is "always shown" with no machine
to flip it. Reuses the enter/exit + reduced-motion rules verbatim; `'none'`/
undefined opts out; flash-free when spread in markup (`backwards` fill).
Verified in-browser: an element with the produced attrs gets `animation-name:
scale-in, fade-in`, enter progresses 0%(op0,scale .985)→50%(.84,.998)→100%(1,1);
`'none'` returns no attrs. `check` clean for the new file + export.
Step 2 (pending): a `<Motion>` wrapper (out: retention → exit) + wiring the
`motion` prop into real components.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`<Cascade>` was the last component using `animation` as its preset selector — a
vestige of the coordinated `cascade-*` system retired in Plan A. Every other
animatable component (Dialog, Popover, Drawer, …) already uses `motion`. Renames
`<Cascade>`'s prop + its context field `animation` → `motion`, so the framework
has ONE universal selector name (RFC §D.12 step a). `MotionPresetName` and the
low-level `data-animation-style` attr are unchanged.
- cascade: types `animation?` → `motion?`; context `CascadeContext.animation` →
`.motion`; component destructure + getter; item reads `ctx?.motion`.
- docs/demo: README props table + usage, index usage, panel-cascade demo.
Verified at runtime: `<Cascade motion="scale-fade">` produces identical wiring —
items carry `data-animation-style="scale-fade"`, `animation-name: scale-in,
fade-in`, structural index 0/1/2/3. `check` clean for the touched files; no
dangling `animation`-prop reference remains (only the `data-animation-style` attr).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the design framing agreed this session, BEFORE implementation:
§D.12 — One universal `motion` prop on any component; the discriminant ("does the
animation realize a perceptual event?") decides the domain: event (firma, morfo+sema,
the prop is override/violation) / state (data-state preset) / content (prop is
primary). Supersedes §5's M5/M6 "motion vs animation separate" — its justification
(the coordinated `cascade-*` system) was retired in Plan A. Honest positioning:
content/state = parity with Framer/SwiftUI; event = above (motion coupled with
sound/haptic from one evaluated signal). Caveat: the JS engine (spring/handoff) is
built but dormant — the "above" on the engine axis needs a first spring preset.
§D.13 — The contracts the CSS model demands + failure modes, separating what the
mechanics already guard from what's deferred:
- structure: `:nth-child` ignores comments/`{#if}` (guarded); intermediate ELEMENT
wrapper breaks the count (the contract); a cascading wrapper becomes its own scope
(`inherits:false` isolates the index); `display:contents` = known dead-zone.
- exit: animated surface exits via Presence/`out:`, NEVER raw `{#if}`; unit-exit works
today (parent opacity carries the children); per-child staggered exit needs JS
lifecycle (PresenceGroup, deferred) → the container-driven rule is enter-only.
- debug: the index is `inherits:false` (no ancestor can step on it); a
`[data-debug-stagger]` mode to surface index/delay/name is pending.
Pointer added in eidos-motion.md. No code changed — design record only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the menu-family event-driven cascade: a SubContent fly-out now cascades
its items in on open, same as the root panel. The SubContent has its own
`data-state`, so the existing container-driven preset rule applies unchanged — it
only needed the `[data-stagger]` marker on the panel + `--motion-stagger-each` on
the sub-content recipe selector. Its items are the same `DropdownMenu.Item`
(already `data-animation-style="fade"`). Menubar inherits via the shared
DropdownMenu.SubContent.
Verified at runtime: SubContent panel carries `data-stagger` + `data-state=open`,
`--motion-stagger-each: 20ms`, items `name:fade-in` with consecutive `:nth-child`
index 0/1/2 (a flat submenu cascades cleanly). `check` clean for the touched
components.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the just-landed DropdownMenu event-driven item cascade to its siblings
so the framework is coherent — every menu in the family fades its items in
staggered on open, same mechanism, zero new generator code (rides the
container-driven preset branch shipped with DropdownMenu).
- ContextMenu (own recipe): Content marks the panel `[data-stagger]` +
`--motion-stagger-each: var(--motion-stagger)`; item / checkbox-item /
radio-item carry `data-animation-style="fade"`.
- Menubar (ONE line): soma `Menubar.Content` delegates to `DropdownMenuContent`,
so its panel already carries `data-dropdown-menu-content` (→ the dropdown
recipe rhythm applies) and re-uses `DropdownMenu.Item` (→ already
`data-animation-style="fade"`). It only adds the `[data-stagger]` marker.
Verified at runtime: ContextMenu (scrubbed opacity gradient 0.96/0.84/0.00),
Menubar (panel carries `data-stagger` + `data-dropdown-menu-content`, items
`name:fade-in`, structural index from `:nth-child` 0/1/2/6/7). `check` clean for
the touched components. (Preview tab runs backgrounded → rAF paused; verified by
scrubbing `animation.currentTime` via WAAPI rather than waiting for playback.)
Deliberately NOT rolled out: select / combobox / command — live-filtered or
long-scroll lists where an open cascade is questionable UX.
Docs: ContextMenu README (Motion parity note), Menubar index doc, DropdownMenu
README (family cross-reference), RFC §D.11.4 (rollout).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Productizes the event-driven cascade on a real component (not the explicit
`<Cascade>` orchestrator). When a DropdownMenu opens, its items fade in
staggered by structural order — the appearance flows from the `open` (emerge)
event, nothing in the app wires it.
Architecture: a +1 GENERIC selector branch in the motion generator, NOT a
per-item `data-state`. A `menuitem` isn't open/closed — the menu is — and eidos
writing a state attr is a smell. The new container-driven form
`[data-stagger][data-state='open'] > [data-animation-style='X']` fires each
child's ENTER off the container's `data-state` (which soma already writes via
the morfo `commits`); items carry only `data-animation-style`. ENTER only: the
coordinated EXIT cascade (retain the container until children finish) is the
deferred soma PresenceGroup work, so a closing container can't drive a child
exit that would be cut off on unmount.
- render-css.ts: `renderCssPresetRules` adds the container-driven enter branch
(same declarations, shared comma selector) — reuses the existing presets +
the foundation `:nth-child` stagger index; zero new system.
- DropdownMenu: Content marks itself `[data-stagger]` +
`--motion-stagger-each: var(--motion-stagger)` (20ms, themeable, no magic
number); item/checkbox-item/radio-item carry `data-animation-style="fade"`
(overridable). `fade` so item opacity doesn't fight the panel's own scale.
- regen generated/base.css (8 css presets gain the container-driven enter).
Verified in browser (frozen frame): index from `:nth-child`, `delay = idx ×
each`, opacity gradient in flight (Ruler 0.98 → Grid 0.89 → Guides 0.69, Log
out 0.00). Honest limit (documented): only DIRECT-child items cascade — items in
a <Group>/<RadioGroup> DOM wrapper aren't `:nth-child` of the panel, so they
appear instantly (the "transparent intermediate").
Docs: dropdown README §Motion, cascade README §2 (concrete consumer), RFC §D.11.4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- dialog + drawer adopt data-depth="modal" (surface·border·base-shadow from the plane);
recipes pruned of orphan content-bg/border/shadow. The bundle's on-surface font fixes
the Times New Roman of portaled content (Decisión 8).
- Dialog.Close inherits the dialog size via context.ts, capped at md (sm→sm, md/lg/xl/full→md;
full is layout, not a bigger control — Radix/Mantine/MD3). THEMING §5 container→part norm.
- Dialog title → h3 typography role (--dialog-title-* points at --style-h3-*, override knob kept).
- Dialog trigger + close compose the framework <Button> via the asChild `child` snippet
(the trigger was a passthrough that rendered soma's native <button>); variant/color/size
defer to the canonical Button defaults (solid/primary) — no hardcoded ghost/neutral.
- demo layout (web/routes/uix/+layout@.svelte): "Display" control — density·scaling·border-width
applied to :root (reaches portaled overlays) to observe the interrelated theming axes live.
- update the depth-plane test (z is token-only; on-surface font) + the archetype audit hand-off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cascade is no longer a separate system — it's the existing state-presets + the
Material stagger + one foundation rule. Retires the --cascade-* reinvention.
- render-css: foundation structural-index writer — `[data-stagger] > *:nth-child` →
`--motion-stagger-index` (forward) / `:nth-last-child` → `--motion-stagger-index-rev`
(reverse). Nobody writes the index; soma never writes a visual var. Every preset's
enter/exit rule consumes it (parallel = `--motion-stagger-each` 0, cascade = N).
- Cascade rewritten as a thin EXPLICIT orchestrator: marks `[data-stagger]`, reflects
open→data-state, passes the preset to items via context; the `out:` retention flips
to data-state=closed (reusing the exit preset), reading the duration via dom.getWindow.
Drops the whole `--cascade-*` namespace + bespoke keyframes.
Docs: MOTION_SERVICE_RFC §D.11 — the final model (three orthogonal axes sema/motion/
eidos, the full lifecycle, the realization ladder snap->transition->animation->JS, the
firma-vs-realization seam, the cascade = existing system + one rule, the motion service
as the engine). eidos-motion aligned.
Verified: browser (enter scale-in/fade-in + nth-child index, exit reverse, zero inline
writes), npm run check 0 mine, motion.test 22/22, eidos-lint 0 invalid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the parallel motion-coordination service per the redesign: animation is a
channel of the EVENT's firma (sema owns it across all channels), not a parallel axis.
Code is gone; the RFC stays as historical record with a retirement banner.
- morfo: drop MorfoPart.animation + its schema/compile/types/exports
- arts/motion: drop CoordinatedPreset / MotionConfig.coordinated
- eidos: drop BUILTIN_COORDINATED_PRESETS / renderCoordinatedPresetRules / the
animation:none neutralization + registry block; regen generated/base.css
- soma: Presence reduced to a single-surface island; delete presence-group /
dom-cascade / coordination; dropdown-menu loses the cascade wiring
- delete Reveal / Rail (morfo + soma + demos)
Docs: MOTION_SERVICE_RFC gains the retirement banner + §D.8 (retirada) + §D.9
(verified model: morfo->soma->sema->eidos pipeline + the two hard rules — soma
never writes a visual --var; data-event-* is a single-target stamp, not a bus) +
§D.10 (Fase 1 prototype). Fix stale "5 canales" claim in GUIA §11; eidos-motion +
dropdown README aligned.
Fase 1 prototype (web/routes/temas/animations/panel-cascade): validates the model
end to end — panel->cards cascade (enter/exit), dynamic removal with Svelte out:
retention, nested cascade — all via :nth-child + custom-property inheritance, with
zero JS visual writes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coherence audit (ARCHETYPE_COHERENCE_AUDIT_2026-06-19): 8 archetypes + 8 closed
decisions, the §13 construction contract, per-component deviation matrix (88/117
deviate). Master guides (soma/COMPONENT_GUIDE, eidos/components/README) point to it.
A1 — elevation bundle via `data-depth`: the plane paints surface·border·shadow·halo
(added `border` to the depth bundle). z-index is NOT painted — stacking is a
positioning concern owned by the floating-wrapper. Popover migrated to the overlay
plane (panel + arrow consume it); recipe orphans pruned.
Popover fixes: arrow un-clipped via an inner [data-popover-viewport] (Radix-style;
the absolute arrow escapes the scroll clip), sized via density tokens (not soma's
hardcoded 10×5), border-seam overlap keyed on data-side, hover-bounce guard in
scheduleHoverOpen (ignore hover-open when already open). Dialog.Close composes
IconButton/Button (neutral) — closes the Lote ② debt.
Option lists: density-driven padding/radius via --space-* + concentric radius,
ScrollArea adoption, emerge open/close events (combobox/select/command), and the
canonical option-highlight in archetypes.css.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- <ColorField.Input swatch> renders an opt-in leading colour chip showing
the field's current value, sized to the control icon (--icon-size-{size})
via a concrete token — never 1em, so it cannot recompute/jitter.
- field-segment-state: drop the active-segment font-weight 600; bold widened
the ch-based segment ~0.5px, resizing the highlight on every increment. The
accent background already marks the active segment.
- color-field stays full-width like the whole field/select family — width is
container-driven, not field-driven (no per-component exception).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the per-field / per-picker duplication the audit surfaced:
- Shared control trigger — new field/field-control-trigger.css selected by a
`data-field-control-trigger` marker: flush (no border, no shadow), UNIFORM
neutral hover tint (not the field accent), font-relative size. Migrated
date-picker + the password visibility toggle; deletes their per-picker chrome.
(date-range / time / time-range pickers still to migrate.)
- Shared segment state layer — new field/field-segment-state.css selected by a
`data-field-segment` marker (date / time / color fields): one hover (editable)
+ active (focus) rule. Fixes a specificity bug where the hover selector
out-ranked :focus, so hovering the active segment downgraded its highlight.
- Segment fixed width — min-inline-size: calc(2ch + 2*segment-px) +
font-variant-numeric: tabular-nums so a segment never reflows when its value
changes 1<->2 digits or between glyphs (no horizontal jitter). date-field done.
- date-range-picker trigger uses the same calendar icon as date-picker (was a
distinct calendar-range glyph).
- Generated element ids use the framework-neutral `uix-` prefix instead of the
layer name `soma-` (createId / useId + announce / menubar inline ids) — the
doctrine that bans data-soma-* / --soma-* keeps a layer name out of ids too.
- field.css: label + helper text scale one step below the control (font-relative
calc); optional-indicator weight to regular.
Foundation tokens added: --field-segment-hover-bg, --field-control-trigger-hover-bg.
Verified in-browser by measurement (date-field segments: states + no-jitter;
date-picker trigger: flush + uniform hover). check = 58 baseline (0 new);
recipe-css-contract clean (only pre-existing palabras WIP).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supersede the "compact control md=14" archetypes (Fase 7) with a single
rule: control text scales 1:1 with its size (md=16) across all components,
so changing --font-size-md re-themes the whole system. Applied to button,
badge, breadcrumb, calendar, pagination, radio-group, toolbar, file-upload,
tag-group, stepper, toggle, tooltip (the field family was already 1:1).
Button + search-field icons now follow the font 1:1 (icon ~= font+2;
density-independent, like the type scale). Fix button icon vertical
centering: the [data-button-icon] slot didn't flex-center, so the inline
<svg> sat on the text baseline ~1px below the button center.
Exceptions left as-is (not control text): avatar (initials sized to the
circle diameter), accordion (section-heading prose scale), password-field
icon-size (it is the visibility-trigger button box, control-coupled by
design), radio-cards (icon follows the card title).
THEMING.md section 5 rewritten: 1:1 universal mapping + "density is
orthogonal to typography" (density scales space + control-height only;
font + icon ride only the global --scaling zoom).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Field font scale: input 1:1 (size N -> --font-size-N, md now 16px), label one
step below (10/12/14/16/18). Recipes: field, spin-field, date/time/color-field,
search-field, password-field, select, editable, tags-input. Segmented-field
labels via CSS calc; generic Field label via label-font-size tokens.
- Picker triggers (date/time/range): box now scales off the local
--_X-field-height + font:inherit so the 1em icon follows the field font; added
the missing xs size variant to time-picker + time-range-picker.
- color-field/time-field commit-set fires + sounds on increment (compare by
ColorValue.hex; time-field passes fallbackTarget for the double-registered
'input' part).
- color-field format-select: reverted the segment-active tint back to its focus ring.
- Docs: THEMING.md field font rule; COMPONENT_COHERENCE_AUDIT.md.
words / palabras / chronos untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prototipo descartable (web/routes/temas/animations/menu-native) que valida la
Decisión A del re-diseño del servicio de motion: el lifecycle de presencia se
apoya en Svelte nativo ({#if} + transition:|global) en lugar de la maquinaria
custom de F2 (DomCascade/pending/beginEnter).
Validado en navegador real:
- entrada escalonada sin ease-vs-jump; salida con stagger invertido
- exit-heavy nativo (panel retenido hasta el ultimo item, sin pending())
- adicion dinamica -> entrada individual (el contenedor arbitra opening/closing)
- roll-up del panel como firma de cierre opcional (when: exit:'after')
RFC Apendice D.7 documenta resultados + hallazgos (grid-template-rows no anima
por WAAPI; usar height/interpolate-size).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents the second coordination mode (DomCascade) wired in M9 F1/F1c/F2: the design, the contract (staggerChildren), pending() exit-heavy, the dropdown-menu cascade, and the four coexistence fixes (transition collision, disabled opacity, dismiss-fade panel signature, trigger/dismissal toggle). Updates the status note, TOC and §15 roadmap; adds the children-DOM cross-link to eidos-motion.md and an opt-in 'animation' Motion section to the soma README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F1c (exit-heavy): DomCascade.pending() aggregates the items' finished so the owner Presence holds the subtree until the exit cascade settles (PresenceOptions.pending, Presence.startPhase). beginEnter drives the enter from the off-state with the transition suppressed (items mount in the on-state, so a passive mirror would ease toward off instead of jumping). pending() settles each finisher (then(noop,noop)) so one cancelled row can't collapse the wait.
F2: wire the children-DOM cascade into the real dropdown-menu. Content part declares animation.surface+staggerChildren; provider routes an opt-in 'animation' prop, runs a DomCascade over getCascadeRows (every visible row incl. disabled), forwards a pending hook through the floating shell. Coexistence fixes surfaced by the menu: item rows hand their transition to the cascade-* preset via a higher-specificity rule (longhands keep the stagger delay); disabled opacity gated off during the cascade; the panel's own dismiss-fade signature suppressed in cascade mode (data-cascade) so it doesn't fade the panel before the rows finish; the trigger is excluded from the menu's dismissal so the toggle closes (pre-existing bug). Verified in a real browser (Playwright): enter+exit cascade with reversed stagger, all four close methods, disabled row fades. dom-cascade 9/9, check 0 new errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El piloto M9 (dropdown-menu) destapó que el PresenceGroup coordina children
que son `Presence` REGISTRADOS (Reveal/Rail), pero un menú real es owner-
`Presence` (el content monta como unidad) + children-DOM (items por selector,
sin `Presence`). 3 grietas en 3 capas: registro, index per-item, y escribir
una CSS-var per-element (que ActiveDom no cubría).
Modo children-DOM (la 2ª coordinación): el owner PROPAGA su lifecycle a los
items DOM — escribe data-animation-style + data-starting/ending-style (espejo
del owner) + --motion-stagger-index/-count (por orden DOM), reutilizando el
preset coordinado de eidos SIN cambios (los items quedan con los mismos attrs
que un child-`Presence` produciría).
F1 (núcleo):
- morfo: `MorfoPart.animation.staggerChildren?: string` (types + schema +
invariante: requiere surface, excluye children).
- adom: `ActiveDom.writeProperty/removeProperty` per-element (la pieza que
faltaba; + stub disabled).
- soma: `DomCascade` (`dom-cascade.svelte.ts` — `sync`/`clear` puros y
testeables, `watch()` corre el $effect espejo) + barrel export.
F1b (validación aislada): demo `/temas/animations/dom-cascade` (owner Presence
+ items DOM + DomCascade.watch). Cazó un bug que el test no veía: DomCascade
usa $effect → debe ser `.svelte.ts`, no `.ts` (el test solo ejercita sync/clear,
nunca watch()). Cascada de ENTRADA verificada en navegador.
PENDIENTE (en la memoria del proyecto): F1c exit-heavy (el owner espera a los
items — getAnimations no ve subtree), F2 el dropdown-menu real (focus-trap/
dismissal/submenús), y volcar §M9 al RFC.
Tests: dom-cascade 5/5, morfo 70/70. svelte-check: 0 errores en mis archivos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Un componente coordinado puede componer la cascada (motion) con sonido/háptico
(sema) en el mismo evento. El cabo: el meta-canal visual de sema estampa
data-event-* siempre que el evento tenga algún canal (channels != []), así que
channels:['sound'] arrastraba la firma visual genérica (present-rise), que pelea
con la cascada.
Solución EN MOTION (sema intacto): eidos neutraliza su PROPIA firma visual sobre
las superficies coordinadas. render-css.ts > renderCoordinatedPresetRules emite
`[data-animation-style='cascade-X'][data-event-phase='active'] { animation: none
!important }`. La firma usa `animation` -> muere; la cascada es `transition` ->
sobrevive. Aditivo: inerte hasta que un coordinado dispare un evento sema.
Ejemplo: Reveal pasa a modo (c) — open y close declaran channels:['sound']
(emerge suena, pitch 600) + expression:'family-default'; suena al abrir y al
cerrar a la vez que la cascada, sin pelea visual. Rail queda como modo (b) puro.
Layout de /temas/animations con events:{ sound: true }; la demo /reveal explica
el modo (c).
Honestidad: la versión inicial del RFC Apéndice B afirmaba que el modo (c)
componía "gratis" — falso (verificado en engine.ts/visual.ts). Corregido: el
Apéndice B documenta ahora el acoplamiento real y el opt-out en motion.
Tests: eidos/motion 25/25 (neutralización) · morfo 70/70 · morfo:vocabulary
limpio. Audio verificado en navegador (suena al abrir y cerrar).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Continuación del servicio de motion-coordination (sobre M1–M6 ya commiteado).
- M5 — naming `animation` / `motion` SEPARADAS por rol (no unificar: tras M6
son sistemas distintos — `motion`=momento `--state`/`data-state`/eidos;
`animation`=coordinado/`data-starting-ending-style`/soma). `animation` gana
type-safety vía el registry augmentable `MotionCoordinatedPresets` +
`CoordinatedPresetName` en `$motion` (la capa compartida, para que soma lo
tipe sin importar eidos); eidos lo puebla con `cascade-slide/-fade/-scale`
por declaration-merging — espejo de `EidosMotionPresets`.
- Reversa fluida — JS handoff (§8.3). El motor expone `MotionHandle.peek()`
(posición + velocidad por propiedad) y la reinyecta en la fase inversa vía
`MotionContext.handoff`: el `spring` continúa desde la posición/velocidad
actual en vez de reiniciar desde el `from`. Orquestado enteramente en el
engine (`takeHandoff` — captura en `cancel`, consume en `run`, y cancela un
run en vuelo cuando el caller no pre-cancela): cubre el camino island
(overlay-spring) y elimina un apilamiento preexistente. La reversa de los
coordinados (transiciones CSS) ya era fluida.
- Demo `/temas/animations/compuesto` — un `Rail` de `<Button>`s que usa los
presets coordinados PREDEFINIDOS de eidos (cero CSS de animación en la
página); el selector cambia `animation` en vivo y cada Button compone su
press (firma sema) con la cascada (motion coordinado).
- Docs — RFC Apéndice B (convivencia de los tres sistemas visuales: firma
`--event` / state-preset `--state` / coordinado, con diagrama + ejemplo
Button-con-tokens), §5 reescrito (separadas por rol), §8.3/§15 al día, y
cross-link del tercer eje desde eidos-motion.md.
Tests: arts/motion 9/9 (handoff) + eidos/motion 24/24 (M5 paridad).
svelte-check: 0 errores en mis archivos.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Servicio de motion cross-layer: morfo declara superficies animables +
coordinación; soma coordina presencia/lifecycle; eidos posee lo visual;
arts/motion ejecuta por-nodo. RFC en eidos/MOTION_SERVICE_RFC.md.
- M1 — contrato MorfoPart.animation (types/schema/compile); children
como { enter?, exit? }.
- M2 — PresenceGroup (soma/layers/presence-group.ts), rune-free;
Presence.group descubre el coordinador por context.
- M3 — exit con retención de DOM (§8.1).
- M4 — interrupción/reversa (§8.3): token de generación + motion.cancel
en flip + toHandle resuelve finished en cancel (sin AbortError suelto).
- M5 — prop `animation` enrutada a las parts surface:true del morfo
compilado (routeAnimation); Panel/Item emiten data-animation-style.
- M6 — stagger auto-derivado del orden de registro (--motion-stagger-*,
inversa en exit) + presets coordinados en la librería de eidos
(MotionConfig.coordinated; cascade-slide/-fade/-scale) que reaccionan
a data-starting/ending-style, NO a data-state.
Helper Coordination (soma/layers/coordination.ts) extraído y validado
por DOS consumidores reales: Reveal (raíz virtual + Panel owner) y Rail
(raíz=owner, together). Demos en /temas/animations/{reveal,rail,
presence-group}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New component-authoring rule, per request: when building a component (or its
demo, or any UI), compose the framework's existing soma/eidos components rather
than re-implementing primitives inline. If a needed building block does not exist
as a framework component, flag the gap so it gets built as a proper reusable
component (its own morfo + soma + eidos) and then composed — never an ad-hoc
reinvention that drifts from the system. The picker family (A27) is the canonical
example of composing existing components.
Full rule in COMPONENT_GUIDE "Before You Start" §4; one-line pointer added to
CLAUDE.md Key Conventions so every session loads it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the arts/ audit (docs/process/arts-audit-2026-06.md):
- connection: drop 'export * from helpers' (leaked internal builders) and name-export the public error surface; remove the redundant EngineConnections.close() alias (consolidated to closeConnection, consistent with openConnection/reconnectConnection/closeAll) and retarget its destructure-safety test.
- http: extract the inline ATTEMPT_COMPLETED diagnostic message into a named builder (attemptCompletedLogMessage); output preserved.
- color: drop stale 'Phase 0 / not consumed yet' comment (uix.color is consumed by Eidos).
Verified: vitest connection/http/color green, type-check clean, prettier clean. The QrCode morfo fix (literal('path')) is left in src/uix/morfo/schema.ts because that file also carries unrelated in-progress animation-morfo work — not swept in here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There is no sium-specific Svelte adapter by design: forms (createForm / Form.Provider) consume any schema via ~standard, Form.AutoFields renders via ~sium introspection, and langs/logger inject through createEngineSium(). sium is page-scoped and not an ActiveUix service. Fixes README (Estado, dep rules, Integracion Svelte, troubleshooting) + core/types.ts comments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
THEMING §5 documented "prominent icons (cards): the icon does NOT follow its
title, it bumps to xl/xxl" — the pre-continuation approach. The final decision
and the shipped code reject it: the radio-cards recipe sets icon-size to
sm/sm/md/md/lg (16/16/18/18/20, following the title) with an explicit comment
("the icon follows the title; a bigger card icon comes from a bigger title,
never from inflating the icon"). The doc had drifted from the code.
Corrected §5 to state the implemented rule: cards/titles icons follow the
title's font; prominence comes from a bigger title, never an inflated icon.
Found while verifying that CONTINUE-icon-type-scale.md's decisions actually
match THEMING before retiring it — they did not, on this point.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The bullet linked THEMING_AUDIT_2026-05-27.md, which does not exist (the
2026-06-01 audit itself flags the link as broken). Repointed to the audit that
does exist, THEMING_AUDIT_2026-06-01.md.
- eidos-motion.md was described as "propuesta motion (deferred, partially
superseded by TSC)" — the same stale contradiction fixed in §14. It is the
implemented two-moment motion system (F1–F7). Corrected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the renumber-safe split of THEMING.md.
- §8 "añadir componente" + §9 "definir theme" -> src/uix/eidos/THEMING_GUIDE.md
(E4 authoring guides).
- §15 "comparación con librerías" + §17 "FAQ — decisiones polémicas" ->
src/uix/eidos/THEMING_NOTES.md (E3 comparison + rationale).
THEMING.md keeps numbered pointer-stubs at §8/§9/§15/§17; §16 anti-patterns,
the ## Referencias section, and the dated §20-34 changelog stay in place. All
34 sections and every "THEMING §N" citation across the corpus + code survive.
THEMING 2571 -> 1930 lines across the three commits; it is now a leaner E1
layer reference with TSC (E2), the guides (E4) and the notes (E3) as siblings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First piece of the THEMING split, using the renumber-safe stub approach so the
many "THEMING §N" citations across the corpus + code keep resolving.
§7 (Token Scope Contract) and §18 (universal coverage, v2.2) move to a new
src/uix/eidos/TSC.md — the eidos visual canon (E2), with CANON.md-style
frontmatter. THEMING.md keeps numbered pointer-stubs at §7/§18, so section
numbers (and therefore §23/§25/§26/§27/§28 citations) are untouched. 2550 -> 2271
lines; 34 headers intact, TOC anchors still resolve.
Remaining split pieces (own commits): guides §8/§9 -> E4, comparison §15 +
FAQ §17 -> E3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
THEMING §14 described motion as "deferred / data-motion-ref does not exist /
superseded by the TSC event:* scope". eidos-motion.md says the opposite: the
two-moment model is implemented (F1-F7, 2026-06-04), the engine was relocated to
arts/motion as a service, and it is the event:* scope that is now obsolete. §14
was a stale snapshot contradicting the canonical motion doc.
Rewrite §14 as a lean pointer to eidos-motion.md (canonical, current) with a
status note flagging the obsolete data-motion-ref / event:* framing, and keep
the theming-specific bit (the --motion-scale-lift "pickup" token). Renumber-safe
— §14 stays §14, so the §N citations across the corpus are untouched.
The full stratum-split of THEMING (TSC -> E2, guides -> E4, comparison/FAQ ->
E3) is deferred: it renumbers sections that are cited by number across the
corpus + code, so it needs a dedicated pass with a citation sweep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "3 duplicate checklists" diagnosis was wrong. There are two legitimate,
complementary artifacts plus one redundant summary:
- COMPONENT_GUIDE.md — the build checklist (ordered authoring steps 1-40 +
rationale rules A1-A37). How to build a component.
- COMPONENT_COMPLETION_CHECKLIST.md — the acceptance matrix (machine-audited
rules with severity/applicability, bound to scripts/component-audit.ts). When
a component counts as done. Merging it into the guide would break the
script binding.
Consolidation = one source per concern, not one file. Both docs now carry a
header that states their role and cross-links the other as the complement.
soma/README §9 (the only real copy) is reduced to a pointer at both; it keeps
no checklist of its own.
Flagged out of scope: COMPONENT_COMPLETION_CHECKLIST M-3.3 still says "7
families" (canon is 8 with delegate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two docs overlapped ~80% (purpose, layers, runtime parts, Soma class,
data-* contracts, naming, checklist). Give each a single role:
- README (1057→400 L): onboarding + authoring guide — purpose, membership
criterion, morfo contract (slim), component + composition pattern, new-
component checklist, and a "deep reference" map pointing into the
architecture doc. Inventory now points at the live directory tree instead
of a hardcoded (drifting) list.
- SOMA_ARCHITECTURE (969→1043 L): the complete deep reference. Absorbed the
few reference bits that only lived in the README — mergeProps, KEYS /
directional keys, focus / roving / scroll-lock, boolean attr helpers, the
internal context() helper and the reactive type-aliases — into a new
§8.bis. Trimmed its purpose section (now owned by the README).
README sections renumbered contiguously; cross-references verified, no other
doc links soma/README by section number.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the Phase 1b debt: the "## Handoff 2026-05-14" blocks embedded at
the top of arts/README, arts/adom/README and arts/format/README move to
docs/process/handoffs-2026-05.md, so the layer references read as
timeless. Same mechanical pattern already applied to the UIX READMEs in
41c3b0f9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 (E1), per the chosen scope. active_architecture.md should read as a
timeless architecture reference:
- §0 "Handoff 2026-05-14" → "## 0. Contratos mínimos por módulo": dropped the
session framing (estado acordado, open/closed questions, P1/P2/P3 findings,
frontend context); kept the minimum-contracts table as reference. The
ownership/degradation rules it stated are now timeless in active-uix/README.
- §10 "Estado actual (2026-05-17)" → a one-line §10 pointing at docs/process/.
The dated status snapshot moved verbatim to
docs/process/active-architecture-snapshot-2026-05.md (kept for traceability;
several of its "pendientes" are since done). Section numbering stays continuous.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3 (E1). After Phase 1b pulled the embedded "Handoff 2026-05-14" out, the
doc still read as session process ("tras cerrar P1", "punto crítico para
mañana", a "Regla de trabajo vigente" section). Rewrote it as a clean layer
reference: what active-uix is, the two boot paths (createActiveUix /
attachActiveUix), the minimum-contracts table (linked to the executable
contracts.ts — fixed the path: it lives in src/uix/, not active-uix/), the
ownership + degradation rules as timeless invariants, and the shell-boot example.
Dropped the process framing. Spanish kept (consistent with its sibling
active_architecture.md; coordinated EN migration is a later pass).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2 (cont.). The docs that still re-transcribe the semantic vocabulary now
name CANON.md as the single source of truth, so a reader/agent knows which copy
is authoritative when they drift:
- src/uix/README §7 reading order: CANON.md added as entry 0.
- sema/README: §intent-policy and §verbs point at CANON §4/§6 + the code.
- active_architecture §6 "Vocabularies cross-layer": points at CANON.
- morfo/README §5.5: semantic vocabulary now cited as canonical in CANON
(was pointing at GUIA).
- GUIA §1: marked as the Spanish narrative; CANON (EN) is authoritative.
Conservative on purpose: the existing lists are kept, not deleted — physically
pruning each copy down to a pointer is a separate, optional pass (it is editorial
and would remove content). Establishing the authority + the backlink already
stops silent drift, since every copy now declares who wins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The §10 "Estado actual" snapshot still said "7 familias" and "66 componentes".
Per the user: the doc must state the current canon, not a frozen historical
figure — delegate makes it 8 families, and there are 115 morfos. The rest of the
dated §10 block (its caducated "Pendiente" list) is reworked / moved to
docs/process/ in the architecture pass. Docs-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1b of the doc-corpus cleanup: reference docs should read as timeless.
Pulled the inline "Handoff 2026-05-1x" blocks out of the six layer READMEs
(README, morfo, sema, soma, eidos, active-uix) into
docs/process/handoffs-2026-05.md, with docs/process/README.md explaining that
process/ holds ephemeral, time-stamped artifacts (hand-offs, audits, plans) —
never the source of truth.
README.md keeps its useful "visión de conjunto" pointer to active_architecture
+ GUIA, minus the hand-off framing.
Deferred on purpose: active_architecture §0 (embeds the "contratos mínimos"
table other docs link to — separated during the architecture pass) and
CLAUDE.md's ~10 session hand-offs (it is the per-session instructions file;
its own step). Docs-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1a of the doc-corpus cleanup: align the framework docs against the code
(the source of truth) where they had silently gone stale.
- 7 → 8 sema families (delegate was missing) in README, active_architecture,
morfo/README and GUIA_IMPLEMENTACION.
- sema/README's intent-policy section still showed the retired flat
`intentPolicy` shape; replaced with the real two-axis SEMA_FAMILY_POLICY
(intentRequirement + intentGuidance). morfo/README's "valenced families
require intent" rule corrected: only commit + signal are
intentRequirement:'required'; the rest make intent optional.
- SEMA_VERBS lists realigned to verbs.ts (adds unselect/apply/partial/block/
move/upload, signal.inform, handle.zoom, sustain.upload, + the delegate family).
- DATA_ATTRS.md: retired the misleading "Terra Data Attributes Contracts"
header (auto-generated from a now-deleted path, redundant with the morfos)
→ marked LEGACY pointing at the morfos as the source of truth.
Dated status blocks (active_architecture §10 "66 componentes / 7 familias")
are deferred to the process-extraction step. Docs-only; no code touched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`timers.schedule/interval/scheduleAt` now accept `key: null` — the engine mints a
unique `auto:N` key via an internal counter (with a `has()` collision guard) and
the caller drives the timer through the returned handle. Naming a timer stays a
feature (cancel-by-name, `replace`, dedup, scoped `cancelAll`), not a tax on every
call. Anonymous timers live in the `auto` scope. +6 engine tests; the race-safety
path is unchanged (the key is resolved before the (id,key,version) machinery).
Uses it to close the raw `setTimeout`/`setInterval` and direct `getComputedStyle`
violations the audit flagged — instead of waiving the uix.timers / $adom rules:
- code-block, relative-time: raw setTimeout/setInterval -> eidos.timers (key:null)
- cropper: raw setTimeout throttle gate -> uix.timers (key:null)
- textarea, s-text, s-text-virtual-list: getComputedStyle(el) -> dom.getWindow(el)
The earlier audit call that these were "nil functional gain" was wrong: the rule
is the rule, and the friction (manual per-instance key invention) was removed at
the framework level rather than used as grounds to skip the rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removed orphan deprecated type aliases with no real consumers: sema `IntentPolicy`
(legacy single-axis policy, superseded by intentRequirement + intentGuidance) and
its re-export; eidos `RecipeTokenObject` (→ RecipeTokenDeclaration) and the comment
that named it. Left `IntentExpectedFamily` and `SemaEventLabel` — both are still
consumed by public morfo types / sema functions, so removing them is an API rename,
not dead-code cleanup (documented in SYSTEM-AUDIT).
Translated 3 non-quote Spanish comments to English (sema/chans/types,
sema/projection/index, morfo/year-grid) per the English-comments rule. Book quotes
stay in Spanish (they're the canonical source).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9 soma component READMEs imported from the removed $lib/util/dias and
$lib/util/colors aliases → $libs/days / $libs/color (matches what the code
actually imports). morfo/README.md said "All 66 morfos" (real count ~116) →
reworded to "Every morfo in the codebase" to drop the brittle number.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apply the icon↔type correlation (option 1): the icon follows the font the
component actually shows, no font step-up. button icon-size-sm: icon-xs→icon-sm
(follows its font); radio-cards icons follow the card title (16/16/18/18/20,
were 18-32 hand-inflated). STATIC_SIZE's font step-down stays — control-density
convention, not a bug.
Fix pre-existing button bug: the Icon's inline `width: var(--icon-size)` beat
`[data-button] svg { inline-size }`, pinning the button icon at 18px regardless
of size. button now feeds `--icon-size` from `--_button-icon-size`, so the icon
follows the button size (14/16/16/18/20). Verified in browser.
CONTINUE-icon-type-scale.md updated with the resolution + a pending audit of the
same Icon-sizing bug in other components.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icon scale large steps now track the type scale (validated by eye in a study
harness at /uix/icon-scale-study): --icon-size-xl 24→32, --icon-size-xxl 48→52,
so the icon keeps its optical weight beside its paired text. 0 impact — nothing
consumed those steps yet.
button: icon-size per size references var(--icon-size-X) (mapped by value → same
rendered px, now density-scalable) instead of hardcoded px.
Docs: THEMING.md §5 documents the type↔icon correlation; CONTINUE-icon-type-scale.md
captures the pending 1:1 work (size = font = icon per name, dropping the font
step-down) for the next session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Size was applied via `var(--qr-code-size-${resolvedSize})` (interpolated), so
the literal token names never appeared in source and the recipe-css-contract
test flagged --qr-code-size-{xs..xl} as orphaned. Use an explicit literal map so
each name appears verbatim; behaviour unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>