Example app

Studio: every vertical, one app.

An AI-assisted mixed-media document editor — agentic chat, a document worker, co-editing, gating, credits, a canvas minimap — wired in one composition file and a set of tiny adapters.

Studio is an AI-assisted mixed-media document editor that composes every StaticFlux vertical into one app, against mock services only: a mock data transport, a mock BFF session endpoint, a mock collaboration network in the document worker, a mock AI provider and memory / IndexedDB storage. It is the integration proof for the framework — and the place every API was designed against first (docs/FEATURE-NEEDS.md).

Shell
cd apps/studio
npx vite                                # dev server — packages resolve from source
npx vite build                          # dist/ + sf-manifest.json, sf-precache.json, sf-build-report.json
cd ../.. && npx vitest run apps/studio  # headless integration tests: boots the whole app per test

Try it

  • / — the guest welcome (guest and logged-in are the same app; boot never waits on auth).
  • /studio/doc/1?panel=chat&prompt=Add%20an%20image — redirects to /login with the intent held; sign in as Ada (free) or Grace (pro), return to the editor with the chat prompt prefilled — never auto-sent.
  • /onboarding/step/1 — wizard steps as view sub-routes.
  • /settings — theme, language incl. Arabic / RTL, the expert workspace.
  • ?sf-mode=web:compact — the mobile editor variant · ?sf-flag=studio/ai-suggestions:false — a local flag override · ?sf-debug — the console telemetry sink.
  • Ctrl/⌘+K — the capability palette from @staticflux/kit · /settings density Focus / Standard / Pro — the kit restyled through the theme contract.

Six features

FeatureKindWeightWhat it exercises
studio-desktop · studio-mobileUI + logicheavy (archive)The editor, as two per-mode features sharing one document store. The document lives in a supervised worker (ECS + Yjs); the main thread renders a read-only projection and sends edits as intents. Co-editing with a mock peer and presence.
agent-chatheadless logic + panelwarmAgentic chat over a streamed resource. Its tools are capabilities — insert text, insert image, summarize, clear — gated per subject, confirmed when destructive, metered in credits.
onboardingwizardcheapSteps as view sub-routes, progress in a user-data slice, a coach mark on the AI button, copy from an experiment variant binding.
settingsUIcheapTheme and locale slices (Arabic included, for RTL), and the expert workspace switch.
billingUI + dialogscheapThe routable upsell dialog /d/upgrade, the low-credit banner and plan facts.

Each feature is a manifest plus a lazily loaded module in its own chunk. Features never import each other: they talk through src/app/contracts.ts — tokens, capabilities, intents, URL bindings, slices, surfaces, flags, meters and chunk ids.

Architecture

Entryindex.html → main.ts (inline mode detector) → boot-browser.ts → bootStudio → mountStudioUi
app/compose.ts (THE composition) · contracts.ts · routes.ts · policies.ts · endpoints.ts · surfaces.ts · features.ts · chunks.ts · document.ts · late.ts · shell.ts · telemetry.ts
adapters/one file per package pair — 39 in total
features/studio-desktop · studio-mobile · agent-chat · onboarding · settings · billing — one chunk each
worker/the document worker: protocol, engine + presence + mock co-editor, spawners
mocks/mock server: BFF session, docs, credits, flags; data routes; shared memory storage
boot sequence
bootApp
  → critical units: platform facts, app profile, flag resolver
    (+ what they need: session store, data client, storage registry, handoff)
  → core-ready
  → subsystems: router first navigation, meter sync, workspace restore,
    locale ⇄ ?lang, perf marks, post-boot
  → running
off the boot path: feature chunks, the document runtime (+ worker), the late units chunk (AI, WebMCP, warmup)

39 adapters: where packages meet

Packages never import their siblings; they expose ports (tokens, multi tokens, stage plugins, structural interfaces) and stay ignorant of each other. The app bridges them with one small adapter file per package pair, each with a header comment stating what it bridges. When a package has a gap, the workaround lives in the adapter with a PACKAGE GAP comment — never as a patch to the package.

