4.9 KiB
Packs
A pack is an encapsulated, opt-in collection that sits ABOVE the UIX
layers and OUTSIDE the component canon. It consumes the framework; the
framework never references it. The live inventory is the src/packs/ tree —
never a list in a doc.
Packs exist so that decorative surface area does not dilute the canon: the catalog's value is contract density (events, ARIA, keyboard, tokens other layers consume); a pack's value is parameterized leaf decoration. Mixing the two would put fashion-velocity artifacts inside the doctrine that must age slowly.
The admission rule (canon vs pack)
Canon = anything with contract surface that others consume — semantic events, ARIA/keyboard behavior, state machines, tokens targeted by other recipes. It enters through the 9-phase route and the acceptance matrix.
Pack = a decorative leaf with parameters — nothing else in the system selects against it, listens to it, or inherits from it. It enters through the P contract below, and stays out of
component:audit.
If a pack piece grows contract surface (a real event, an a11y obligation, a token another recipe wants), that is the signal to promote it to the canon through the full route — never to grow the pack's privileges.
Hard boundaries
- Dependency direction is one-way:
src/packs/*may import$uix/*,$scene,$adomand the other arts; nothing undersrc/{arts,libs,uix}may import fromsrc/packs/. Deleting a pack directory must leavenpm run checkgreen — that is the encapsulation proof. - No morfo, no matrix: pack pieces declare no morfo and take no row in
the acceptance matrix. Their DOM is decorative (
aria-hiddenwhere it paints,pointer-events: noneunless an effect opts into pointer input). - Packs are removable by construction — an app that never imports a pack pays zero bytes for it (per-module registration, tree-shaken).
The P contract (the pack quality floor)
A pack is exempt from the acceptance matrix, NOT from discipline. Every pack
ships with these six obligations, guarded mechanically (packs-check):
| P | Obligation |
|---|---|
| P-1 | Every animated piece declares a reduce policy ('static-frame' | 'hide') — no silent default; reduced motion is honored via the injected DOM port, never a raw matchMedia. |
| P-2 | Full teardown: every mount returns a handle whose dispose() releases frames, observers, GL resources and listeners. |
| P-3 | DOM only through the injected port (dom.requestFrame / observeResize / observeIntersection / listen) — raw requestAnimationFrame / ResizeObserver / IntersectionObserver / addEventListener in src/packs/ is an error. |
| P-4 | Color parameters are token-aware: they accept an eidos custom-property name (resolved via eidos.resolveToken, re-resolved on mode change) or a raw CSS color as fallback; hardcoded palettes without a token path are an error for theme-facing params. |
| P-5 | Decorative DOM is aria-hidden="true"; pointer interactivity is opt-in per piece and never traps focus. |
| P-6 | Off-screen and hidden-tab work is paused; a scene budget guards against unbounded concurrent GL contexts (warn via logger). |
The promotion path (walked)
The first pack (ambient, animated backgrounds) was built ahead of its
canonical consumer: the agent-presence component Aura, which materializes
the delegate + sustain families — the visual channel is the ONLY channel
delegate activates (see decisions/book-deviations.md D.8).
That promotion has since happened, and it validated the design: because the
effects are shared resources, not pack internals — they live with the
runtime ($scene/effects) — the pack mounts them decoratively and Aura
mounts the same resources semantically (states from the agent lifecycle;
intent modulating speed / amplitude / hue — the visual analog of sound's
pitch / gain / contour). Orientative state → effect mapping: idle →
mesh/fog/grainient · listening → orb/lumen · thinking → aurora/silk ·
acting/streaming → threads/waves · searching → radar · error (event, not
state) → ray.
With Aura landed, the scene runtime is promoted: uix.scene is part of
ActiveUix's surface (createActiveUix builds it from the available dom;
attach reads app.scene, falling back to its own). createEngineScene remains
the standalone factory for use outside a UIX tree.
Provenance note
The seed collection (web/routes/demos/animations, 45 effects) is kept
intact as comparative reference material — it is NOT the pack. The pack
implementations are built clean against the P contract; the originals stay to
compare against and are excluded from the framework's guards.