# arts — runtime artifacts
`src/arts/` contains the runtime building blocks of the application. Each
artifact is independent, has its own README, and follows two consistent
naming conventions:
- **`Engine*`** — public methods over private state (or no state at all).
Pure factory; the locale, logger or any volatile input is passed as
argument on every call. When an artifact has a true server-authoritative
counterpart (`perm`, `cache` ), the engine lives under `src/svrs/` .
- **`Active*`** — an `Engine*` that exposes public reactive state. Lives in a
`.svelte.ts` file because it owns `$state` . Imports must target the file
directly, not the barrel, to keep the rest of the artifact runes-free.
## Handoff 2026-05-14
La frontera entre `arts` y `uix` queda fijada por la tabla de contratos de
[`../uix/contracts.ts` ](../uix/contracts.ts ). `ActiveApp` compone servicios y
mantiene `prefs` ; `ActiveUix` consume esos servicios cuando se adjunta a una
app o los crea en modo standalone. `frontend` permanece como artefacto legacy
opt-in: `ActiveUix` ya no lo usa para preferencias UIX. La proyeccion
cross-modal pertenece a `arts/prefs` (`createActivePrefsDomProjection(...)`) y
la proyeccion visual pertenece a `ActiveEidos` .
## ActiveEngine Contract
Root active artifacts implement the shared `ActiveEngine<TSnapshot, TError>`
contract from `$libs/active` :
```ts
interface ActiveEngine< TSnapshot , TError > {
readonly loading: boolean;
readonly lastError: TError | null;
readonly disposed: boolean;
snapshot(): TSnapshot;
clearError(): void;
onChange(listener: (snapshot: TSnapshot) => void): () => void;
dispose(): void;
}
```
Conventions:
- Use direct getters (`Auth.current`, `Cache.loading` , `Perms.lastError` ),
not a module-specific `.state` object.
- Use `loading` , never `pending` , for in-flight work.
- Use `onChange()` for snapshot subscriptions. Lower-level clients may expose
their own event buses, but active roots keep this name.
- `dispose()` is idempotent, clears owned listeners/entries/resources, and
subsequent public operations throw the artifact's `XxxDisposedError` .
- Root active artifacts that create entries (`ActiveCache.entry()`,
`ActiveConnections.connection()` , etc.) own those entries and dispose them
when the root is disposed.
## Logger And Diagnostics Contract
Every artifact that emits runtime information follows the same two-layer
contract:
```ts
import type { DiagnosticEvent, Diagnostics, Logger } from '$libs/logger';
```
- Public options use `logger?: Logger` . Do not create artifact-local logger
interfaces or narrowed aliases for individual modules.
- The root logger implementation is `EngineLogger` from `$logger` ; it extends the
shared `Logger` contract from `$libs/logger` .
- Artifact code defines `<Artifact>Diagnostics` with
`create<Artifact>Diagnostics(logger?)` and emits catalogued events for
internal diagnostics.
- Diagnostic event names live in the artifact `consts.ts` as
`*_DIAGNOSTIC_EVENTS` . Messages live in `errors.ts` or `consts.ts` , never as
inline strings in runtime logic.
- `Diagnostics<TEvent>` always exposes `{ logger, emit(event) }` . The `logger`
property is the common `Logger` , so modules that need an ad-hoc `info` or
`error` still have the full logger without inventing a second interface.
- Level routing is controlled by the logger/transports via the existing
per-level enablement map, not by module-specific severity systems.
Typical shape:
```ts
export const HTTP_DIAGNOSTIC_EVENTS = {
REQUEST: 'http.request',
NETWORK_ERROR: 'http.network_error'
} as const;
export function createHttpDiagnostics(logger?: Logger): HttpDiagnostics {
return createCatalogDiagnostics({
logger,
defaultCategory: LOGGER_CATEGORY,
catalog: HTTP_DIAGNOSTIC_LOGS
});
}
```
This gives every module the same path to Sentry, Loki, Datadog, console,
test-capture transports or any future sink: inject one `Logger` , emit typed
diagnostic events, let `logger` route.
## Error Contract
Errors follow the same rule: strings are centralized, and public programmer
errors are typed.
- Error messages and error names live in `errors.ts` or `consts.ts` .
- Runtime code must not throw inline string/template errors outside tests or
vendored code.
- Public programmer errors use artifact-specific classes and guards:
`SessionDisposedError` , `ConnInvalidNameError` , `UnitsUnknownUnitError` , etc.
- Expected runtime failures should be returned as tagged data/results when the
artifact already has such a contract (`http`, `connection` , `perm` , `cache` ).
- Validation failures are data (`SiumValidationError.issues`) and diagnostics
are emitted separately when a logger is injected.
## Map
| Artifact | Layer(s) | Purpose | Depends on |
| ---------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [`langs` ](./langs/README.md ) | `EngineLangs` , `ActiveLangs` , `ActiveMonoLangs` | i18n: type-safe translations, BCP 47 resolution, plurals, refs, JSON round-trip | — |
| [`logger` ](./logger/README.md ) | `EngineLogger` | Structured logger: levels, transports, filters, vitals, dispose | — |
| [`timer` ](./timer/README.md ) | `EngineTimers` , `ActiveTimers` | Deterministic timer scheduler: clock injection, one-shots, intervals, cancellation, snapshots, backoff | `$libs/timers` , `$logger` (optional) |
| [`format` ](./format/README.md ) | `EngineFormat` , `ActiveFormat` | Localized formatting: numbers, currency, units, dates | `$logger` (currency) |
| [`adom` ](./adom/README.md ) | `ActiveDom` | Reactive DOM service: viewport, breakpoints, attribute writes, scroll lock | `$libs/dom` , `$reactive` |
| [`frontend` ](./frontend/README.md ) | `ActiveFrontend` | Legacy UI projection service. New UIX code uses `$prefs` projection for cross-modal attrs and `ActiveEidos` for visual attrs. | `$adom` |
| [`sium` ](./sium/README.md ) | `EngineSium` | Validation contracts: schemas, issues, introspection, Standard Schema interop | `$langs` (optional), `$logger` (optional), `$libs/days` , `$libs/color` |
| [`storage` ](./storage/README.md ) | `EngineStorage` , `ActiveStorage` | Reactive sync key/value: pluggable adapters, version+migrate, TTL, validation, intra-tab + cross-tab sync, reactive keys | `$sium` (Standard Schema interop, optional) |
| [`http` ](./http/README.md ) | `EngineHttp` | HTTP client: tagged `HttpResult` , Standard Schema validation, retry, timeouts, hooks, SvelteKit `event.fetch` integration | `$libs/http` , `$libs/standard-schema` (type-only), `$logger` |
| [`session` ](./session/README.md ) | `EngineSession` , `ActiveSession` | Session lifecycle: adopt/revoke/refresh, auto-refresh, 401-rescue hook, SvelteKit SSR via `adoptServer` + cookie reader | `$storage` , `$timer` , `$http` , `$logger` (optional) |
| [`connection` ](./connection/README.md ) | `EngineConnections` , `ActiveConnections` | Realtime connection registry: transports, reconnect, heartbeat, request/reply, channels, session bridge | `$timer` , `$logger` (optional), `$session` bridge (optional) |
| [`auth` ](./auth/README.md ) | `ActiveAuth` (`EngineAuth` in `$svrs/auth` ) | Authentication: password flows, CSRF, current session reflector, devices, logout, server-authoritative auth handlers | `$libs/auth` , `$http` , `$cache` , `$svrs/auth` |
| [`perm` ](./perm/README.md ) | `ActivePerms` (`EnginePerms` in `$svrs/perm` ) | Authorization: policy runtime adapter, HTTP client/handlers, cache snapshot, `<Can />` guard | `$libs/perm` , `$libs/svrs` , `$http` , `$logger` (optional) |
| [`cache` ](./cache/README.md ) | `ActiveCache` (`EngineCache` in `$svrs/cache` ) | Data cache: deterministic keys, policies, scopes, stale/revalidate, tags, memory/storage adapters | `$libs/cache` , `$storage` (adapter), `$logger` (optional) |
| [`active-app` ](./active-app/README.md ) | `ActiveApp` | App composition: core Logger + Bus + Timers + Orca + Prefs, plus declared services via factories | every artifact above |
## Composition
Most apps consume the artifacts through `active-app` :
```ts
import { createActiveApp } from '$active-app';
import {
defineActiveDom,
defineActiveFormat,
defineActiveLangs
} from '$active-app/service-factories';
const App = createActiveApp({
logger: { level: LogLevel.INFO, transports: [consoleTransport()] },
services: {
langs: defineActiveLangs({ schema, defaultLocale: 'es', fallbackChain: ['en'] }),
format: defineActiveFormat(),
dom: defineActiveDom()
}
});
App.langs.t('common.ok');
App.format.currency.format(99.5);
App.prefs.language.set('es-MX'); // propagates to langs when wired by the factory
```
`App` always exposes the fixed core (`logger`, `bus` , `timers` , `orca` ,
`prefs` ). Feature services exist only when the application declares their
slot. For translations that means `App.langs` exists when `services.langs`
is declared with `defineActiveLangs(...)` ; otherwise the property is not part
of the typed surface.
`Sium` , `Session` , `Connections` , `Auth` and `Perms` are exposed as
**factories** because they are feature/page-scoped: App injects shared
services, but construction is explicit at the call site.
```ts
const App = createActiveApp({
services: {
sium: defineEngineSium({}),
connections: defineActiveConnections({}),
auth: defineActiveAuth({ initial: data.auth }),
perm: defineActivePerm({ endpoint: '/perm' })
}
});
applyStandardOrca(App);
```
See `active-app/README.md` for the full composition contract.
## Cross-artifact dependencies
```
langs logger
\ / | \
\ / | \
format http timer
\ | /|\
adom ─── frontend \ | / | connection
\ \ \ | / | \
\ \ \ | / auth perm
───────────── active-app ─ storage ─ cache
:
sium
```
- `langs` and `logger` are the dependency-free roots (`zero-dep` libraries).
- `format` consumes `logger` only inside `currency` (rate fetcher diagnostics).
- `sium` accepts `langs` and `logger` via injection; without them it falls back
to local message interpolation.
- `timer` is the deterministic scheduler consumed by `session` and `connection` .
- `http` accepts `logger` via injection (auto-wired through `active-app` ) and keeps
shared HTTP literals/types in `$libs/http` .
- `session` consumes `storage` for persistence, `timer` for auto-refresh, and `http`
for 401-rescue integration.
- `connection` consumes `timer` for reconnect/heartbeat/ack timeouts and accepts the
App session bridge when composed through `active-app` .
- `auth` splits cleanly: `$svrs/auth` owns identity proof, CSRF and server
handlers; `$auth` owns the active client reflector. It feeds `session` ,
`perm` and `cache` through ports rather than owning their state.
- `perm` splits cleanly: `$svrs/perm` owns the authoritative engine/HTTP
handlers, while `$perm` owns the active UI reflector and `<Can />` .
- `cache` splits cleanly: `$svrs/cache` owns the imperative engine, while
`$cache` owns the active Svelte wrapper and can consume `storage` through its
storage adapter.
- `adom` depends only on the pure helpers in `libs/dom` and on `libs/reactive` .
- `frontend` depends on `adom` for DOM attribute writes and is legacy opt-in.
- `active-app` composes always-present roots and exposes factories for scoped
artifacts (`sium`, `session` , `connection` , `auth` , `perm` ).
## Shared types
| Module | Type | Used by |
| --------- | ------------------------------------------------------- | ------------------------------------------------------- |
| `$locale` | `LocaleSource` | `format.localeSource` , legacy `frontend.localeSource` , `active-app` wiring |
| `$langs` | `SupportedLocale` (`LangBase \| ${LangBase}-${string}`) | `langs` , consumers that want type-safe locales |
## Aliases
```js
// svelte.config.js
alias: {
$active-app: 'src/arts/active-app',
$adom: 'src/arts/adom',
$auth: 'src/arts/auth',
$cache: 'src/arts/cache',
$connection: 'src/arts/connection',
$frontend: 'src/arts/frontend',
$format: 'src/arts/format',
$http: 'src/arts/http',
$langs: 'src/arts/langs',
$logger: 'src/arts/logger',
$perm: 'src/arts/perm',
$session: 'src/arts/session',
$sium: 'src/arts/sium',
$storage: 'src/arts/storage',
$svrs: 'src/svrs',
$timer: 'src/arts/timer',
$libs: 'src/libs',
$locale: 'src/libs/locale',
$reactive: 'src/libs/reactive'
}
```
## Bundle policy
Every artifact is designed to tree-shake cleanly:
- `package.json` declares `"sideEffects": ["**/*.css", "**/*.svelte"]` , so any
`.ts` / `.svelte.ts` module that the bundler does not statically reach is
dropped from the production bundle.
- All barrels use **named re-exports** (`export { a, b } from './x'`) instead
of `export *` . This lets the bundler prove which symbols are reached from
a given import and drop the rest of the source module.
- `.svelte.ts` files defer module-level state (`viewport.svelte.ts`,
`body-scroll-lock.svelte.ts` ) so importing the barrel does not allocate
Svelte runes runtime for unused features.
- External adapters (`$logger/adapters/*`) and dev helpers (`$active-app/testing`,
`$sium/_examples/` ) live outside the main barrel. A consumer that does
not reference them never pays for them.
A consumer that builds
`createActiveApp({ services: { langs: defineActiveLangs({ schema }) } })` and
only calls `App.langs.t(...)` should land roughly in the 40– 50 KB minified range.
A consumer that wires every artifact (Sium + Storage cookies + legacy Frontend
preferences + Web Vitals) lands in the ~120 KB range. The difference is
the per-feature surface, paid only when reached.
## Test pages
Interactive docs now live under `web/routes/active` and `web/routes/uix` .
Older `/test/*` pages may still exist in local branches, but the canonical
artifact names are the directory names listed in the map above:
- `active-app` — full composition end-to-end
- `ecosystem` — total integration demo: auth, session, perm, cache, http, storage, sium, format, legacy frontend, adom, timer, connection, langs and logger in one app flow
- `langs` — i18n with reactive locale switching, plurals, BCP 47
- `logger` — log levels, transports, vitals, Sentry integration
- `format` — numbers / currency / units / dates with shared locale
- `frontend` — legacy theme, mode, dir, density applied to a target
- `adom` — viewport, breakpoints, scroll lock, roving focus
- `sium` — login / signup / profile schemas with translated issues
- `storage` — adapters (memory / local / session / cookie), envelope versioning + migrate, raw mode, TTL, mergeDefaults, cross-tab sync
- `http` — GET/POST with Sium validation, retry + Retry-After, timeout, cancellation, lifecycle hooks, tagged `HttpResult`
- `session` — session lifecycle: adopt/revoke/refresh with generation guard + dedup, auto-refresh, tagged `RevokeResult` , permission checks, event stream
- `timer` — scheduler snapshots, intervals, cancellation and deterministic clocks
- `connection` — websocket chat and connection/channel lifecycle
- `auth` — server-authoritative auth surface: password flow, CSRF, devices, routes and security events
- `perm` — authorization checks, `<Can />` , HTTP handlers and client cache
- `cache` — cache policies, scopes, tags and active entries
Use `/active` for the current application/runtime docs and `/uix` for the UIX
component system docs.