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/src/arts/adom/README.md

416 lines
14 KiB

eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
# ActiveDom
`adom` contiene `ActiveDom`: el servicio DOM reactivo de aplicación.
## Qué es hoy
Ahora mismo `ActiveDom` no es un bus de eventos semánticos ni un reflector de
`data-event*`.
Su responsabilidad actual es más pequeña y más concreta:
- exponer el ancho de viewport de forma reactiva
- resolver el breakpoint actual
- mantener la definición de breakpoints de la app
- resolver valores responsive
- ofrecer helpers de consulta (`isAtLeast`, `matches`)
- aplicar/remover atributos DOM de forma controlada (`apply`, `remove`)
- registrar listeners globales o transversales con cleanup (`listen`)
- resolver `document` / `window` propietario para iframes, popups y tests
- ejecutar acciones imperativas (`focus`, `scrollTo`, `scrollWindowTo`,
`scrollWindowBy`, `requestFrame`)
- escribir/remover nodos gestionados y texto accesible (`writeNode`,
`writeText`, `removeNode`)
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
En otras palabras:
```text
libs/dom -> arts/adom -> App.dom / app.dom
puro reactivo consumo de app
```
## Composicion via active-app
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
`createActiveApp(...)` puede construir `App.dom` como servicio cuando la app lo
declara con `defineActiveDom()`. `ActiveUix` lo usa como unico escritor de
attrs cuando sus capas reciben una superficie DOM. Las preferencias
transversales se proyectan mediante `createActivePrefsDomProjection(...)`; las
visuales mediante `ActiveEidos`. Si no existe `dom` porque el integrador pidio
`dom:false`, UIX usa `disabledDom` en modo standalone. Construir
`createActiveDom()` directamente solo es necesario en tests aislados o en
consumidores fuera de la composicion estandar.
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
## Qué pertenece a cada capa
### `libs/dom`
Primitives DOM puras o casi puras:
- guards y traversal DOM
- focus helpers
- tabbable helpers
- responsive helpers puros
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
**Capa de implementación.** Componentes y soma NO importan de aquí
directamente — todo se reexporta a través de `$adom` (ver más abajo).
Sólo `arts/adom/*` y los tests de `libs/dom` pueden importar
`$libs/dom` directo.
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
No mantiene estado de aplicación.
### `arts/adom`
Runtime reactivo de DOM:
- `viewport`
- `breakpoints`
- `currentBreakpoint`
- `resolve(...)`
- `isAtLeast(...)`
- `matches(...)`
- `apply(...)` / `remove(...)` como superficie unica de mutacion de atributos
- `listen(...)` como superficie unica para listeners globales o transversales
- `query(...)`, `elementFromPoint(...)`, `activeElement(...)` para consultas
contra el documento propietario
- `focus(...)`, `scrollIntoView(...)`, `scrollTo(...)`, `scrollWindowTo(...)`,
feat(adom): canonize dom.raf() frame scheduler + migrate Words overlays Add `raf(callback, node?): ActiveDomFrameCleanup` to the ActiveDom surface — a one-shot animation frame that returns an idempotent **disposer** (the same `() => void` shape as `listen` / `observe*`), so an `$effect` can `return dom.raf(...)` and Svelte cancels the pending frame on teardown. It wraps the existing `requestFrame` / `cancelFrame` (which already resolve the instance's `targetWindow` — iframe / popup / happy-dom safe), adding no new scheduling logic. New `ActiveDomFrameCleanup` type exported from the barrel; `raf` also implemented on the disabled-dom stub (throws, like `requestFrame`). Why: the doctrine is "all DOM activity via ActiveDom". `listen`/`observe*` already returned disposers; the animation frame was the gap — `requestFrame` exposes a raw numeric handle (per-call bookkeeping + leak risk), and layout components were falling back to the GLOBAL `requestAnimationFrame`, which targets the wrong window in iframe/popup contexts (the exact bug getWindow/ getDocument fix elsewhere). `raf` closes it. Migrated the 3 raw `requestAnimationFrame` sites the Words audit surfaced — words-block-gutter (reposition), words-bubble-menu + words-slash-menu (overlay position) — to `dom.raf(...)`. Bubble/slash keep their `tick()` fallback for no-rAF environments. Documented the decision + rationale as a dated Backlog entry at the end of `src/arts/adom/README.md` (and listed `raf` in the API + imperative-actions sections). Notes the kept distinction: `raf` is for layout frames, NOT the `$timer` lifecycle scheduler; low-level requestFrame/cancelFrame stays for consumers that already hold the handle (drawer/slider/splitter/floating/ focus-scope). Remaining raw rAF in other eidos components (tabs-indicator…) left for when those are touched — flagged in the backlog. Gates: npm run check 1 error (pre-existing grafito, not adom/Words) · soma words + adom 479/479 · prettier clean · browser smoke: gutter repositions, bubble menu positions, no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
`scrollWindowBy(...)`, `requestFrame(...)` / `raf(...)` para acciones
imperativas que no deben depender del `window/document` global (`raf`
devuelve un disposer — ver Backlog)
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- `BodyScrollLock` como helper global de body scroll lock, sin bloquear eventos de puntero
- `DOMContext` como helper scoped para `Document` / `ShadowRoot`
- `RovingFocusGroup` como helper runtime para navegación compuesta por teclado
`ActiveDom` sí mantiene estado reactivo y por eso vive aquí, no en `$libs/dom`.
## Posición en App
`ActiveDom` vive a nivel de aplicación:
```ts
App.dom;
app.dom;
```
La implementación se consume desde la capa de aplicación y desde artefactos que
necesitan escribir atributos finales sobre un target DOM, por ejemplo
`ActivePrefsDomProjection`, `SomaRuntime`, `VisualChannel` o `ActiveEidos`.
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
## API actual
La API pública real de `ActiveDom` hoy es esta:
```ts
export interface ActiveDom {
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
breakpoints: Active<Breakpoints>;
viewport: { readonly width: number };
currentBreakpoint: Active<Breakpoint>;
resolve<T>(value: ResponsiveProp<T> | undefined): T | undefined;
isAtLeast(breakpoint: Breakpoint): boolean;
matches(breakpoint: Breakpoint): boolean;
apply(change: StructuralChange): void;
remove(target: HTMLElement, names: readonly string[]): void;
writeStyle(
id: string,
css: string,
options?: ActiveDomWriteStyleOptions
): HTMLStyleElement | undefined;
removeStyle(id: string, options?: ActiveDomRemoveStyleOptions): void;
writeNode(id: string, options?: ActiveDomWriteNodeOptions): HTMLElement | undefined;
writeText(target: HTMLElement, text: string): void;
removeNode(id: string, options?: ActiveDomRemoveNodeOptions): void;
listen(
target: EventTarget,
event: string | readonly string[],
handler: EventListener
): () => void;
observeResize(
target: Element,
callback: ResizeObserverCallback,
options?: ResizeObserverOptions
): () => void;
observeMutation(
target: Node,
callback: MutationCallback,
options: MutationObserverInit
): () => void;
observeIntersection(
target: Element,
callback: IntersectionObserverCallback,
options?: IntersectionObserverInit
): () => void;
activeElement(node?: Element | Window | Node | Document | null): Element | null;
query<T extends Element = Element>(
selector: string,
root?: ParentNode | Document | null
): T | null;
elementFromPoint(
x: number,
y: number,
node?: Element | Window | Node | Document | null
): Element | null;
focus(target: HTMLElement | null | undefined, options?: FocusOptions): void;
scrollIntoView(target: Element | null | undefined, arg?: boolean | ScrollIntoViewOptions): void;
scrollTo(target: Element | null | undefined, arg: ScrollToOptions | number, y?: number): void;
requestFrame(
callback: FrameRequestCallback,
node?: Element | Window | Node | Document | null
): number;
cancelFrame(handle: number, node?: Element | Window | Node | Document | null): void;
feat(adom): canonize dom.raf() frame scheduler + migrate Words overlays Add `raf(callback, node?): ActiveDomFrameCleanup` to the ActiveDom surface — a one-shot animation frame that returns an idempotent **disposer** (the same `() => void` shape as `listen` / `observe*`), so an `$effect` can `return dom.raf(...)` and Svelte cancels the pending frame on teardown. It wraps the existing `requestFrame` / `cancelFrame` (which already resolve the instance's `targetWindow` — iframe / popup / happy-dom safe), adding no new scheduling logic. New `ActiveDomFrameCleanup` type exported from the barrel; `raf` also implemented on the disabled-dom stub (throws, like `requestFrame`). Why: the doctrine is "all DOM activity via ActiveDom". `listen`/`observe*` already returned disposers; the animation frame was the gap — `requestFrame` exposes a raw numeric handle (per-call bookkeeping + leak risk), and layout components were falling back to the GLOBAL `requestAnimationFrame`, which targets the wrong window in iframe/popup contexts (the exact bug getWindow/ getDocument fix elsewhere). `raf` closes it. Migrated the 3 raw `requestAnimationFrame` sites the Words audit surfaced — words-block-gutter (reposition), words-bubble-menu + words-slash-menu (overlay position) — to `dom.raf(...)`. Bubble/slash keep their `tick()` fallback for no-rAF environments. Documented the decision + rationale as a dated Backlog entry at the end of `src/arts/adom/README.md` (and listed `raf` in the API + imperative-actions sections). Notes the kept distinction: `raf` is for layout frames, NOT the `$timer` lifecycle scheduler; low-level requestFrame/cancelFrame stays for consumers that already hold the handle (drawer/slider/splitter/floating/ focus-scope). Remaining raw rAF in other eidos components (tabs-indicator…) left for when those are touched — flagged in the backlog. Gates: npm run check 1 error (pre-existing grafito, not adom/Words) · soma words + adom 479/479 · prettier clean · browser smoke: gutter repositions, bubble menu positions, no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
raf(callback: FrameRequestCallback, node?: Element | Window | Node | Document | null): () => void;
scrollWindowBy(
arg: ScrollToOptions | number,
y?: number,
node?: Element | Window | Node | Document | null
): void;
scrollWindowTo(
arg: ScrollToOptions | number,
y?: number,
node?: Element | Window | Node | Document | null
): void;
getDocument(node?: Element | Window | Node | Document | null): Document;
getWindow(node?: Node | ShadowRoot | Document | Window | null): Window;
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
dispose(): void;
}
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
```
Creación:
```ts
const dom = createActiveDom({
breakpoints: readableActive(() => ({
lg: 1100
}))
});
const domWithDefaults = createActiveDom();
```
Responsive:
```ts
const columns = dom.resolve({ base: 1, sm: 2, lg: 3 });
const tone = dom.resolve({ base: 'compact', md: 'normal', xl: 'wide' });
```
Mutacion DOM:
```ts
dom.apply({
target: node,
attrs: {
'data-state': 'open',
'aria-busy': true,
'data-hidden': false
}
});
dom.remove(node, ['data-state', 'aria-busy']);
```
Nodos gestionados:
```ts
const region = dom.writeNode('app-live-region', {
host: dom.getDocument().body,
attrs: { role: 'status', 'aria-live': 'polite' },
text: 'Ready'
});
if (region) dom.writeText(region, 'Saved');
dom.removeNode('app-live-region', { host: dom.getDocument().body });
```
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
Reglas:
- los breakpoints se definen en la creación del `dom`
- `breakpoints` es opcional; si no se pasa, usa `BREAKPOINTS_DEFAULT`
- no hay herencia de `dom` padre
- `ActiveDom` es servicio de app, no scope anidado
- `ActiveDom` registra el listener de `resize` al construirse (por instancia).
El singleton compartido (`shareViewport: true`) pospone la asignación del
listener a la primera lectura del viewport — importar `$adom` no aloca
estado reactivo si nadie consume el viewport.
- `apply` solo escribe atributos; no interpreta semantica ni eventos
- listeners de `document` / `window`, observers (`ResizeObserver`,
`MutationObserver`, `IntersectionObserver`) y acciones imperativas
transversales van por `ActiveDom`
- lecturas locales de un elemento propio (`getBoundingClientRect`, `contains`,
`closest`, `clientWidth`, `scrollTop`) siguen siendo responsabilidad del
componente; envolverlas en `ActiveDom` seria ruido
- `writeNode`/`writeText` son para nodos owned por servicios UIX
(live regions, descripciones ocultas, style hosts auxiliares), no para
saltarse el render de Svelte en componentes normales
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- `false | null | undefined` remueven atributos
- `viewport` es **per-instancia por defecto**: cada `ActiveDom` posee su
propio listener de `resize`, scoped al `targetWindow` (default `window`).
Esto evita filtraciones entre tests, iframes, popups y entornos happy-dom.
Llamar `dispose()` desadjunta ese listener.
### Compartir el viewport entre instancias
Para apps "single window" donde toda la composición vive en el mismo
documento, opt-in al singleton evita N listeners para el mismo evento:
```ts
const dom = createActiveDom({ shareViewport: true });
```
Bajo esta opción, `dispose()` es no-op para el viewport (el singleton vive
toda la vida del proceso). Lo que sí se libera es `breakpoints`,
`currentBreakpoint` y los demás reactivos por-instancia.
### Tracking de un window distinto
Para iframes, popups o entornos de test:
```ts
const iframeDom = createActiveDom({ targetWindow: iframe.contentWindow! });
const popupDom = createActiveDom({ targetWindow: popup });
```
Ignorado cuando `shareViewport: true` (el singleton siempre rastrea el
top-level `window`).
## Qué no es
`ActiveDom` no es:
- `EngineSemantic`
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- broker de eventos
- reflector de `data-event*`
- scheduler de observers compartidos con cache global; solo expone factories
scoped a la ventana propietaria del target
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
- sistema de theme
- sistema de modal, backdrop o inert
- reemplazo de `$libs/dom`
Además, `uix/adom` puede alojar helpers DOM con estado global real, como
`BodyScrollLock`, o helpers scoped de runtime como `DOMContext`, cuando ya no
son primitives puras de `$libs/dom` pero tampoco pertenecen a un componente UI concreto.
También caben aquí helpers runtime de foco con estado propio, como
`RovingFocusGroup`, que reutilizan `$libs/dom` por debajo pero ya no son
solo utilidades puras.
## Pagina De Prueba
La pagina manual esta en `/test/adom` y cubre:
- viewport y breakpoint actual
- `resolve()` responsive
- `apply()` / `remove()`
- `BodyScrollLock`
- `DOMContext`
- `RovingFocusGroup`
## Relación con otras piezas
### Semántica
La semántica pertenece a `Sema` y a `EngineSemantic`, no a `ActiveDom`.
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
Si mañana `ActiveDom` refleja eventos al DOM, será como consumidor de
`EngineSemantic`, no como autoridad semántica.
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Theme
El theme no pertenece a `dom`. En UIX, `ActiveEidos` posee `data-theme`,
`data-mode` y `data-density`; `ActiveDom` solo recibe la mutación ya resuelta.
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### Air y Terra
`air` y `terra` no consumen esta capa nueva.
Su código actual sirve como referencia histórica para extraer utilidades hacia
`$libs/dom`, pero no forman parte del runtime nuevo.
## Estado del diseño
`ActiveDom` está en fase fundacional.
Lo que ya está cerrado:
- `app.dom`
- `App.dom`
- `viewport`
- `breakpoints`
- `currentBreakpoint`
- resolución responsive
- listeners, observers y acciones imperativas scoped a la ventana propietaria
del target
eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
Lo que queda para fases posteriores, si de verdad hace falta:
- reflexión de eventos semánticos al DOM
- APIs por `Document` o `ShadowRoot`
- introspección/diagnóstico de runtime más rica
La regla importante por ahora es simple:
> `ActiveDom` es el servicio reactivo de DOM de la app; `$libs/dom` es su base pura.
feat(adom): canonize dom.raf() frame scheduler + migrate Words overlays Add `raf(callback, node?): ActiveDomFrameCleanup` to the ActiveDom surface — a one-shot animation frame that returns an idempotent **disposer** (the same `() => void` shape as `listen` / `observe*`), so an `$effect` can `return dom.raf(...)` and Svelte cancels the pending frame on teardown. It wraps the existing `requestFrame` / `cancelFrame` (which already resolve the instance's `targetWindow` — iframe / popup / happy-dom safe), adding no new scheduling logic. New `ActiveDomFrameCleanup` type exported from the barrel; `raf` also implemented on the disabled-dom stub (throws, like `requestFrame`). Why: the doctrine is "all DOM activity via ActiveDom". `listen`/`observe*` already returned disposers; the animation frame was the gap — `requestFrame` exposes a raw numeric handle (per-call bookkeeping + leak risk), and layout components were falling back to the GLOBAL `requestAnimationFrame`, which targets the wrong window in iframe/popup contexts (the exact bug getWindow/ getDocument fix elsewhere). `raf` closes it. Migrated the 3 raw `requestAnimationFrame` sites the Words audit surfaced — words-block-gutter (reposition), words-bubble-menu + words-slash-menu (overlay position) — to `dom.raf(...)`. Bubble/slash keep their `tick()` fallback for no-rAF environments. Documented the decision + rationale as a dated Backlog entry at the end of `src/arts/adom/README.md` (and listed `raf` in the API + imperative-actions sections). Notes the kept distinction: `raf` is for layout frames, NOT the `$timer` lifecycle scheduler; low-level requestFrame/cancelFrame stays for consumers that already hold the handle (drawer/slider/splitter/floating/ focus-scope). Remaining raw rAF in other eidos components (tabs-indicator…) left for when those are touched — flagged in the backlog. Gates: npm run check 1 error (pre-existing grafito, not adom/Words) · soma words + adom 479/479 · prettier clean · browser smoke: gutter repositions, bubble menu positions, no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
---
## Backlog / Decisiones de evolución
Registro de cambios de superficie posteriores a la fase fundacional. Cada
entrada documenta **qué** se añadió y, sobre todo, **por qué** — para que la
decisión no se pierda y futuros consumidores entiendan el patrón canónico.
### 2026-06-02 — `dom.raf(callback, node?)`: frame de animación con disposer
**Qué.** Nuevo método en la superficie `ActiveDom`:
```ts
raf(callback: FrameRequestCallback, node?: …): ActiveDomFrameCleanup
```
Agenda un `requestAnimationFrame` de una sola pasada contra la ventana que
posee `node` (o el `targetWindow` de la instancia) y devuelve un **disposer**
`() => void` idempotente que cancela el frame pendiente. Tipo
`ActiveDomFrameCleanup` exportado junto a `ActiveDomListenerCleanup` /
`ActiveDomObserverCleanup`.
**Por qué.** La doctrina del proyecto es _"toda actividad de DOM pasa por
ActiveDom"_ (sin `window.addEventListener`, sin `new ResizeObserver`, sin
`document.querySelector` crudos en componentes). `listen` y `observe*` ya
cumplían esa regla devolviendo un **disposer** — la forma exacta que un
`$effect` de Svelte puede `return` para que el framework limpie en el
teardown. Pero el frame de animación quedaba fuera:
- `ActiveDom` exponía `requestFrame` / `cancelFrame` (resuelven bien la
ventana iframe/popup), pero devuelven el **handle numérico crudo**. Eso
obliga a cada consumidor a guardar el número, gestionar el guard de
"pendiente" y cancelar a mano en el teardown — bookkeeping repetido y
propenso a fugas.
- Los componentes que necesitaban un frame de _layout_ (medir y reposicionar
un overlay) caían en `requestAnimationFrame` / `cancelAnimationFrame`
**globales**. Eso apunta al `window` global, que es **incorrecto** en
contextos iframe / popup / happy-dom — el mismo bug que `getWindow` /
`getDocument` resuelven para el resto de la API.
`raf` cierra ese hueco: envuelve `requestFrame`/`cancelFrame` (cero lógica
nueva de scheduling) y devuelve el disposer simétrico. Un consumidor escribe
ahora `return dom.raf(reposition, node)` y Svelte cancela el frame solo.
**Detonante.** La auditoría del editor **Words** (2026-06-02) encontró 3
`requestAnimationFrame` crudos —`words-block-gutter`, `words-bubble-menu`,
`words-slash-menu`— que reposicionaban overlays apuntando al `window` global.
Migrados a `dom.raf(...)`. El precedente de rotura ya existía en otros sitios
del eidos (p. ej. `tabs-indicator`), así que esto canoniza el patrón, no es
un parche puntual de Words.
**Distinción que se mantiene.** `dom.raf` es para frames de **layout**
(medir/posicionar, ligados al ciclo de pintado). NO sustituye al servicio
`$timer` (`App.timers`), que es para _lifecycle timers con clave_
(debounce / heartbeat / intervalos / one-shots con delay). Son dominios
distintos: el timer no tiene primitiva de frame y `raf` no tiene clave ni
cancelación por scope. El `requestFrame`/`cancelFrame` de bajo nivel se
conserva para los pocos consumidores que ya guardan el handle (drawer,
slider, splitter, floating, focus-scope…) y aún no migran.
**Pendiente (no bloqueante).** Migrar los `requestAnimationFrame` crudos
restantes del eidos (`tabs-indicator`, etc.) a `dom.raf` cuando se toquen
esos componentes; no se hizo en barrido para no ensanchar el diff de la
auditoría de Words.

Powered by TurnKey Linux.