apps/studio/src/adapters/
// adapters/gate-loader.ts — gate → loader: denied chunks are never fetched (I-8)
// adapters/auth-router.ts — auth → router: the auth-boundary stage sends guests to /login with returnTo
// adapters/urlstate-router.ts — urlstate ⇄ router: one shared history, intent capture/authorize plugins
// adapters/capabilities-gate.ts — gate → capabilities: decide port (agent subject distinct) + meter port
  • a11y-router.ts — a11y ⇄ routerroute-change focus + announcement
  • advisors-loader.ts — advisors → loaderMarkov next-chunk advisor ranks warmup candidates
  • auth-config.ts — auth (+platform) → configevaluation context (targeting key, mode)
  • auth-data.ts — auth → datauser-data identity follows the session; logout wipes (loads the deferred user-data chunk if needed)
  • auth-gate.ts — auth → gatesession + BFF claims (plan, entitlements) as facts
  • auth-router.ts — auth → routerauth-boundary stage: guests → /login with returnTo
  • auth-runtime.ts — auth ⇄ runtimesession change → supervisor.refresh() (epoch); per-principal session scope
  • capabilities-gate.ts — gate → capabilitiesdecide port (agent subject distinct) + meter port (reserve/commit)
  • capabilities-messaging.ts — messaging → capabilitiesconfirm port (stacked dialog)
  • capabilities-router.ts — capabilities → routerupgrade denial opens /d/upgrade
  • capabilities-runtime.ts — runtime → capabilitiesactivation port (ensureActive)
  • config-gate.ts — config → gateflags as facts
  • data-config.ts — data → configremote flag layer over the data client
  • document-worker.ts — document ⇄ worker (+data)supervised worker, replicas, projection, DocumentStore (lazy chunk)
  • gate-data.ts — gate ⇄ datameter backend over billing endpoints; balance sync
  • gate-loader.ts — gate → loaderchunk gate (denied chunks never fetched, I-8)
  • gate-router.ts — gate → routerauthorize stage for requires.gate
  • gate-ui.ts — gate → uiDecidePort (slots, workspaces, surfaces)
  • handoff-auth.ts — handoff → authguest draft carried through login (upgrade hook)
  • handoff-urlstate.ts — handoff ⇄ urlstateintent hold slot (lazy handoff store), login return port, intent restore stage (signed in)
  • i18n-urlstate.ts — i18n ⇄ urlstatesticky ?lang=; <html lang dir>
  • loader-perf.ts — loader → perfLoAF script URL → chunk attribution
  • loader-router.ts — router → loaderobserve feature transitions; warm likely chunks after navigation
  • loader-runtime.ts — loader ⇄ runtimedev manifest, static-import strategy, feature host loading via chunk loader
  • messaging-router.ts — messaging → routerDialogSurfacePort over the routable dialog bridge
  • perf-runtime.ts — perf ⇄ runtimemount → mark session; ReadinessToken reports marks to both
  • platform-gate.ts — platform → gateform factor / host / capabilities as facts
  • platform-loader.ts — platform → loaderwarmup facts + loader profile
  • platform-perf.ts — platform → perfdevice tier for budgets
  • platform-router.ts — platform → routerper-mode variant predicate; profile change → router.refresh()
  • runtime-data.ts — runtime → dataepoch source = app epoch; pause → transport pause
  • runtime-messaging.ts — runtime → messaging"Reconnecting…" while paused (functional surface manager)
  • runtime-router.ts — runtime → routerfeature driver mounting into the main outlet
  • runtime-urlstate.ts — runtime → urlstateURL sync in the frame scheduler
  • storage-shell.ts — storage → shell/messagingworkspace/seen preference stores
  • storage-ui.ts — storage → uiappearance preference store (device: scheme, contrast, density, motion)
  • urlstate-router.ts — urlstate ⇄ routerone shared history; intent capture/authorize plugins; flush hook
  • urlstate-runtime.ts — urlstate → runtimeauthorized intent activates its owner (chat opens the panel)
  • worker-core.ts — auth/config/gate/platform → workercore-context hub (lazy chunk)

What it proves

  • Every experience type without special cases — chat (headless agent + UI + streamed resources), wizard (sub-routes, exit guards, persisted progress), complex editor (document worker, projection UI, lazy panels, archive) and co-editing (CRDT + collaboration transport + presence).
  • Free vs premium, guest vs logged-in, expert workspace, embedded mode — all as policies, bindings and per-mode features; no feature reads a flag or a mode.
  • Mock services only. No network, no real auth. Client gating is UX, not security: the mock backend re-checks plan and credits.

AGENTS.md on GitHub: @staticflux/runtime · @staticflux/router · @staticflux/gate · @staticflux/worker · @staticflux/document