Guide · @staticflux/runtime

Lifecycles & the runtime

Every lifecycle in StaticFlux is a typed finite state machine. The runtime supervises the app, its subsystems and its features — and the state you are in decides which operations even exist.

The app is a supervisor

docs/LIFECYCLES.md §1
starting → core-ready → running ⇄ degraded
                         running ⇄ paused
            running|degraded|paused → reinitializing → core-ready
            any → shutting-down → terminated

bootApp({ units, telemetry, subsystems }) resolves critical units, starts critical subsystems and returns a Result<App>. The supervisor broadcasts app-wide commands in dependency order (reverse for teardown): pause, resume, abort (one root signal cancels everything in flight), refresh (an epoch bump — stale data is revalidated and late responses dropped, no reload), reinitialize (back to core and re-boot, no reload) and shutdown.

Subsystems declare dependsOn and a restart policy (backoff, maxRestarts per windowMs). A subsystem that gives up moves the app to degraded and its dependents are told they are impaired. Features may request commands (supervisor.request); a command policy decides.

TypeScript
import { defineSubsystem } from '@staticflux/runtime';

export const syncSubsystem = defineSubsystem({
  id: 'app/background-sync',
  dependsOn: ['router/navigation'],
  restart: { maxRestarts: 3, windowMs: 60_000 },
  start: async (ctx) => queue.connect(ctx),
  pause: () => queue.hold(),
  resume: () => queue.release(),
  refresh: async (epoch) => queue.resetFor(epoch),
  stop: () => queue.close(),
});

Features: logic and UI live separately

docs/LIFECYCLES.md §6
declared → loading → loaded → activating → activated
activated → mounting → mounted ⇄ hidden
mounted|hidden → unmounting → activated            (logic stays warm)
activated → deactivating → loaded                  (logic disposed)
loaded → archived (heavy: snapshot persisted) | evicted

A feature is defineFeature({ id, weight, retention, modes, budget, readiness, load }) plus a module from defineFeatureModule({ units, activate, mount, visible, hide, unmount, snapshot, restore }). The logic scope contains the UI scope, which contains the visible scope — so a mounted feature always has live logic (I-5), and hidden keeps DOM and state while dropping effects.

Weight classes set the default demotion: cheap features are recreated, warm keep logic alive, heavy ones archive a snapshot (to any AsyncStore) and rehydrate fast. The resource manager enforces budgets on memory pressure and device tier — framework budgets win over feature preferences — and never demotes a feature in use. Dependencies between features are ref-counted (I-6).

Frame scheduler

createFrameScheduler() coalesces updates into one flush per frame, separates measure and mutate phases (no layout thrashing), runs prioritized postTask work (interaction | critical | visible | idle), offers yieldToMain and idleUntilUrgent, and falls back where scheduler.postTask / yield are missing (Safari).

Workers: scheduling and memory ledgers

Inside workers, @staticflux/runtime/worker gives the same priorities without frames: createWorkerScheduler() slices synchronous work into 8 ms tasks (postTask, yield, shouldYield), and createLongTaskDetector reports tasks over 50 ms. @staticflux/runtime/memory is a per-thread cache memory ledger for render, media and export workers: caches register with an evict callback and report their bytes; over budget — or when memory pressure rises — the ledger evicts lower priorities first, synchronously, before the resource manager demotes any feature.

TypeScript
import { createMemoryBudgets, MiB } from '@staticflux/runtime/memory';

const memory = createMemoryBudgets({ budgets: { gpu: 512 * MiB }, pressure });
const tiles = memory.register({ id: 'render/tiles', kind: 'gpu', evict: (bytes) => cache.evict(bytes) });
if (!tiles.ok) return tiles;
tiles.value.report(cache.bytes);            // after every change; headroom() before a big allocation

All lifecycles, transitions and invariants

AGENTS.md on GitHub: @staticflux/runtime · @staticflux/core