'The bus is a mechanical typed event engine. It creates envelopes, invokes listeners and reports listener failures; it does not decide application orchestration by itself.'
],
table:[
{
name:'publish(type, payload, options?)',
purpose:'Synchronously publish an event.',
notes:'Returns the envelope and collected listener failures.'
},
{
name:'publishAsync(type, payload, options?)',
purpose:'Publish and await async listeners.',
notes:'Useful when tests or orchestration need deterministic completion.'
},
{
name:'on(type, listener, options?)',
purpose:'Subscribe to one event type.',
notes:'Options support signal, once and listener id.'
},
{
name:'once(type, listener, options?)',
purpose:'Subscribe for one delivery.',
notes:'Removes the listener before invoking it.'
},
{
name:'onAny(listener, options?)',
purpose:'Subscribe to every event.',
notes:'For diagnostics, test capture and devtools-like surfaces.'
},
{
name:'listenerCount(type?)',
purpose:'Inspect listener counts.',
notes:'Helps detect leaks in long-lived apps and tests.'
},
{
name:'dispose()',
purpose:'Remove listeners and close the bus.',
notes:'Further public operations throw BusDisposedError.'
}
]
},
{
title:'EngineBusOptions',
table:[
{
name:'logger',
purpose:'Shared Logger contract.',
notes:'App injects App.Logger into App.Bus.'
},
{
name:'clock',
purpose:'Timestamp source.',
notes:'App injects Timers.clock for deterministic tests.'
},
{
name:'idFactory',
purpose:'Envelope id generation.',
notes:'Defaults to incremental bus-* ids.'
},
{
name:'maxListenersPerEvent',
purpose:'Leak warning threshold.',
notes:'Default is BUS_DEFAULT_MAX_LISTENERS_PER_EVENT.'
},
{
name:'listenerErrorMode',
purpose:'Default listener failure policy.',
notes:'log-and-continue, throw or collect; publish options can override it per event.'
}
]
},
{
title:'BusEnvelope',
table:[
{
name:'id / type / payload / at',
purpose:'Core event facts.',
notes:'Every event is wrapped before listeners receive it.'
},
{
name:'source',
purpose:'Publisher identity.',
notes:'Defaults to buss; app translators use their module source.'
},
{
name:'correlationId / causationId',
purpose:'Trace event chains.',
notes:'Useful for cross-module integration tests and diagnostics.'
},
{
name:'context / tags',
purpose:'Non-secret metadata.',
notes:'Do not put credentials, tokens or authorization headers in public app events.'
body:'Auth proves identity. Session keeps continuity. Permissions decide access. Storage must not persist secrets.'
},
overview:[
'Auth is split into a shared language package, a server-authoritative engine and an active Svelte client. The server engine owns identity proof, CSRF, password/recovery flows, OAuth/MFA primitives, device methods and security events. The active client currently exposes current, password, recovery, email verification, sign-out and device methods over HTTP.',
'Use Auth when the app needs to sign users in or out, load the current actor, request verification or reset flows, and bind that identity proof to session state. Do not use Auth to decide permissions or to store long-lived credentials in the browser.',
'In an App composition, Auth receives App.Http for route calls, App.Cache for invalidation and App.Logger for diagnostics.'
],
dynamics:[
'The server creates an EngineAuth with ports. Those ports are the real integration points: store persists auth records, actors maps credentials to actor refs, sess starts or ends sessions, cach clears identity-scoped data, and logr records security events.',
'The browser creates ActiveAuth only as a reflector. It loads /current, sends CSRF-protected commands to server routes, updates current after successful responses and emits local state changes for UI. A protected server action must never trust ActiveAuth state.',
'The normal request path is: server hook resolves current auth, page load serializes a safe AuthCurrentView, ActiveAuth hydrates that snapshot, user triggers sign-in/out, server mutates session, and app-event consumers react only when their own auto*On options opt in.'
notes:'Singleton per App; second call throws AappAlreadyCreatedError.'
}
],
api: artifactApis.auth,
sections:[
{
title:'Creation and route wiring',
body:[
'Auth has two creation points. The server creates EngineAuth from $svrs/auth with ports for storage, actors, session, cache, crypto and logging. The browser creates ActiveAuth through App.createActiveAuth(), which talks to the server routes and mirrors the safe AuthCurrentView.',
'Do not create ActiveAuth before the server routes exist. The client cannot prove identity by itself; every sign-in, sign-out, CSRF and recovery operation is a server command.'
'The server engine exposes route handlers and ports instead of importing a specific database, mailer or framework. That keeps auth portable and testable.'
'Production auth should not use memory adapters. Use createDbAuthAdapter(repos) from $svrs/auth and map your ORM or SQL repositories to the AuthRepository contract.',
'The reference PostgreSQL schema lives in src/svrs/auth/sql/postgres.sql. It models credentials, flows, linked accounts, devices, session bindings and refresh token rotation without forcing a concrete ORM.'
],
table:[
{
name:'auth_credentials',
purpose:'Credential and factor records.',
notes:'Stores identifier hashes and password hashes, never raw passwords.'
},
{
name:'auth_flows',
purpose:'Expiring server flows.',
notes:'Email verification, password reset, OAuth state and MFA/WebAuthn challenges.'
},
{
name:'auth_session_bindings',
purpose:'Auth-to-session binding.',
notes:'Lets auth revoke by actor, device, password change, refresh reuse or logout.'
'Password sign-up and sign-in bind a successful identity proof to session state through the session port.',
'CSRF is requested and sent automatically by ActiveAuth for state-changing client calls.',
'Email verification and password reset use expiring server flows; tokens are verified on the server.',
'Device listing and revoke are exposed by the engine/client contracts; route wiring must expose AUTH_ROUTE_PATHS.DEVICES and DEVICE_REVOKE explicitly because the default handler map currently covers current, CSRF, password, recovery and sign-out routes.'
'Auth proves identity and mutates session through explicit server ports; Cache and Permissions react to public app events only when their own consumer options opt in.',
body:'Modules import interfaces, error classes, SILENT_BUS and helpers from $libs/buss. The composition root (aapp) and tests import the createEngineBus implementation from $buss. Modules must never import from $buss directly — that mirrors the Logger / SILENT_LOGGER pattern in $libs/logr.'
},
overview:[
'Bus is the low-level event engine. It owns typed subscriptions, envelopes, listener error handling, listener leak warnings and disposal.',
'The contract surface (EngineBus, EventPublisher, BusEnvelope, BusListener, error classes, SILENT_BUS, generic constants) lives in $libs/buss as pure types and helpers — no runtime state. The engine factory createEngineBus() lives in $buss and imports its types from $libs/buss.',
'The application-level event vocabulary lives in $libs/aapp/events. App creates one central App.Bus and uses translators to turn selected module events into stable app.* events.',
'Destructive reactions are deliberately outside App orchestration. Cache, Permissions and Connections decide their own automatic reactions through autoInvalidateOn or autoReauthOn.'
],
dynamics:[
'Modules do not create their own buses and do not import from $buss. They accept an injected EngineBus or EventPublisher (interfaces from $libs/buss) and publish/listen through constants exported by the event owner.',
'Module events are local facts such as sess.changed. App translators can publish public app events from those facts, such as app.user.identity.changed.',
'Consumers subscribe only to public app.* events. This avoids direct imports such as Cache knowing Session internals, while still allowing integrated behavior.',
'In the current implementation, App automatically wires the session identity translator and the dispose-starting publisher. Other app events are already typed and can be published explicitly by app code or future translators.'
],
commonMistakes:[
{
name:'importing from $buss in module code',
purpose:'$buss exposes the concrete engine; module code must depend only on the contract layer.',
notes:'Import EngineBus, EventPublisher, BusSubscription, BusEnvelope, SILENT_BUS, etc. from $libs/buss. Only aapp and tests touch $buss.'
},
{
name:'putting secrets in app events',
purpose:'Public events can be logged, captured or inspected by diagnostics.',
notes:'Use actor ids, tenant ids, causes and correlation ids; never tokens, passwords or authorization headers.'
},
{
name:'expecting publish to clear data',
purpose:'Publishing is observable and does not mutate other artifacts by default.',
notes:'Enable cache.autoInvalidateOn, permissions.autoInvalidateOn or connections.autoReauthOn in the consumer.'
},
{
name:'using bus for private in-module events',
purpose:'It creates needless coupling and noise.',
notes:'Keep private event emitters inside the artifact; use App.Bus for cross-artifact facts.'
},
{
name:'creating createEngineBus() inside modules',
purpose:'It fragments the event graph and makes cross-artifact behavior invisible.',
notes:'Use App.Bus in application code; modules should accept an injected bus/publisher.'
},
{
name:'publishing inline event strings',
purpose:'Magic strings drift and break refactors.',
notes:'Declare event constants in uppercase at the owner boundary, for example APP_EVENT_USER_IDENTITY_CHANGED.'
notes:'Use as a default for modules that take an optional bus, mirror of SILENT_LOGGER. Importable from $libs/buss.'
},
{
name:'createEngineBus(options?)',
purpose:'Build a real bus engine. The implementation factory.',
notes:'Importable only from $buss (the artifact). Used by App and tests; never inside an artifact module.'
},
{
name:'App.Bus',
purpose:'Application bus instance, always-present.',
notes:'Created by createActiveApp with Logger and Timers.clock injected. Modules receive it through their factory options.'
}
],
api: artifactApis.buss,
sections:[
{
title:'Layer Split — Where to import from',
body:[
'libs/buss owns the pure contract: interfaces, generic constants, error classes, SILENT_BUS, helpers. Nothing here has runtime state. Module code (cach, sess, perm, conn, …) imports types from $libs/buss exclusively — that is the rule.',
'arts/buss owns the engine implementation. createEngineBus() lives there. The composition root (aapp) and tests import the engine from $buss; nothing else does. The split mirrors the Logger / SILENT_LOGGER pattern in $libs/logr.',
'A module that needs to publish or subscribe accepts a bus through its factory options, typed against the interface from $libs/buss. App passes App.Bus when constructing the module via App.createActive*; otherwise the module falls back to SILENT_BUS so the publish path stays unconditional.'
],
table:[
{
name:'arts/<module> code',
purpose:'Always import from $libs/buss.',
notes:'Types, SILENT_BUS, error classes, helpers. Never createEngineBus.'
},
{
name:'arts/aapp/active-app.svelte.ts',
purpose:'Imports createEngineBus from $buss.',
notes:'The single legitimate consumer of the engine implementation in production code.'
},
{
name:'Tests that build their own bus',
purpose:'Import createEngineBus from $buss.',
notes:'Acceptable; bus engines are cheap and self-contained.'
},
{
name:'libs/aapp/events.ts',
purpose:'Imports interfaces from $libs/buss.',
notes:'Defines APP_EVENT_* constants and helpers; never touches the engine.'
'Every event constant value across the framework is scoped with the artifact prefix to disambiguate aggregated logs, devtools and any future cross-bus serialization. A bare "delete" or "hit" leaves observers guessing which module emitted it; "cach.delete" or "cach.hit" does not.',
'Format: <artifact>.<concept> with snake_case for compound terms inside a level (cach.stale_if_error) and dots for hierarchy (cach.refresh.start). Same convention auth has used since v0 with AUTH_EVENT_NAMES.',
'Method labels passed to ensureLive(method) follow the same rule: sess.adopt, cach.invalidate, auth.signOut. Bare names like "adopt" or "invalidate" never appear in error messages.'
notes:'Stays "*" because the framework bus owns the symbol. Per-emitter wildcards are scoped (CACHE_EVENT_ALL = "cach.*", etc.).'
}
]
},
{
title:'App.Event Contract',
body:[
'Stable public app events live in $libs/aapp/events, not inside individual artifacts. Examples are APP_EVENT_USER_IDENTITY_CHANGED, APP_EVENT_TENANT_SWITCHED, APP_EVENT_PERMISSIONS_REFRESH_REQUESTED, APP_EVENT_CONNECTIVITY_CHANGED, APP_EVENT_CACHE_INVALIDATE_REQUESTED and APP_EVENT_DISPOSE_STARTING.',
'Event constants are mandatory, and app events should normally flow through onApp* / publishApp* helpers. Those helpers keep payload typing, runtime guards and unsafe-payload checks in one place. Raw bus.on(APP_EVENT_...) is acceptable for low-level tests; raw bus.publish("app...") is not application code.',
'The string value stays lowercase scoped; the exported symbol is uppercase snake. Current built-in translators publish APP_EVENT_USER_IDENTITY_CHANGED from SESS_EVENT_CHANGED and APP_EVENT_DISPOSE_STARTING during App.dispose(). The other app events are stable contract points for explicit app code and future translators.'
]
},
{
title:'Centralization Rule',
body:[
'There is one app-level bus per application: App.Bus. Modules do not call createEngineBus() for their own private island. They accept an injected bus, EventPublisher or AppEventBus shape from the composition root.',
'createEngineBus() remains public because App, isolated services and unit tests need to build a bus engine, but it is not the normal usage pattern inside framework modules.'
]
},
{
title:'Translator vs Consumer',
table:[
{
name:'aapp orchestration',
purpose:'Controls translators.',
notes:'standard enables the available built-in translators; silent disables them.'
},
{
name:'Cache autoInvalidateOn',
purpose:'Consumer reaction.',
notes:'standard clears on user identity change and tenant switch events.'
},
{
name:'Permissions autoInvalidateOn',
purpose:'Consumer reaction.',
notes:'standard invalidates on identity, permission refresh and tenant switch events.'
},
{
name:'Connections autoReauthOn',
purpose:'Consumer reaction.',
notes:'standard derives a session source; connection.session and connection.auth decide per socket.'
}
]
},
{
title:'Identity Change Flow',
body:[
'This example shows the complete chain. The bus does not perform the side effects; it only carries the public identity event. Cache, Permissions and Connections react because each consumer opted in explicitly.',
'Connections has two extra guards: the registry must opt in with autoReauthOn, and each concrete connection must enable session handling and provide auth if it expects to send a fresh authentication frame.'
],
table:[
{
name:'orchestration: standard',
purpose:'Enables the App translator.',
notes:'Session changes can become APP_EVENT_USER_IDENTITY_CHANGED.'
},
{
name:'cache.autoInvalidateOn',
purpose:'Cache-owned reaction.',
notes:'Clears active client cache on identity/tenant app events.'
},
{
name:'permissions.autoInvalidateOn',
purpose:'Permissions-owned reaction.',
notes:'Invalidates the local decision cache on identity/permission/tenant app events.'
'Session is not authentication. It does not verify passwords, OAuth callbacks or permissions. It keeps continuity once another layer has established identity.',
'The session shape has three slots: user, credential and data. User is identity, credential is how the client can refresh or authenticate to the server, and data is session-scoped application state such as tenantId or cartId.',
'The engine is deterministic and testable: refresh can be deduped, storage is pluggable, timers can be injected and lifecycle changes can emit safe sess.* events when a bus is injected.'
'Session starts from a trusted source: SSR data, an auth success response or a refresh callback. adoptServer() is for data already validated on the server; adopt() validates client-provided values through configured schemas.',
'Refresh is a controlled lifecycle operation. Multiple concurrent refresh calls dedupe into one remote call; a successful refresh replaces current, a null result clears the session, and a thrown refresh keeps the previous value while reporting the error.',
'Revoke is different from clearLocal(). revoke() calls the configured server revoke callback and then clears state. clearLocal() only wipes local memory/storage when the server already invalidated the session.'
'In an application, create Session through App.createActiveSession() so App can inject Logger and Bus. Create createEngineSession()/createActiveSession() directly only in tests, isolated services or when you deliberately do not use App.',
'The storage option decides where the local session snapshot lives. The callbacks onRefresh and onRevoke are the only places that should call the server. Session itself does not know your endpoint shape.'
'The HTTP integration can intercept 401 responses, trigger a deduped refresh and retry once with fresh credentials. A sentinel header prevents infinite retry loops.'
'When Session is created through App, App injects App.Bus. Session publishes safe sess.* events after state has been updated; aapp can translate SESS_EVENT_CHANGED into APP_EVENT_USER_IDENTITY_CHANGED.',
'Cache, Permissions and Connections do not listen to sess.* directly. They react only to public app.* events and only when their own autoInvalidateOn or autoReauthOn option opts in.'
body:'The server engine is the authority. ActivePermissions is for UX, cache and rendering helpers only.'
},
overview:[
'Permissions is not a simple RBAC helper. It models authorization as explicit decisions using roles, attributes, relations and context in the same runtime.',
'The server engine evaluates policies and returns rich decisions: allow, deny, not applicable or indeterminate. The client reflector batches remote checks, caches snapshots, can invalidate from public app events and exposes a Can component for UI gates.',
'ActivePermissions can subscribe to public app.* events when App injects App.Bus. The default is safe: no automatic invalidation happens unless autoInvalidateOn is configured.',
"autoInvalidateOn: 'standard' currently invalidates the local decision cache on APP_EVENT_USER_IDENTITY_CHANGED, APP_EVENT_PERMISSIONS_REFRESH_REQUESTED and APP_EVENT_TENANT_SWITCHED.",
'Permissions does not listen to sess.*, auth internals or cache internals directly. The public bus contract keeps the client reflector decoupled from the source of the identity or policy change.'
'$svrs/perm ships a reference PostgreSQL model in src/svrs/perm/sql/postgres.sql. It creates permission_policies, permission_relations and permission_decision_audit.',
'The framework does not own your ORM. Map the SQL to Prisma, Drizzle, Kysely or raw SQL and expose a tiny repository that returns PolicyIR rows and relation facts.',
'Policy rows are versioned per tenant and namespace. Store validated PolicyIR JSON, publish exactly one active version per policy id, and invalidate permission/cache scopes after publishing.'
'permission_relations is the generic ReBAC table. Use it for facts like project.owner, team.member or invoice.approver when the domain does not already have a stronger table.',
'Relation providers must fail closed. If tenant id, resource id or backend state is unavailable, return unknown instead of false unless the database definitely says the relation does not exist.'
],
table:[
{
name:'hasRelation(input)',
purpose:'Check one actor/resource/relation tuple.',
notes:'Used by rel(...).is(actor()) policy conditions.'
},
{
name:'listSubjects(input)',
purpose:'Reverse lookup actors for a resource.',
notes:'Used by who() and admin/audit views.'
},
{
name:'listResources(input)',
purpose:'Find resources reachable by a subject.',
notes:'Useful for list pages and reverse queries.'
'Can is a UI convenience component. It asks ActivePermissions whether content should render, but it never replaces server checks. Protected mutations and reads must still call the server engine.'
'Cache answers more than "do I have this value?". It decides freshness, scope safety, invalidation status, stale serving and what to do if the origin fails.',
'The pure runtime lives in libs/cach. Server and active wrappers compose that runtime with adapters, diagnostics and Svelte state.',
'Scopes are part of the key. Private data should be cached under tenant, actor or permission scopes, never as public.'
],
dynamics:[
'A query normalizes its key, resolves scope, reads the adapter, evaluates policy windows and tag epochs, then decides whether to return fresh data, serve stale data while refreshing, serve stale-if-error or call the fetcher.',
'Invalidation does not have to delete every entry immediately. Tag epochs mark entries as stale/invalidated lazily, which keeps distributed adapters cheap while explain() can still tell why a value was rejected.',
'ActiveCache wraps the same runtime with entry state. The UI reads data/status/loading/error from ActiveCacheEntry; the server or service layer uses EngineCache for request handlers, jobs and SSR.',
'When App.Bus is injected, ActiveCache can opt in to public app-event invalidation. The default remains none, so publishing an identity event does not clear data unless cache.autoInvalidateOn says so.'
'App.Cache is always present and defaults to an in-memory adapter. That is safe for first use, tests and local UI state, but production private data should configure scopeResolver and an intentional adapter strategy.',
'Server-side cache roots should be created from $svrs/cach. Client active cache roots are UI helpers and should not become the source of truth for permission-sensitive data.'
'$libs/cach contains the pure runtime. $svrs/cach is the server-side entry point for request handlers, jobs and shared services. $cach/createActiveCache is the Svelte-facing wrapper for UI state.',
'The same policy/key/scope semantics apply in both places, but server caches normally use process/distributed adapters while client caches use memory or Storage-backed adapters.'
],
table:[
{
name:'defaultMemoryAdapter',
purpose:'Options for the implicit memory adapter.',
notes:
'Only applies when adapter is omitted; demos/tests can suppress the production warning explicitly.'
},
{
name:'server request cache',
purpose:'Deduplicate work during one request.',
notes:'Good for SSR loaders and backend services.'
},
{
name:'process memory cache',
purpose:'Fast L1 cache.',
notes:'Use max entries/size and clear by actor/tenant on identity changes.'
},
{
name:'storage/tiered/distributed adapter',
purpose:'Durable or shared L2.',
notes:'Redis/KV adapters should honor epochs, TTL and namespace boundaries.'
},
{
name:'ActiveCacheEntry',
purpose:'UI state around one query.',
notes:'Status/loading/error live here; security decisions still belong server-side.'
'Entries can be invalidated by exact key, key prefix, tag, scope or predicate. Tags are the recommended high-level contract between mutations and cached reads.'
'App injects App.Bus into App.Cache. By default Cache only observes its own cache events; it does not clear when the app publishes identity or tenant facts.',
"Set cache.autoInvalidateOn: 'standard' to clear the active cache on APP_EVENT_USER_IDENTITY_CHANGED and APP_EVENT_TENANT_SWITCHED. Use a list such as ['userIdentityChange'] for narrower behavior.",
'Cache listens only to public app.* events. It should not import sess, auth or perm internals to decide when private data becomes unsafe.'
'explain() tells why the cache served, missed, refreshed or rejected an entry. This is intentionally part of the public surface because cache bugs are otherwise invisible.'
'Storage is the framework primitive for client-safe persistence: preferences, drafts, non-secret state and SSR-readable cookies.',
'Each entry has defaults, serializer, optional version/migration, optional validation, TTL and remove/reset semantics. Active entries expose current as reactive state.',
'Storage is intentionally synchronous in v1. Async stores such as IndexedDB can be added later through a separate async contract.',
'Only createEngineStorage() and createActiveStorage() are root factories. Adapters are storage backends: they are passed to a root through options.adapter or overridden per entry.'
],
dynamics:[
'A root owns the adapter default, namespace, entry registry, bus and diagnostics. Every call to entry() creates a handle for one logical key and stores strings through the selected adapter.',
'Reads always pass through serializer, envelope, TTL, migration, mergeDefaults and validation. If a stored value is expired, corrupt or invalid, the entry falls back to its default and reports through diagnostics/onError.',
'Active entries add a reactive current property and an onChange stream. current is not a deep persistence proxy: assigning a nested field does not write to the adapter unless you reassign the value or call update().'
'Storage has one root and many entries. The root is created with createEngineStorage() or createActiveStorage(); entries are created through Storage.entry(key, defaults, options?).',
'Adapters are not roots. localAdapter, sessionAdapter, cookieAdapter and createMemoryAdapter() implement SyncStorageAdapter. They decide where strings are stored; the root decides namespaces, entry registry, diagnostics and lifecycle.',
'aapp creates App.Storage internally with createActiveStorage(). Feature code should normally use App.Storage.entry(...). Create your own root only in tests, SSR helpers or isolated subsystems.'
'aapp uses Storage to persist Frontend preferences when frontend.persist is enabled. fend owns preference semantics; aapp only bridges them to storage entries.'
'Http wraps fetch with a tagged result model. Callers receive ok/value for success and structured non-ok results for HTTP, network, timeout and validation failures.',
'It centralizes shared HTTP constants in libs/http so methods, headers and content types are not magic strings scattered through modules.',
'The engine supports retry, Retry-After, abort signals, request/response hooks, body validation and response validation through Standard Schema.'
],
dynamics:[
'Every call builds a request from root defaults plus per-call options, runs before hooks, serializes query/body, starts timeout control, executes fetch, parses the response, validates it when a schema is supplied and returns a tagged result instead of throwing for normal HTTP failures.',
'Retry is policy-driven. The engine can retry safe/idempotent calls, respect Retry-After and stop on total timeout. Callers still inspect the final tagged result.',
'with(options) creates a scoped child client. This is the preferred way to bind SvelteKit event.fetch, request-specific headers or tenant-specific baseUrl without mutating the root client.'
'App.Http is the normal browser/client client. On the server, create a scoped child with event.fetch so SvelteKit cookies, internal routes and platform behavior are preserved.',
'Do not mutate a global Http instance with request-specific headers. Use with() to create a child client for one request, tenant or backend integration.'
'Formats centralizes everything that depends on locale but is not text translation: numeric separators, currency, units and date/time conventions.',
'It deliberately does not depend on Lang. Both consume the same LocaleSource when composed through App, so changing App locale updates translations and formats from one source of truth.',
'Each submodule can run as an engine or active wrapper. Auto values derive from locale until the user sets an explicit override.'
],
dynamics:[
'ActiveFormats listens to a LocaleSource. When the locale changes, every submodule recomputes values that are still auto: numeric separators, currency, unit system, date order and hour cycle.',
'Manual values are sticky. If a user calls setCurrency(), setSystem(), setDateOrder() or similar setters, later locale changes do not overwrite that choice. clearX() returns that setting to auto mode.',
'Numbers, Currency, Units and Dates can run independently, but the aggregate Formats root is the normal application surface because it keeps one locale and one auto/manual contract across every formatter.'
'When Formats is created through App, it receives a LocaleSource backed by App.Lang. That is the intended wiring: one locale change updates translations, numbers, currency, units, dates and Frontend direction.',
'Create standalone sub-engines only when a non-UI service needs one formatting domain. UI code should prefer App.Formats so auto/manual state stays consistent.'
'If a setting is auto, locale changes can update it. If the user sets a value explicitly, locale changes do not override it. clearX() returns to auto.'
'Application-level frontend preferences: dir, theme, mode, density, reduced motion and reduced sound, applied through adom.',
factories:['createActiveFrontend'],
dependsOn:['$adom','$locale'],
layer:'ActiveFrontend',
overview:[
'Frontend is not a component system. It owns global presentation preferences and writes stable attributes to the configured DOM target.',
'dir, mode and reducedMotion can be auto. theme, density and reducedSound are explicit preferences. The same auto/manual dynamic used by Formats applies here.',
'When built through App, Frontend consumes App.Lang as LocaleSource and App.Dom as the DOM writer.'
],
dynamics:[
'Frontend reads locale and environment preferences, resolves auto-capable values, and writes the result to DOM attributes through Dom.apply(). Components then style against those attributes instead of each component recalculating theme, direction or density.',
'The auto/manual dynamic matches Formats. While dir/mode/reducedMotion are auto, locale or media-query changes can update them. Once the user sets a value explicitly, later auto sources stop overriding it until clearX() is called.',
'Persistence is not owned by Frontend. Frontend emits preference changes; aapp can bridge selected keys to Storage when frontend.persist is configured.'
'Reactive DOM service for viewport, breakpoints, attribute writes, scroll lock and focus-oriented helpers.',
factories:['createActiveDom'],
dependsOn:['$libs/dom','$reactive'],
layer:'ActiveDom',
overview:[
'Dom is the runtime DOM layer. It keeps browser-specific behavior out of formatting, frontend preferences and feature modules.',
'It can resolve responsive values, track viewport, apply attributes declaratively and coordinate scroll lock. In SSR, browser tracking is inert.',
'Frontend uses Dom.apply() to update application-level attrs, so theme/dir changes are centralized.'
],
dynamics:[
'In the browser, ActiveDom installs viewport/media listeners and updates a reactive viewport snapshot. During SSR those listeners are inert so imports stay safe.',
'Responsive values are resolved from the current breakpoint. Consumers can pass a scalar or a breakpoint map and receive the best value for the active viewport.',
'DOM writes are centralized through apply(). Frontend uses this to update html/body attributes, while feature modules can use the same writer for controlled attribute/class/style updates.'
'App.Dom is the shared DOM service for the application. It should own viewport tracking, responsive resolution and global writes that other artifacts depend on.',
'When using createActiveDom() directly, decide the target boundary explicitly: document-level app shell, an embedded widget root, or a test DOM. Do not let unrelated feature modules write global attributes independently.'
'Sium is page-scoped by design. Forms live in pages and features, so App exposes createSiumEngine() instead of keeping a global validator alive for every route.',
'The engine creates schemas, validates values, returns structured issues and carries metadata for UI generation. It supports Standard Schema interoperability.',
'When created from App, Sium receives App.Lang and App.Logger. If Lang is not provided, its local resolver is only a fallback.'
],
dynamics:[
'Create a Sium engine close to the form or feature that needs it. Define schemas once, then call validate() for submitted values or Standard Schema consumers such as HTTP body validation.',
'Validation returns structured issues with paths. UI code should render issues by path instead of flattening everything into one string, otherwise nested forms become hard to explain.',
'Translations flow through the injected Lang engine when present. Sium should not copy Lang behavior; its local resolver exists only so validation still has fallback messages without i18n.'
'Sium is intentionally not an always-on App root. Create it where the form, HTTP body or feature validator lives, usually through App.createSiumEngine() so Lang and Logger are injected.',
'Schemas should be owned by the feature that validates the data. Share schemas only when multiple boundaries validate the exact same shape, for example a form and an HTTP endpoint.'
'Structured logger with shared Logger contract, EngineLogger runtime, transports, filters, failure routing and diagnostics support.',
factories:['createEngineLogger'],
dependsOn:['$libs/logr'],
layer:'EngineLogger / Logger contract',
overview:[
'The minimal Logger interface lives in libs/logr and is what all modules receive. EngineLogger lives in arts/logr and extends that contract with transports, history, child loggers, timers and lifecycle.',
'Diagnostics are a cataloged layer above Logger. They map internal framework events to normal logger calls without forcing every log to become an event.',
'Transports can be filtered per level, buffered, throttled on failure and adapted to Sentry, Datadog, Loki, Logtail or OpenTelemetry.'
],
dynamics:[
'Application code and modules receive the small Logger contract from $libs/logr: trace, debug, info, warn, error and fatal. They do not need to know whether the concrete logger is EngineLogger, Sentry, a test spy or a custom adapter.',
'EngineLogger is the runtime implementation. It normalizes entries, applies level enablement, routes to transports, handles transport failures and emits synthetic failure entries to the remaining transports without cascading into the failed one.',
'Diagnostics sit above Logger. A module can define a catalog of internal events and map each one to a normal logger call. Free-form info/debug logs still go directly through Logger.'
'Create one EngineLogger at the application root and inject its Logger contract into other artifacts. Modules should depend on $libs/logr.Logger, not on EngineLogger internals.',
'Use child/context helpers for module scopes if needed, but keep category names and standard messages in module consts.ts.'
'The framework uses an explicit per-level enablement map for routing, not module-specific severity systems. EngineLogger still performs final filtering and transport dispatch.'
'If a transport fails, EngineLogger emits a synthetic failure entry to the remaining transports and marks deniedFor so the failing sink does not receive its own failure. Throttling prevents cascades.'
'Timers centralizes scheduling so modules do not scatter setTimeout and setInterval logic. Reconnect, heartbeat, auto-refresh and cache-like workflows can all share a deterministic scheduler.',
'The engine supports keyed timers, replacement, cancellation by scope, intervals, snapshots and injected clocks for deterministic tests.',
'ActiveTimers exposes reactive snapshots for debug panels and test pages.'
],
dynamics:[
'Every timer has a stable key and optional scope. Scheduling with replace cancels the previous entry for that key before creating the next one, which prevents duplicate refresh/reconnect loops.',
'The engine uses the injected clock for timestamps and backoff calculations, so tests can advance time deterministically instead of waiting for real time.',
'ActiveTimers wraps the engine and exposes entries() as reactive snapshots. That is for observability and test pages; business logic should keep using schedule(), interval(), cancel() and cancelScope().'
'App.Timers is the shared scheduler for browser-side artifacts. Connections, auto-refresh and debug panels should use this root instead of creating their own timer islands.',
'Create EngineTimers directly for deterministic unit tests, workers or server utilities that need an injected clock and do not need Svelte state.'
'Connections is a registry of named realtime connections. A connection is not the engine: EngineConnections owns all connection state; each Connection owns transport, channels, heartbeat, reconnect and request/reply.',
'The transport contract is pluggable. Browser WebSocket is one transport; tests and demos can use mock transports without changing the connection runtime.',
'When composed through App, Connections receives App.Timers, App.Logger and App.Bus. The registry can derive a session source from public app identity events when autoReauthOn opts in; a connection still needs its own session option and auth provider to reauthenticate.'
'Create one registry, then create named connections inside it. The registry tracks all names and aggregate state; each connection owns its transport lifecycle, channel collection, send buffer, heartbeat timers and reconnect strategy.',
'Transports emit open/message/close/failure signals. The connection translates those into framework states, schedules heartbeat and reconnect through Timers, and routes logs through the shared Logger/diagnostic constants.',
'Channels are scoped streams over a connection. They can join, leave, send and request. After reconnect, auto-join channels rejoin so feature code does not rebuild subscriptions manually.',
'Identity reauth is opt-in at three levels: the registry must enable autoReauthOn, each connection must enable its own session option, and the connection must provide auth. Without those, App.Bus identity events are just observable facts.'
'Create a Connections registry first. The registry is the root; individual connections are children owned by that registry. App.createActiveConnections() injects App.Timers, App.Logger and App.Bus.',
'Use one registry for related realtime connections so aggregate state, disposal and app-event reauth reactions stay coordinated.'
'If autoReauthOn is enabled in the registry, Connections derives a ConnectionSessionSource from APP_EVENT_USER_IDENTITY_CHANGED. It does not listen to sess.* directly and does not import auth, perm or cache internals.',
'Each connection still decides whether to react through its own session option. Reauthentication additionally requires an auth provider; without auth there is no credential payload to send, so the connection can only be observed or manually handled.'
'ActiveConnections tracks names and states reactively: connectedNames, reconnectingNames, failedNames, allConnected and anyConnected are ready for UI panels.'