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).
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/loginwith 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·/settingsdensity Focus / Standard / Pro — the kit restyled through the theme contract.
Six features
| Feature | Kind | Weight | What it exercises |
|---|---|---|---|
studio-desktop · studio-mobile | UI + logic | heavy (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-chat | headless logic + panel | warm | Agentic chat over a streamed resource. Its tools are capabilities — insert text, insert image, summarize, clear — gated per subject, confirmed when destructive, metered in credits. |
onboarding | wizard | cheap | Steps as view sub-routes, progress in a user-data slice, a coach mark on the AI button, copy from an experiment variant binding. |
settings | UI | cheap | Theme and locale slices (Arabic included, for RTL), and the expert workspace switch. |
billing | UI + dialogs | cheap | The 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
index.html → main.ts (inline mode detector) → boot-browser.ts → bootStudio → mountStudioUicompose.ts (THE composition) · contracts.ts · routes.ts · policies.ts · endpoints.ts · surfaces.ts · features.ts · chunks.ts · document.ts · late.ts · shell.ts · telemetry.tsstudio-desktop · studio-mobile · agent-chat · onboarding · settings · billing — one chunk eachbootApp
→ 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.
// 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 + announcementadvisors-loader.ts— advisors → loaderMarkov next-chunk advisor ranks warmup candidatesauth-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 factsauth-router.ts— auth → routerauth-boundarystage: guests →/loginwithreturnToauth-runtime.ts— auth ⇄ runtimesession change →supervisor.refresh()(epoch); per-principal session scopecapabilities-gate.ts— gate → capabilitiesdecide port (agent subject distinct) + meter port (reserve/commit)capabilities-messaging.ts— messaging → capabilitiesconfirm port (stacked dialog)capabilities-router.ts— capabilities → routerupgradedenial opens/d/upgradecapabilities-runtime.ts— runtime → capabilitiesactivation port (ensureActive)config-gate.ts— config → gateflags as factsdata-config.ts— data → configremote flag layer over the data clientdocument-worker.ts— document ⇄ worker (+data)supervised worker, replicas, projection, DocumentStore (lazy chunk)gate-data.ts— gate ⇄ datameter backend over billing endpoints; balance syncgate-loader.ts— gate → loaderchunk gate (denied chunks never fetched, I-8)gate-router.ts— gate → routerauthorizestage forrequires.gategate-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 attributionloader-router.ts— router → loaderobserve feature transitions; warm likely chunks after navigationloader-runtime.ts— loader ⇄ runtimedev manifest, static-import strategy, feature host loading via chunk loadermessaging-router.ts— messaging → routerDialogSurfacePortover the routable dialog bridgeperf-runtime.ts— perf ⇄ runtimemount → mark session;ReadinessTokenreports marks to bothplatform-gate.ts— platform → gateform factor / host / capabilities as factsplatform-loader.ts— platform → loaderwarmup facts + loader profileplatform-perf.ts— platform → perfdevice tier for budgetsplatform-router.ts— platform → routerper-mode variant predicate; profile change →router.refresh()runtime-data.ts— runtime → dataepoch source = app epoch; pause → transport pauseruntime-messaging.ts— runtime → messaging"Reconnecting…" while paused (functional surface manager)runtime-router.ts— runtime → routerfeature driver mounting into the main outletruntime-urlstate.ts— runtime → urlstateURL sync in the frame schedulerstorage-shell.ts— storage → shell/messagingworkspace/seen preference storesstorage-ui.ts— storage → uiappearance preference store (device: scheme, contrast, density, motion)urlstate-router.ts— urlstate ⇄ routerone shared history; intent capture/authorize plugins; flush hookurlstate-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.
Studio README
Run commands, architecture map and the full adapter table.
compose.ts
The single place every package is wired.
Before / After
Patterns lifted from Studio, side by side.
AGENTS.md on GitHub: @staticflux/runtime · @staticflux/router · @staticflux/gate · @staticflux/worker · @staticflux/document