|
|
---
|
|
|
title: UIX — Active Architecture
|
|
|
type: reference
|
|
|
audience: human + agent
|
|
|
authority: E1 architecture — the deep whole-system view: motivations, the four layers, the transcription chain, the hard rules
|
|
|
status: current
|
|
|
source: migrated from src/uix/active_architecture.md (2026-07-02, docs-book F7.2)
|
|
|
---
|
|
|
|
|
|
# UIX — Active Architecture
|
|
|
|
|
|
> The living document of UIX's active architecture: motivations, the four
|
|
|
> layers, how they articulate, what problem they solve, what they
|
|
|
> deliberately leave out. This doc is the whole-system view; the per-layer
|
|
|
> chapters are the operational reference. Dated status snapshots live in
|
|
|
> [`docs/process/`](../process/) (see §10).
|
|
|
|
|
|
---
|
|
|
|
|
|
## 0. Minimum contracts per module
|
|
|
|
|
|
What each module requires, what is optional, how it degrades and when it
|
|
|
fails. The ownership and degradation rules are stated, timelessly, in
|
|
|
[`architecture/active-uix.md`](./active-uix.md) §"Ownership and degradation
|
|
|
rules".
|
|
|
|
|
|
> Executable source: `src/uix/contracts.ts`. Boundary test:
|
|
|
> `src/uix/contracts.test.ts`.
|
|
|
|
|
|
```text
|
|
|
Module Requires Optional When missing Error
|
|
|
active-uix langs,prefs,dom* clipboard,format,events,portal standalone disabledDom missing langs/dom in attach
|
|
|
morfo none translations registers no translations no
|
|
|
soma dom events,langs,format,clipboard disabledDom from uix invalid morfo/event/part; absent optional service
|
|
|
sema projector/dom* sound,haptic,visual:false none SemaConfigError without dom/projector
|
|
|
eidos dom* langs,format,prefs,mode/density sources applyDom:false missing dom with applyDom active
|
|
|
adom ActiveDom surface target/window/breakpoints disabledDom only explicit ADom errors without a real DOM
|
|
|
|
|
|
* `dom` means an `ActiveDom` surface, not necessarily a real DOM. It may be
|
|
|
`disabledDom` only in standalone when the integrator asks for `dom:false`.
|
|
|
In attach it must come from `ActiveApp`.
|
|
|
* Outside `ActiveUix`, an `EngineSemantic` with the visual channel active
|
|
|
must receive `dom` or `projector`; `visual:false` is the explicit
|
|
|
degradation.
|
|
|
```
|
|
|
|
|
|
Future changes must derive from this table, not from constructors invented
|
|
|
in lower layers.
|
|
|
|
|
|
Applied correction: `ActiveUix` neither imports nor instantiates
|
|
|
`Soma`/`Eidos`. `portal` remains a generic UIX setting; `Soma` consumes it as
|
|
|
the default for `portalTo`, and `ActiveEidos.create(...)` creates the visual
|
|
|
scope when the app needs Eidos.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 0.1 Canonical naming
|
|
|
|
|
|
The architecture may keep the historical folder names (`morfo`, `soma`,
|
|
|
`sema`, `eidos`), but the public surface must use a consistent grammar.
|
|
|
General rule: one name represents one concept; if a term is a historical
|
|
|
alias, it must be marked as such with a retirement path.
|
|
|
|
|
|
| Concept | Canonical name | Avoid / retire |
|
|
|
| ------------------------------------ | ----------------------------------------- | --------------------------------------------------------- |
|
|
|
| Runtime translation service | `langs` | `lang` as a service |
|
|
|
| Active language | `prefs.language` | `locale` for the translation language |
|
|
|
| Locale / regional formats | `prefs.locale` | `language` for formats |
|
|
|
| Declarative text catalogs | `translations` | `langs` inside `morfo`; global per-component tables |
|
|
|
| UIX preferences | `prefs` | `settings`, `presentation` as new names |
|
|
|
| UIX perceptual events | `events` | `semantic` as a public service |
|
|
|
| In-flight occurrence | `signal` | using it for the whole layer |
|
|
|
| A morfo event's semantic payload | `semantic` | mixing it with the runtime service |
|
|
|
| Declarative TS contract | `morfo` | `contract` as a duplicated TS API |
|
|
|
| Exported CSS/data contract | `contract` | `morfo` for external CSS |
|
|
|
| Runtime CSS bridge | `ActiveEidos` | a mandatory visual runtime for components |
|
|
|
| Independent pure engine | `EngineX` only if it lives outside `ActiveX` | decorative engines |
|
|
|
| Eidos visual root | `DrawerProps`, `DialogProps` | `DrawerProviderProps` in the visual API |
|
|
|
|
|
|
Decisions already applied:
|
|
|
|
|
|
- `ActiveUix.events` is the canonical name of the perceptual engine. In
|
|
|
attach mode it reads `app.events`; `defineUixServices(...)` declares the
|
|
|
service under the same name.
|
|
|
- There is no public `ActiveUix.semantic` service. `semantic` survives only
|
|
|
as the payload name in `morfo.events[].semantic`.
|
|
|
- `morfo.texts` is the declarative field for component-owned idlangrefs —
|
|
|
the `morfo.translations` nomenclature was renamed to `texts` during the
|
|
|
2026-05 migration (see `langs/components/*.ts` for the per-component
|
|
|
catalogs). `langs` remains the runtime service.
|
|
|
- `prefs` is the only name for preferences. `ActiveUix` exposes the raw
|
|
|
`ActivePrefs`; Soma/Eidos consume bounded views. No `settings` is
|
|
|
introduced.
|
|
|
- `ActiveUix.motion` (`EngineMotion`, `arts/motion`) is the animation engine,
|
|
|
consumed by Soma (`soma.motion`) and Eidos (`eidos.motion`). It lives in
|
|
|
`arts/`, not in Eidos, so Soma can animate (spring) without a soma→eidos
|
|
|
dependency. In attach it reads `app.motion`.
|
|
|
|
|
|
Retirement order:
|
|
|
|
|
|
1. Keep `assertContract` as a data-contract validator, not as a parallel
|
|
|
registry. `registerContract` remains for tooling/direct tests; Soma
|
|
|
registers contracts via `registerMorfo()`.
|
|
|
2. Only afterwards clean up prop names in visual components.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 1. The thesis in one sentence
|
|
|
|
|
|
> UIX treats a component as **four layers with explicit contracts**, not as a
|
|
|
> monolithic block mixing structure, behavior, semantics and presentation.
|
|
|
|
|
|
The four layers are **Morfo · Soma · Sema · Eidos**. Each does one sharp job
|
|
|
and communicates with the others only through the DOM and a shared
|
|
|
declarative contract. None invades the next.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 2. The problem it solves
|
|
|
|
|
|
In most UI frameworks a component accumulates:
|
|
|
|
|
|
- the DOM's **public contract** (attributes, parts, ARIA)
|
|
|
- the **headless behavior** (state, keyboard, focus, events)
|
|
|
- the event's **semantics** (what "opening a dialog" means beyond an
|
|
|
attribute change)
|
|
|
- the **visual layer** (CSS, animations, theming)
|
|
|
- the **modal engines** (sound, haptic and CSS reactions via DOM events)
|
|
|
- the **app-service integration** (i18n, dates, theme, etc.)
|
|
|
|
|
|
All of that lives mixed together. Renaming a `part` touches six places with
|
|
|
no automatic verification. An event's semantics are buried in hardcoded
|
|
|
strings only the component knows. CSS couples to incidental DOM structure.
|
|
|
Sound engines rewrite per-component mappings. When you want to change a
|
|
|
cross-cutting decision — "all triggers must share a common hover dim" — you
|
|
|
must enumerate the 30 components that have a trigger.
|
|
|
|
|
|
UIX breaks that block into four layers with disjoint responsibilities and a
|
|
|
common communication channel: **the DOM with attributes declared by the
|
|
|
cross-layer contract**.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. The four layers
|
|
|
|
|
|
### Morfo — the cross-layer contract
|
|
|
|
|
|
`Morfo` declares the component's genetics: its parts, the `data-*` it emits,
|
|
|
the ARIA it contributes, the roles, the states, the semantic events it may
|
|
|
fire, and the keys it dispatches. One declaration per component, in
|
|
|
TypeScript, validated by sium.
|
|
|
|
|
|
Morfo **executes nothing**. It is DNA, not protein.
|
|
|
|
|
|
```ts
|
|
|
// src/uix/morfo/components/dialog.ts (excerpt)
|
|
|
export const dialogMorfo = {
|
|
|
name: 'Dialog',
|
|
|
kebab: 'dialog',
|
|
|
scope: ['soma', 'sema'],
|
|
|
events: [{
|
|
|
name: 'emerge-close-cancel',
|
|
|
semantic: {
|
|
|
family: 'emerge',
|
|
|
verb: 'close',
|
|
|
target: v.partRef('content'),
|
|
|
sequence: 'pre'
|
|
|
},
|
|
|
prewrite: [{ part: v.partRef('content'),
|
|
|
attr: 'data-last-action', value: 'cancelled' }],
|
|
|
commits: { part: v.partRef('content'),
|
|
|
attr: 'data-state', value: 'closed' }
|
|
|
}],
|
|
|
parts: [
|
|
|
{ name: 'Trigger', kebab: 'trigger', archetype: 'trigger', role: 'button', ... },
|
|
|
{ name: 'Content', kebab: 'content', archetype: 'content', role: 'dialog', ... },
|
|
|
// ...
|
|
|
]
|
|
|
} as const satisfies Morfo
|
|
|
```
|
|
|
|
|
|
Morfo is **the single cross-layer articulation point**. Any data the other
|
|
|
layers need to share with each other passes through here. It is the most
|
|
|
important structural rule: if two layers need to know the same thing, that
|
|
|
"same thing" lives in morfo.
|
|
|
|
|
|
### Soma — the headless behavior
|
|
|
|
|
|
`Soma` consumes morfo and transcribes it into executable behavior. It reads
|
|
|
`morfo.events`, `morfo.keyboard`, `morfo.parts[].data` and `aria`, and
|
|
|
materializes them: dispatches keys, applies attributes to the DOM, manages
|
|
|
state, integrates with context (Field, Form, Soma).
|
|
|
|
|
|
Soma **decides no visuals**. It knows no colors. No transitions. No sounds.
|
|
|
Only states, events, focus, keyboard and how to materialize all of that in
|
|
|
the DOM.
|
|
|
|
|
|
Soma's central piece is `SomaRuntime`: a morfo interpreter that receives the
|
|
|
reactive sources from the provider (states, props, parts, events, actions)
|
|
|
and takes care of:
|
|
|
|
|
|
- emitting the static attrs (`partProps`)
|
|
|
- applying the state-derived attrs via `dom.apply` (effects)
|
|
|
- dispatching keys via `keydown(part, event)`
|
|
|
- executing events via `trigger(eventName)` with the full perceptual chain
|
|
|
|
|
|
The provider contributes **what the morfo cannot infer**: reactive getters
|
|
|
over internal state, concrete handlers, and the glue for orthogonal layers
|
|
|
(Presence, Dismissal, ScrollLock).
|
|
|
|
|
|
### Sema — vocabulary + perceptual channels
|
|
|
|
|
|
`Sema` defines the framework's canonical vocabulary and orchestrates the
|
|
|
**dispatch of perceptual signals** to a set of modular channels.
|
|
|
|
|
|
Canonical vocabulary (`SEMA_MAP` in `src/uix/sema/sema-map.ts`):
|
|
|
|
|
|
- **8 families** — `contact`, `commit`, `signal`, `handle`, `emerge`,
|
|
|
`shift`, `sustain`, `delegate`. Each declares a `hold`, a base for the real
|
|
|
channels (`sound`, `haptic`) and the set of active channels.
|
|
|
- **6 intents** — `neutral`, `affirm`, `fulfill`, `risk`, `threat`, `loss`.
|
|
|
Each intent declares per-channel `deltas` applied over the family base when
|
|
|
the family is valenced.
|
|
|
- **Action verbs** (`SEMA_VERBS` in `src/uix/sema/verbs.ts`) — `present`,
|
|
|
`dismiss`, `commit`, `cancel`, `announce`, `warn`, … — the canonical verbs
|
|
|
for `morfo.events[].semantic.verb`, and the tail of `morfo.events[].name`.
|
|
|
|
|
|
Sema **does not decide which event happened** — the provider decides. The
|
|
|
`EngineSemantic` only:
|
|
|
|
|
|
- keeps a registry of channels implementing `Channel`
|
|
|
- generates each occurrence's `id`
|
|
|
- resolves the per-channel `EffectiveSignature` (base × intent deltas)
|
|
|
- dispatches each signal to every registered channel
|
|
|
- blocks the caller only for as long as the visual channel needs
|
|
|
|
|
|
```
|
|
|
src/uix/sema/
|
|
|
├── engine.ts registry + channel prepare/dispatch
|
|
|
├── resolver.ts resolveSignature(signal): EffectiveSignature
|
|
|
├── sema-map.ts per-family base + per-intent deltas table (typed)
|
|
|
├── verbs.ts SEMA_VERBS catalog
|
|
|
└── chans/
|
|
|
├── types.ts Channel interface
|
|
|
├── visual.ts VisualChannel (built-in, data-event projection + hold)
|
|
|
├── sound.ts SoundChannel (Web Audio, prepare-time priming)
|
|
|
└── haptic.ts HapticChannel
|
|
|
```
|
|
|
|
|
|
The **visual channel** (built-in) is the only one sharing the DOM plane with
|
|
|
the subsequent structural commit, and therefore the only one that blocks the
|
|
|
caller. `EngineSemantic` runs generic channel hooks; `VisualChannel.prepare()`
|
|
|
projects `data-event` + `data-event-id` + `data-event-phase` (and optionally
|
|
|
`data-event-family`, `data-event-intent` and `data-event-direction`) onto the
|
|
|
target through a
|
|
|
`SignalProjector`. In `ActiveUix` that projector receives `uix.dom`, so attr
|
|
|
writes enter through the same DOM owner soma uses. `VisualChannel` holds the
|
|
|
configurable window and the cleanup removes the projection before resolving
|
|
|
the Promise (strict sequential semantics).
|
|
|
|
|
|
> **Namespace discipline**: the semantic projection writes **only**
|
|
|
> attributes under the `data-event-*` prefix. It never touches `data-state`,
|
|
|
> `data-intent`, `data-disabled` or other state attrs — those belong to the
|
|
|
> runtime/morfo. Eidos reads `data-event-intent` for reactions to the
|
|
|
> transient signal and `data-intent` (when the morfo emits it) for the
|
|
|
> persistent state.
|
|
|
|
|
|
The visual channel's internal hold defaults come from
|
|
|
`SEMA_MAP.families[*].hold` and resolve over the perceptual scale
|
|
|
`SEMA_DURATIONS`: `glimpse`, `brief`, `noticed`, etc. The integrator can
|
|
|
override per signal (`signal.hold`) or globally via
|
|
|
`new EngineSemantic({ dom, visual: { defaultHold } })`.
|
|
|
|
|
|
The **SoundChannel** is implemented: it synthesizes short earcons via Web
|
|
|
Audio (two oscillators → biquad lowpass → ADSR-lite envelope, parameterized
|
|
|
by `effective.sound.{pitch, centroid, gain, contour, roughness, duration}`).
|
|
|
It does **prepare-time priming**: it creates + resumes the `AudioContext` in
|
|
|
the `prepare()` of an audible signal, synchronously inside the user gesture.
|
|
|
Only afterwards does it register the capture-phase listener on `document` for
|
|
|
later re-resumes. It is opt-in: `new EngineSemantic({ sound: true })`.
|
|
|
|
|
|
Reusable sound signatures live in `src/uix/sema/sounds.ts`. Component packs
|
|
|
reference names (`sound('handle.pickup.air')`, `sound('notification.ping')`)
|
|
|
or dynamic recipes, never loose constants. A repository entry can be
|
|
|
synthetic or an external `.wav` with a synthetic fallback; `SoundChannel`
|
|
|
plays `sampleUrl` and falls back to synthesis when fetch/decode fails.
|
|
|
|
|
|
The **HapticChannel** is an opt-in channel; any non-visual channel is
|
|
|
fire-and-forget: it manages its own timing on its plane without affecting the
|
|
|
caller.
|
|
|
|
|
|
### Eidos — the visual layer
|
|
|
|
|
|
`Eidos` is the visual layer. Its access to the system is **the DOM**: it
|
|
|
reads parts, data-attrs, ARIA, archetypes and event signals the other layers
|
|
|
write. It does not import soma internals; it does not ask sema.
|
|
|
|
|
|
Eidos **is not just CSS**. It covers what the dead `air/` branch called the
|
|
|
"visual runtime" plus the token system — inheriting no code. Its current
|
|
|
structure:
|
|
|
|
|
|
```
|
|
|
src/uix/eidos/
|
|
|
├── active-eidos.svelte.ts ActiveEidos: visual runtime/context created by ActiveEidos.create
|
|
|
├── archetypes.css rules common to [data-archetype=*]
|
|
|
├── events.css global hints for [data-event-*] (sema visual)
|
|
|
├── generated/base.css foundation CSS generated from the base EidosConfig (incl. @font-face)
|
|
|
├── lib/ config support, recipes, CSS contract and shared types
|
|
|
└── components/{x}/ recipe + Svelte wrapper + per-component types
|
|
|
├── {x}.css recipe (selectors [data-{x}], variants)
|
|
|
├── {x}.svelte wrapper over soma's headless provider
|
|
|
├── types.ts visual Props + soma's public props
|
|
|
└── index.ts default root + attached parts
|
|
|
```
|
|
|
|
|
|
`ActiveEidos` is the source of truth for theming: primitives (color + alpha
|
|
|
scales, size map, spaces, control height, radius, border, opacity, z-index,
|
|
|
focus ring, layout, typography, shadow, motion, icon), semantic roles and
|
|
|
themes. It also resolves the active theme from its visual sources (`theme`,
|
|
|
`modeSource`, `densitySource` or defaults) and injects runtime CSS when the
|
|
|
app doesn't precompile it. External themes can come from CSS alone if they
|
|
|
honor the custom-property contract
|
|
|
(`themeSource: 'auto' | 'config' | 'css'`); `getCssContract()` publishes that
|
|
|
contract as typed data and `renderContractCss()` materializes it as empty CSS
|
|
|
from the config. Per-component recipe aliases (`--toast-*`, `--dialog-*`,
|
|
|
etc.) live in `EidosConfig.recipes` and are generated into
|
|
|
`generated/base.css`; the CSS recipes remain selectors/states, not a parallel
|
|
|
token source. `ActiveEidos.listRecipes()` and `getRecipeTokens(component)`
|
|
|
are the query surface for theme editors; they return names and defensive
|
|
|
copies, never mutable handles into the internal config. `ActiveEidos` can
|
|
|
also write runtime variables into its own style block, validating them
|
|
|
against the contract so a theme editor doesn't mutate CSS by hand, variable
|
|
|
by variable. Full-configuration persistence uses `EidosConfigDocument`
|
|
|
(`kind + version + options`), keeping `EidosConfig` a pure authoring object
|
|
|
with versioning at the storage/exchange edge.
|
|
|
|
|
|
`ActiveEidos` is also the context the Svelte wrappers consume:
|
|
|
`ActiveEidos.require()` exposes only the visual surface (`dom`, `langs`,
|
|
|
`format`, `prefs` and helpers like `resolve(...)`, `breakpoint(...)` and
|
|
|
`isBelow(...)`). Wrappers do not import `getActiveUix()` directly.
|
|
|
|
|
|
The public Svelte wrapper follows disciplined option C: a visual root
|
|
|
`<Drawer>` / `<Tabs>` / `<Checkbox>` and attached parts `<Drawer.Trigger>`,
|
|
|
`<Drawer.Content>`, etc. There is no public `Provider` and no flat
|
|
|
snippet-first API.
|
|
|
|
|
|
Selection rules (eidos reads, never writes):
|
|
|
|
|
|
```css
|
|
|
/* Style common to all triggers, component-independent */
|
|
|
[data-archetype='trigger'] {
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
/* Tint the exit anim by cause (saved/cancelled/dismissed) */
|
|
|
[data-state='closed'][data-last-action='cancelled'] {
|
|
|
animation: ...;
|
|
|
}
|
|
|
|
|
|
/* React to a perceptual signal during the hold (200–260ms by family) */
|
|
|
[data-event-family='commit'][data-event-phase='active'] {
|
|
|
animation: eidos-commit-settle 260ms var(--ease-out);
|
|
|
}
|
|
|
|
|
|
/* Variant by transient intent (the signal's, not the state's) */
|
|
|
[data-event-family='commit'][data-event-intent='threat'][data-event-phase='active'] {
|
|
|
animation: eidos-announce-pulse-threat 400ms var(--ease-spring);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The DOM is the channel between events/sema and eidos. The `VisualChannel`
|
|
|
projects the occurrence through `SignalProjector` + `ActiveDom`; Eidos
|
|
|
reacts.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3.bis ActiveUix without `frontend` (closed)
|
|
|
|
|
|
`frontend` no longer exists as an active artifact. The cross-cutting source
|
|
|
of preferences is `ActivePrefs`, following the same pattern `ActiveApp` uses;
|
|
|
DOM projection is explicit and lives outside `ActiveUix`.
|
|
|
|
|
|
The current partition:
|
|
|
|
|
|
- `uix.langs` — language and translations; syncs from `prefs.language`.
|
|
|
- `uix.format` — regional formats; consumes `prefs.locale` as a
|
|
|
`LocaleSource`.
|
|
|
- `uix.clipboard` — clipboard write capability; in standalone it is created
|
|
|
unless `clipboard:false`, in attach it is consumed from `app.clipboard`
|
|
|
when a component asks.
|
|
|
- `uix.dom` — the single writer of global attrs via `dom.apply`.
|
|
|
- `uix.motion` — the animation engine (`EngineMotion`, `arts/motion`):
|
|
|
registers + runs `--state`-moment presets (CSS settle / JS
|
|
|
spring/waapi/rect drivers). Consumed by Soma (`Presence` via `soma.motion`)
|
|
|
and Eidos (`eidos.motion`: generates CSS + registers its presets). In
|
|
|
standalone it is created with the available `dom`; in attach it reads
|
|
|
`app.motion`.
|
|
|
- `uix.prefs` — effective cross-cutting preferences: `language`, `locale`,
|
|
|
`direction`, `motion`, `sound`, `haptic`, etc.
|
|
|
- `uix.portal` — the generic portal target; layers like Soma adapt it to
|
|
|
their API (`portalTo`) without `ActiveUix` knowing those layers.
|
|
|
|
|
|
Eidos stays outside `ActiveUix`'s surface: `ActiveEidos.create(...)` creates
|
|
|
the visual context and, when runtime CSS is needed, uses `uix.dom`,
|
|
|
`uix.langs`, `uix.format` and explicit `mode`/`density` sources when the
|
|
|
integrator doesn't want the defaults.
|
|
|
|
|
|
`prefs.direction` is the single source of effective direction. If the user
|
|
|
sets no intent, it derives from `prefs.language`; calling
|
|
|
`prefs.direction.set('rtl')` makes that override rule; calling
|
|
|
`prefs.direction.clear()` goes back to deriving. The `html[dir]` attribute is
|
|
|
only the DOM projection of that effective value; `html[lang]` is the
|
|
|
symmetrical projection of `prefs.language`, and travels with it because the
|
|
|
browser reads both from the DOM — font selection, hyphenation, screen-reader
|
|
|
announcement.
|
|
|
|
|
|
That projection answers the page, not the component. A component knows its own
|
|
|
direction because it resolves one — the chain runs the prop, then prefs, and
|
|
|
never reads the DOM projection back; a component that inherits from a parent
|
|
|
composes that link at the call site rather than adding a step to the resolver.
|
|
|
The projection is why the common case needs no per-component assertion at all,
|
|
|
and the rest — the chain, which attribute carries the assertion, which selector
|
|
|
may read it — is [`canon/direction-contract.md`](../canon/direction-contract.md).
|
|
|
|
|
|
DOM projection is split by ownership:
|
|
|
|
|
|
```text
|
|
|
ActivePrefsDomProjection -> dir, lang, data-motion, data-sound, data-haptic
|
|
|
ActiveEidos -> data-theme, data-mode, data-density
|
|
|
```
|
|
|
|
|
|
In standalone mode, `createActiveUix()` instantiates `ActivePrefs` with the
|
|
|
standard UIX preset and creates the configured services. In attach mode,
|
|
|
`attachActiveUix(app)` reuses `app.prefs` because `prefs` belongs to
|
|
|
`ActiveApp`'s core. `langs` and `dom` are the required services for attach:
|
|
|
if they are missing, `attachActiveUix(app)` fails early. `clipboard`,
|
|
|
`events` and `format` are optional; if a layer needs them and the app didn't
|
|
|
declare them, `ActiveUix`'s getter fails explicitly.
|
|
|
|
|
|
`ActiveUix` does not auto-project preferences onto the DOM. The cross-modal
|
|
|
projection exists in `arts/prefs` as `createActivePrefsDomProjection(...)`;
|
|
|
the composition root that wants those global attributes wires it. This
|
|
|
allows `ActiveApp` without UIX, UIX without Eidos, or Eidos with precompiled
|
|
|
CSS — without duplicate projectors.
|
|
|
|
|
|
When a UIX shell wants runtime visual mode, the canonical flow is:
|
|
|
|
|
|
```ts
|
|
|
const prefsProjection = createActivePrefsDomProjection({ prefs: uix.prefs, dom: uix.dom });
|
|
|
const eidos = ActiveEidos.create({
|
|
|
theme: 'base',
|
|
|
modeSource,
|
|
|
applyDom: true
|
|
|
});
|
|
|
```
|
|
|
|
|
|
`prefsProjection` and `eidos` are disposed with the shell. Light/dark mode is
|
|
|
not written into `prefs.theme`; it is passed to `ActiveEidos` as a visual
|
|
|
source.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. How they articulate — the transcription chain
|
|
|
|
|
|
The four layers form a declarative transcription chain where each translates
|
|
|
the previous contract into its own language:
|
|
|
|
|
|
```
|
|
|
Morfo declares (TypeScript constant + sium schema)
|
|
|
↓
|
|
|
SomaRuntime transcribes (Soma — reading morfo + sources)
|
|
|
↓
|
|
|
Provider supplies sources/handlers (Soma — TypeScript class)
|
|
|
↓
|
|
|
Effects sync attrs (Soma — $effect + dom.apply)
|
|
|
↓
|
|
|
EngineSemantic dispatches signals (Sema — registry + prepare/dispatch)
|
|
|
↓
|
|
|
VisualChannel prepares data-event* (Sema — via SignalProjector/uix.dom)
|
|
|
↓
|
|
|
Eidos reads the DOM and applies CSS (Eidos — selectors + tokens)
|
|
|
```
|
|
|
|
|
|
Plus, in parallel (not in the chain):
|
|
|
|
|
|
- **ADom** materializes the `dom.apply`/`dom.remove` Soma asks for on the
|
|
|
derived structural attrs (data-state, aria-\*, etc.).
|
|
|
- **Sema's non-visual channels** (sound, haptic, future) receive the same
|
|
|
signal and materialize it in their modality — fire-and-forget.
|
|
|
|
|
|
The pieces with disjoint responsibilities:
|
|
|
|
|
|
| Piece | Responsibility | Doesn't do |
|
|
|
| ------------------- | ----------------------------------------------------- | ---------------------------------------- |
|
|
|
| **Morfo** | Declare the contract | Execute anything |
|
|
|
| **SomaRuntime** | Transcribe morfo into behavior | Decide business logic |
|
|
|
| **Provider** | Supply reactive sources + handlers | Write mutable attrs to the DOM |
|
|
|
| **Effects** | Apply derived attrs via `dom.apply` | Decide which attrs (morfo says that) |
|
|
|
| **EngineSemantic** | Channel registry + prepare/dispatch | Know DOM, audio, vibration |
|
|
|
| **VisualChannel** | Project `data-event*` via projector + awaited hold | Write structural attrs |
|
|
|
| **SignalProjector** | Project `data-event*` via `dom.apply` | Decide when to emit |
|
|
|
| **ADom** | Imperative DOM mutations for structural attrs | Know the upper layers |
|
|
|
|
|
|
`Eidos` stays outside that chain: it reads from the DOM; it does not
|
|
|
participate in the transcription.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 5. The causal chain of one interaction
|
|
|
|
|
|
A concrete example: the user clicks a Toast's **×** button.
|
|
|
|
|
|
```
|
|
|
1. Browser fires click → Svelte calls Close.onclick
|
|
|
|
|
|
2. Close.onclick runs:
|
|
|
void this.toastItem.runtime.trigger('emerge-dismiss')
|
|
|
|
|
|
3. SomaRuntime.trigger('emerge-dismiss'):
|
|
|
3.1. Looks up event 'emerge-dismiss' in morfo.events ✓
|
|
|
3.2. Resolves target = the Item DOM element via partRef('item')
|
|
|
3.3. AWAITS events.emit({ target, name: 'emerge-dismiss', family: 'emerge' })
|
|
|
EngineSemantic dispatches the signal to ALL registered channels:
|
|
|
- VisualChannel.prepare(): SignalProjector applies data-event*
|
|
|
via dom.apply(target, data-event-family=emerge)
|
|
|
- VisualChannel.handle(): holds the window (240ms for emerge)
|
|
|
- cleanup: dom.apply(target, data-event*=undefined)
|
|
|
- SoundChannel, HapticChannel: fire-and-forget (not awaited)
|
|
|
The Promise resolves when the VisualChannel finished the cleanup
|
|
|
(strict sequential semantics)
|
|
|
|
|
|
4. SomaRuntime invokes the provider's handler:
|
|
|
sources.events['emerge-dismiss']() →
|
|
|
this.provider.toaster.dismiss(opts.toast.current.id) →
|
|
|
toast.dismissing = true (state mutation)
|
|
|
|
|
|
5. The runtime's reactive EFFECTS see that isOpen changed:
|
|
|
resolvePartAttrs recomputes the item part's attrs
|
|
|
dom.apply(target, { 'data-state': 'closed' }) on the next tick
|
|
|
|
|
|
6. Eidos (CSS) has been reacting throughout the sequence:
|
|
|
- during t=0..240ms: [data-event^="emerge-dismiss"] fires an @keyframes fade-out
|
|
|
(CSS animation, not transition: it runs full-duration even if the attr
|
|
|
disappears afterwards)
|
|
|
- at t≈245ms: [data-state="closed"] takes over
|
|
|
- the Presence layer applies data-ending-style; CSS finishes the animation
|
|
|
```
|
|
|
|
|
|
State is the single source of truth. The DOM is derivation. The perceptual
|
|
|
signal PRECEDES the structural change by the full hold (~240ms for emerge) —
|
|
|
the caller waits for the cleanup before mutating state, giving CSS a
|
|
|
perceivable window to choreograph the exit.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 6. The primitives that travel between layers
|
|
|
|
|
|
### DOM attributes — the universal channel
|
|
|
|
|
|
Everything that travels between layers travels through DOM attributes:
|
|
|
|
|
|
| Attribute | Who writes | Who reads |
|
|
|
| ------------------------------------------- | --------------------------------------- | ----------------------------- |
|
|
|
| `data-{component}` | partProps (static) | Eidos (root selector) |
|
|
|
| `data-{component}-{part}` | partProps (static) | Eidos (part selector) |
|
|
|
| `data-archetype="trigger"` | partProps (static) | Eidos (transversal selector) |
|
|
|
| `id` | partProps | ARIA refs, tests |
|
|
|
| `role` | dom.apply (effect) | Screen readers, Eidos |
|
|
|
| `aria-*` | dom.apply (effect) | Screen readers, Eidos |
|
|
|
| `data-state="open"` | dom.apply (effect) | Eidos (variant selector) |
|
|
|
| `data-disabled` | dom.apply (effect) | Eidos (state selector) |
|
|
|
| `data-event="emerge-dismiss"` | sema.emit (transient) | Eidos (event selector) |
|
|
|
| `data-event-phase="active"` | sema.emit (transient) | Eidos |
|
|
|
| `data-event-id="sig-N"` | sema.emit (transient) | Future sound/haptic |
|
|
|
| `data-event-family="commit"` | sema.emit (transient) | Eidos (family selector) |
|
|
|
| `data-event-intent="risk"` | sema.emit (transient) | Eidos (signal tinting) |
|
|
|
| `data-event-direction="forward"` | sema.emit (transient, per-emit only) | Eidos (directional signature) |
|
|
|
| `data-color="primary"` | dom.apply (effect) | Eidos (per-token recipe) |
|
|
|
| `data-intent="risk"` | dom.apply (effect, optional per morfo) | Eidos (persistent state) |
|
|
|
| `dir` | prefs projection (page) / provider | Browser bidi, Eidos `:dir()` |
|
|
|
| `data-dir` | provider (resolved, opt-in per recipe) | Eidos (unconditional hook) |
|
|
|
| `data-last-action="cancelled"` | trigger prewrite | Eidos (exit tinting) |
|
|
|
| `data-starting-style` / `data-ending-style` | Presence layer | Eidos (animation hooks) |
|
|
|
|
|
|
**Operational rule**: what `dom.apply` writes, Svelte does not render. Static
|
|
|
identity (id + marker + archetype + ref attachment) ships via `partProps`.
|
|
|
State-derived attrs ship via `dom.apply` from effects. There is no
|
|
|
double-write.
|
|
|
|
|
|
### Cross-layer vocabularies
|
|
|
|
|
|
> **Canonical:** the semantic vocabulary (families, intents, verbs) lives in
|
|
|
> [`CANON.md`](../CANON.md). The summary below is for the cross-layer view;
|
|
|
> the canon + code are authoritative.
|
|
|
|
|
|
Two stable vocabularies anchor the articulation:
|
|
|
|
|
|
**Archetypes** — part categories that appear across multiple components. The
|
|
|
canonical inventory is the `ARCHETYPE_VOCABULARY` const
|
|
|
(`src/uix/morfo/types.ts`) — not copied here: a copied list drifted (it froze
|
|
|
at 24 while the code had 26). A `Trigger` of Dialog, Popover, DropdownMenu
|
|
|
and Tooltip is the same category — Eidos can style them transversally with
|
|
|
`[data-archetype=trigger]`.
|
|
|
|
|
|
**Verbs** (`src/uix/sema/verbs.ts:SEMA_VERBS`), grouped by family:
|
|
|
|
|
|
```
|
|
|
contact: press · tap · activate · focus · trigger · release
|
|
|
commit: select · unselect · toggle · save · submit · confirm · complete ·
|
|
|
fail · cancel · reset · discard · delete · restore · expire ·
|
|
|
acknowledge · apply · partial · block · move · set · remove ·
|
|
|
reorder · upload
|
|
|
signal: announce · notify · warn · alert · inform · emphasize · remind
|
|
|
handle: pick · carry · drop · drag · resize · reorder · rotate · scroll · zoom
|
|
|
emerge: present · dismiss · open · close · expand · collapse · reveal · hide
|
|
|
shift: enter-mode · exit-mode · navigate · route · step · return · context
|
|
|
sustain: start · progress · loading · waiting · syncing · processing ·
|
|
|
streaming · pending · retrying · upload · end
|
|
|
delegate: offer · plan · authorize · act · review · escalate · return
|
|
|
```
|
|
|
|
|
|
Verbs that look like one family but belong to another per the canon:
|
|
|
**select / toggle / acknowledge** are `commit` (they fix state; they are not
|
|
|
mere contact); **edit** is `shift.enter-mode` (it changes the regime).
|
|
|
|
|
|
`morfo.events[].name` **declares its family**: the shape is
|
|
|
`{family}-{verb}[-{nuance}]` (`commit-toggle`, `emerge-close-cancel`), and
|
|
|
`validateMorfo` rejects a name that does not start with its own family. That
|
|
|
lets Sema/Sound/Haptic/Eidos subscribe or style by family or verb without
|
|
|
enumerating components.
|
|
|
|
|
|
**Intents** (`src/uix/sema/sema-map.ts:SEMA_MAP.intents`), 6 values:
|
|
|
|
|
|
```
|
|
|
neutral — no affective load (default)
|
|
|
affirm — low positive ("all is well")
|
|
|
fulfill — resolutive positive ("goal accomplished")
|
|
|
risk — moderate negative ("check this")
|
|
|
threat — active negative ("alarm, immediate attention")
|
|
|
loss — consummated consequence (negative + low activation, posterior)
|
|
|
```
|
|
|
|
|
|
Intent is orthogonal to family: a `commit` can be `affirm` (subscribe),
|
|
|
`risk` (publish), `threat` (delete), or `neutral` (a plain toggle). The
|
|
|
provider declares it in `morfo.events[].semantic.intent` (literal) or
|
|
|
exposes it as a prop (`fromProp + supported subset`).
|
|
|
|
|
|
---
|
|
|
|
|
|
## 7. Hard rules
|
|
|
|
|
|
The operational invariants that keep the system coherent:
|
|
|
|
|
|
1. **Morfo knows no runtime code.** It is pure declaration.
|
|
|
|
|
|
2. **SomaRuntime depends on Dom and Semantic.** By construction, not by
|
|
|
import. The provider injects them.
|
|
|
|
|
|
3. **The provider does not write mutable attrs to the DOM directly.** It
|
|
|
supplies them as sources to the runtime.
|
|
|
|
|
|
4. **`Semantic` may use `Dom` (downward).** `Dom` does not know `Semantic`.
|
|
|
|
|
|
5. **`ADom` knows no upper layers.** It only applies the mutations,
|
|
|
listeners and cross-cutting DOM actions it receives.
|
|
|
|
|
|
The DOM boundary does not require wrapping local reads: a component may
|
|
|
call `el.contains(...)`, `el.closest(...)`, `el.getBoundingClientRect()`
|
|
|
or read its own element's `scrollTop`. By contrast, `document/window`
|
|
|
listeners, global queries, imperative focus and window scrolling go
|
|
|
through `ActiveDom`.
|
|
|
|
|
|
**Timing, not just ownership.** Layout-forcing reads
|
|
|
(`getBoundingClientRect`, `getComputedStyle`, `offset*`, `scroll*`,
|
|
|
`client*`) must run POST-LAYOUT, never synchronously right after a
|
|
|
DOM/style write — read-after-write forces a mid-turn reflow (the
|
|
|
`[Violation] Forced reflow while executing JavaScript` family). Defer them
|
|
|
with `dom.measure(read, node?)` (the frame-coalesced read queue, the
|
|
|
sanctioned vehicle) or from a `dom.raf` callback; a bare deferred read
|
|
|
complies just like `dom.apply` does for writes. To resolve a theme token
|
|
|
into a concrete color, use `eidos.resolveToken(token)` (config + the
|
|
|
`uix.color` engine) — NOT a `getComputedStyle` probe. The dev-only
|
|
|
`uix.perf` detector (opt-in `reflowDetector`) attributes violations at
|
|
|
runtime via Long Animation Frames. The framework governs layout READS the
|
|
|
same way `dom.apply` governs writes.
|
|
|
|
|
|
6. **`Eidos` consumes DOM and `data-*`, not Soma/Sema internals.** If it
|
|
|
needs something, it must be declared in morfo or emitted in a sema
|
|
|
signal.
|
|
|
|
|
|
7. **What `dom.apply` writes, Svelte does not render from `partProps`.** One
|
|
|
authority per attribute.
|
|
|
|
|
|
8. **State is the single source of truth. The DOM is derivation.** Handlers
|
|
|
mutate state; effects derive attrs.
|
|
|
|
|
|
9. **Event handlers in `runtime.trigger` are synchronous.** Async goes
|
|
|
before calling `trigger`.
|
|
|
|
|
|
10. **Guards live at the call-site, not inside the handler.** If the guard
|
|
|
reaches the handler, the perceptual signal was already emitted.
|
|
|
|
|
|
11. **`morfo.events[].commits` is descriptive, not executable.** It
|
|
|
documents the observable; smoke validates it.
|
|
|
|
|
|
12. **The 2-of-3 rule for extending Morfo.** A morfo extension is only
|
|
|
justified when **at least two of the three layers** (soma, sema, eidos)
|
|
|
consume it. Soma-only conveniences live in the provider via a virtual
|
|
|
prop.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 8. The authorship / transcription distinction
|
|
|
|
|
|
A useful lens for deciding where each thing lives:
|
|
|
|
|
|
- **Authorship** — written once by a human, with intent. A component's
|
|
|
`Props`, the morfo, the event handlers. It lives in the author's
|
|
|
TypeScript.
|
|
|
- **Transcription** — mechanically derived from authorship. The provider's
|
|
|
`Opts`, the per-prop `readableActive(() => x)` wrapping, the structural
|
|
|
attrs. A helper / runtime / generator derives it.
|
|
|
|
|
|
UIX aims for only the authorship to be human. Transcription is code that
|
|
|
writes code:
|
|
|
|
|
|
| Authorship | Transcription | How |
|
|
|
| --------------------------------------- | ----------------------- | -------------------------------------------- |
|
|
|
| `Props` | `Opts` | hand-written `extends WithRefOpts, StateProps<>, ActiveProps<>` — or `OptsFromProps<P, Managed, StateKey, Preserve>` |
|
|
|
| Each wrapper prop | Active/State boxes | `bindProps<XOpts>({ ... })` — target-typed |
|
|
|
| A part's whole `{ id, ref }` bag | `WithRefOpts` | `partOpts(() => id, () => ref, setRef)` |
|
|
|
| `morfo.events[].commits` | Final DOM after handler | Effects derive |
|
|
|
| `morfo.parts[].data` | Attributes on each tick | Resolver + dom.apply |
|
|
|
| `morfo.events[].name` + canonical verb | `data-event="..."` | sema.emit |
|
|
|
|
|
|
Two details the table cannot carry, both load-bearing:
|
|
|
|
|
|
- **`Preserve` is not optional decoration.** `OptsFromProps` strips `undefined`
|
|
|
from an optional prop by default; the fourth parameter lists the keys whose
|
|
|
absence MEANS something (`dir` above all) and keeps their `T | undefined`.
|
|
|
Omitting it silently destroys the distinction —
|
|
|
[`canon/direction-contract.md`](../canon/direction-contract.md) §1.
|
|
|
- **`bindProps` is target-typed, never inferred.** The declared `Opts`
|
|
|
computes the config's expected shape (`ConfigFor<O>`), so keys, getter types
|
|
|
AND setter bodies are checked and the return IS the opts — no cast. Designs
|
|
|
that infer FROM the bag degrade the setter's parameter to `any`.
|
|
|
|
|
|
That last point has a behavioural twin, and it is a **cross-layer contract**,
|
|
|
not a build step:
|
|
|
|
|
|
> **No silent internal write.** Every internal write to a bindable notifies, by
|
|
|
> construction — the change callback lives INSIDE that key's setter, which is
|
|
|
> the only write path, so the provider cannot forget it. Exactly one named
|
|
|
> exception: a **coalesced** (debounced) notification, which is never lost
|
|
|
> because clear / submit / unmount flush it. A path that writes without
|
|
|
> notifying is a defect — the `bind:` consumer and the callback consumer would
|
|
|
> see different histories of the same component.
|
|
|
|
|
|
Rules and the third convention:
|
|
|
[`guides/component-guide.md`](../guides/component-guide.md) §Callback
|
|
|
conventions; acceptance row `E-3.7` in
|
|
|
[`guides/completion-checklist.md`](../guides/completion-checklist.md).
|
|
|
|
|
|
This distinction explains why the 2-of-3 rule holds: the morfo is
|
|
|
**cross-layer authorship**. If only soma needs something, it is
|
|
|
soma-internal transcription — not authorial, and it doesn't belong in morfo.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 9. What this architecture is NOT
|
|
|
|
|
|
To avoid mission creep, it helps to fix what UIX **does not want to be**:
|
|
|
|
|
|
- **Not a visual collection.** Eidos is visual; UIX as a system is not.
|
|
|
- **Not an opinionated wrapper over existing primitives.** The four layers
|
|
|
are original; they don't wrap Radix/Headless UI.
|
|
|
- **Not a classic design system.** Tokens, themes and recipes belong to
|
|
|
Eidos, not to the core.
|
|
|
- **Not a monolithic event service that executes every modality.** Sound,
|
|
|
Haptic, Motion and future modalities register as **channels** of
|
|
|
`EngineSemantic`; each manages its own modality. The engine is only
|
|
|
registry + dispatch.
|
|
|
- **Not a global EventEmitter dressed up as architecture.** Every event has
|
|
|
a specific DOM target and a semantic owner declared in morfo.
|
|
|
- **Not a mini-DSL in JSON.** Morfo is descriptive declaration, not a
|
|
|
program. Logic lives in the provider's TypeScript; morfo only says which
|
|
|
attrs and which semantics.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 10. Project status
|
|
|
|
|
|
> Dated status snapshots ("what is implemented as of X") live in
|
|
|
> [`docs/process/`](../process/) — e.g.
|
|
|
> `active-architecture-snapshot-2026-05.md`. This document describes the
|
|
|
> architecture, not the progress.
|
|
|
|
|
|
## 11. Acknowledged risks
|
|
|
|
|
|
No design is risk-free. UIX has four, explicitly:
|
|
|
|
|
|
### 11.1 Layer excess
|
|
|
|
|
|
If the boundaries don't stay sharp, the system feels more complex than what
|
|
|
it solves. The 2-of-3 rule and the "virtual prop" doctrine mitigate this,
|
|
|
but they require sustained discipline.
|
|
|
|
|
|
### 11.2 Names without discipline
|
|
|
|
|
|
`Morfo`, `Sema`, `Soma`, `Eidos` are names that only work if the contracts
|
|
|
are sharp. If Sema starts knowing about the DOM, or Soma decides visuals,
|
|
|
the names become decoration.
|
|
|
|
|
|
### 11.3 Responsibility invasion
|
|
|
|
|
|
The constant danger is one layer trying to do another's job:
|
|
|
|
|
|
- `Sema` becoming a multimodal runtime (a regression).
|
|
|
- `Soma` deciding CSS or motion.
|
|
|
- `SomaRuntime` interpreting business logic.
|
|
|
- `Eidos` reaching into soma internals.
|
|
|
|
|
|
UIX only works if each layer accepts its limits.
|
|
|
|
|
|
### 11.4 Lack of precedent
|
|
|
|
|
|
There are no UI systems with this exact composition. That means more
|
|
|
architectural freedom but also fewer external patterns to copy when an edge
|
|
|
case appears.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 12. Why it can be worth it
|
|
|
|
|
|
If the boundaries hold, UIX offers something uncommon:
|
|
|
|
|
|
- **Architectural explainability.** Every decision falls into a recognizable
|
|
|
layer; "where does this live" has a predictable answer.
|
|
|
- **Less cross-layer drift.** The morfo is authoritative; the other layers
|
|
|
derive. Renaming a part touches one place, not six.
|
|
|
- **Automatic contract validation.** Sium schema + smoke + morfo-check catch
|
|
|
structural drift before it reaches production.
|
|
|
- **More freedom to introduce new engines.** Sound, Haptic, Motion, any
|
|
|
future modality registers as an additional `Channel` in `EngineSemantic`
|
|
|
without touching morfo or soma.
|
|
|
- **Honesty about framework-vs-integrator boundaries.** UIX provides
|
|
|
vocabularies, contracts, transport and extension points; it doesn't
|
|
|
pretend to decide every modality for every app.
|
|
|
|
|
|
The important idea:
|
|
|
|
|
|
> **Cross-modal coherence can be treated as the integrator's responsibility,
|
|
|
> not as the false promise of a centralized runtime that claims to know
|
|
|
> everything.**
|
|
|
|
|
|
---
|
|
|
|
|
|
## 13. The summary sentence
|
|
|
|
|
|
> **Morfo declares · SomaRuntime transcribes · Provider supplies · Effects
|
|
|
> sync · Semantic emits · Dom applies · Eidos reads.**
|
|
|
|
|
|
Seven words describing the whole chain. If an architectural decision
|
|
|
contradicts one of those seven, the decision is wrong — or the architecture
|
|
|
must evolve consciously.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 14. To go deeper
|
|
|
|
|
|
- [`architecture/overview.md`](./overview.md) — the general positioning (more narrative)
|
|
|
- [`architecture/morfo.md`](./morfo.md) — declaration, archetypes, the 2-of-3 rule
|
|
|
- [`architecture/sema.md`](./sema.md) — the `emit` contract, canonical verbs
|
|
|
- [`SOMA_ARCHITECTURE.md`](./soma-architecture.md) — the runtime + components
|
|
|
- [`component-guide.md`](../guides/component-guide.md) — the operational guide to create/migrate components
|
|
|
- [`architecture/eidos.md`](./eidos.md) — the visual layer: tokens, themes, recipes, wrappers
|
|
|
- [`src/arts/adom/README.md`](../../src/arts/adom/README.md) — `dom.apply` + reactive DOM services
|
|
|
- [`guia-semantica-historica.md`](../decisions/guia-semantica-historica.md) — the original API conventions (historical seed; [`CANON.md`](../CANON.md) rules)
|
|
|
|
|
|
Detailed design decisions and historical trade-offs live in the
|
|
|
`active-uix` branch's git log.
|