Sandbox
The sandbox is a gallery of small, self-contained pages at /sandbox/<name> —
one per capability — that each exercise a single package or AI feature
in isolation. It's where you see a piece of the stack actually run before wiring it
into a real feature, and where you confirm a backing service is configured.
It is deliberately separate from the product app. The sandbox lives outside the
_authed shell: no sidebar, no product nav — its own minimal chrome (a brand link
back to the hub, a "PulsN ↗" escape link to the app, and the language / theme /
AI-provider toggles). It still requires a session (the layout redirects to
/login), and every server call re-checks auth. You reach it by URL, not from the
app menu.
Graceful degradation is the point
Most sandboxes talk to an external service (Valkey, Centrifugo, an LLM, …). None of
them require it to load. The recurring pattern: a server function calls the
package's is<Thing>Enabled() and returns a discriminated union —
{ enabled: false } or { enabled: true, … }. The page then shows either a green
"on" badge with live data, or an amber alert naming the exact env var to set.
So you can run the whole sandbox area with nothing but Postgres, and switch each capability on by adding one variable. (This mirrors the package graceful-degradation contract.)
Switching the AI provider
The shell carries a provider toggle (a segmented control) that writes an
ai_provider cookie — openrouter (hosted, default) or ollama (local).
fusion-ai reads that cookie per request, so every AI sandbox switches backend
with no reload. See AI.
Package sandboxes
One per package. Those with no "Needs" run with zero setup (just Postgres).
| Sandbox | Demonstrates | Needs |
|---|---|---|
/sandbox/cache | set a key in Valkey, read it back, show the value + remaining TTL | VALKEY_URL |
/sandbox/metrics | fetch the live Prometheus exposition document from the in-process registry | METRICS_ENABLED=1 |
/sandbox/jobs | the always-safe enqueue seam (the heavy Hatchet client stays lazy) | HATCHET_CLIENT_TOKEN |
/sandbox/realtime | mint a Centrifugo connection token, connect a WebSocket, see publications | CENTRIFUGO_* |
/sandbox/collab | a collaborative editor on a shared Yjs doc (local-only without a server) | COLLAB_WS_URL (optional) |
/sandbox/edge-sso | resolve an SSO identity across authenticated / spoofed / anonymous headers | — |
/sandbox/ldap | filter builder, DN→CN, AD-group→project sync diff, in-memory search | — |
/sandbox/import-export | live CSV import plan (new / update / skip / error), preview, download | — |
/sandbox/gis | a MapLibre map of Swedish cities; click → WGS84 → SWEREF 99 coordinates | — |
/sandbox/settings | edit runtime app config (name, language, sign-ups, …) stored in app_config | — |
/sandbox/audit | record a change-history row and see the audit feed, newest first | — |
/sandbox/mailer | trigger an email recorded to the mailbox outbox (real delivery optional) | SMTP_HOST (optional) |
The last three are backed by fusion-db foundation tables (app_config, audit_log,
mailbox), so they need only Postgres — no external service to switch on.
AI-feature sandboxes
Each demos one @tanstack/ai capability through fusion-ai, using the
production fusion-ui chat components. All need an AI provider configured
(OPENROUTER_API_KEY or OLLAMA_BASE_URL).
| Sandbox | Demonstrates | Feature |
|---|---|---|
/sandbox/ai | one-shot prompt → answer (PromptInput + AiResponse) | generateText |
/sandbox/streaming | token-by-token chat | streamChat + useChat / SSE |
/sandbox/tools | a tool-calling agent loop (roll_dice, get_current_time) | server tools + maxIterations |
/sandbox/reasoning | a reasoning model streaming its "thinking" separately from the answer | reasoning model + collapsible |
/sandbox/persistence | a chat thread that survives reload (localStorage) | Chat threadId + persistence |
/sandbox/generative-ui | a tool call renders a live bar chart inline instead of a tool line | custom renderToolPart |
/sandbox/assistant | a floating assistant whose client tool flips the app's theme | client tool + ChatWidget |
The AI sandboxes call API route handlers under src/routes/api/sandbox/ that
delegate to fusion-ai's streamChat — see AI for that side.
The proactive-agent sandbox
/sandbox/agent is the operator console for the proactive agent:
run a tick on demand, watch the run state and the findings it proposes, see what's
delivered vs. suppressed, toggle the global kill switch, and check per-user
consent. It runs with a deterministic demo model when no AI provider is set (so it
works with zero config), and produces real findings once a provider is configured. The
agent's conversational side is the chat-first home.
SSR safety
Some packages are browser-only (ProseKit/Yjs in collab, MapLibre in gis,
centrifuge in realtime). Those components are loaded with lazy() and rendered
only after mount, or imported dynamically inside the click handler — so a
browser-only dependency never reaches the server render.
Adding a sandbox
Add one entry to the sandboxes array in src/routes/sandbox.index.tsx (kept
inside the component so its labels follow the active locale) and a matching
src/routes/sandbox.<name>.tsx route. Follow the is<X>Enabled() →
badge-or-alert pattern so it degrades like the rest.