Flatbox — an integrated service
Flatbox is an app in the sidebar like any other, but it's different in one important way: its data lives in a separate service, not this app's database. Where ProcessN and the other domains are in-process (the app owns their tables and rules), Flatbox is reached over HTTP, and it computes its own permissions. It's the example's blueprint for "wire in a system you don't own."
Flatbox is a (lightly modelled) document-review system: BIM/drawing review sessions, the packages and documents in them, and review comments. The app shows read-only views of all of it.
Coming from Django? This is the difference between a model you query directly and a third-party API you call with
requestsand a token — except the call is a server function and the remote service tells you what the user may edit.
Where it lives
| Layer | Files |
|---|---|
| Routes | src/routes/_authed.flatbox.projects.tsx · …sessions.tsx · …packages.tsx · …comments.tsx |
| Server functions | src/lib/flatbox-integration-server.ts (HTTP → the Flatbox server) |
| Tables | none in this app — Flatbox owns its own database |
| The other service | example/flatbox/ (a separate TanStack Start app + DB) |
The seam: HTTP + a delegated permission flag
A Flatbox page loader calls a server function, which makes an authenticated HTTP request to the Flatbox server with a Bearer API token. The response already says, per row, what the user may do:
So authorization is delegated: this app doesn't replicate Flatbox's rules, it trusts
the canEdit the other service computed. (The in-process domains are the
opposite — this app enforces everything itself.) Users map by email: the Flatbox seed
creates accounts with the same emails as the main seed, so the token's Flatbox user lines
up with a real Portfolio login. The wired-up demo user is
Sara Lindqvist (sara.lindqvist.tikab@p4o.se).
It degrades gracefully
Like every capability, the integration is safe when it isn't configured:
- No
FLATBOX_API_TOKEN→ the server function returns503with a hint to runbun run db:seed:flatbox. - Flatbox unreachable →
502("isbun run dev:flatboxrunning?"). - On the dashboard, the Flatbox card swallows any failure and yields nothing, so one down integration never breaks the page. Because it's the slow, cross-server card, the dashboard streams — fast in-process cards render first, the Flatbox card arrives after.
Run it
Flatbox uses the same Postgres server as the app, in a separate database:
docker compose up -d postgres # shared Postgres
bun run db:create:flatbox # create the flatbox database
bun run db:migrate:flatbox # apply its migrations
bun run db:seed:flatbox # seed it — prints FLATBOX_API_TOKEN at the end
bun run dev:flatbox # serve Flatbox on http://localhost:3100Paste the values the seed prints into the main app's .env.local, then start it:
FLATBOX_API_URL=http://localhost:3100
FLATBOX_API_TOKEN=fbx_demo_integrated_sara_0123456789abcdefbun run dev, sign in, and the Flatbox nav group lists Sara's projects, review
sessions, packages and comments — read-only, scoped by Flatbox itself.
Extending it
- A new Flatbox view → add a route + a server function that calls another Flatbox
endpoint; render the rows read-only, honouring the
canEditflags it returns. - A different external service → copy the shape: a
*-integration-server.tsthat holds the HTTP calls + token, loaders that call it, graceful502/503on failure. The seam is the lesson, not Flatbox itself. - Writes → only if the remote service exposes them; never reconstruct its permission rules locally — call it and trust its answer.