Architecture
Suite is a local-first collaborative canvas. One Loro document per workspace is the source of truth, @suite/graph validates every write, and the React canvas renders a projection of that document. A card exists whether or not a board displays it; a board is a view that places cards.
This page is the map. Each subsystem has its own reference page, and the reasoning behind a choice is recorded in the internal decision log, which is not part of the deployed docs.
Three rules
Section titled “Three rules”- Offline works. The network accelerates sync; it is not required to open or edit a workspace.
- Loro owns the document. The UI reads through
@suite/graphand writes through typed methods, never raw Loro. - Every write is validated. Zod schemas in
packages/graph/src/schema.tsparse at the boundary before a value reaches the CRDT.
Repo map
Section titled “Repo map”| Path | Responsibility |
|---|---|
apps/canvas |
React 19 canvas, Tiptap editors, panels, launcher, Zustand UI state |
apps/collab-server |
Loro WebSocket relay, HTTP API, SQLite stores, projection worker |
packages/graph |
Schema, records, typed CRDT layer, pure planning and projection |
services/auth |
Identity, organizations, invitations, seats, licence checks |
services/billing |
Stripe webhook, hosted licence issue |
apps/control-plane |
Accounts, billing and the Postgres they share, in one process of its own |
apps/docs |
Starlight site built from docs/ |
apps/site |
Marketing page for suite.run |
e2e |
Playwright journeys, grouped by area |
scripts |
Docs, limits, shape and docs-symbol checks |
The canvas app starts at / with a workspace launcher. A workspace open uses ?workspace=<id> (?doc= is a legacy alias), and a share link uses ?share=<token>.
Data flow
Section titled “Data flow”pointer / keyboard │ ▼React surface ── typed method ──► WorkspaceDocument ── Zod ──► LoroDoc ▲ │ │ ▼ └──── snapshot store ◄──── observe ─┘WorkspaceDocument is the only writer. It exposes a facade over domain modules in packages/graph/src/loro/, and each mutation validates with the schema before it commits. apps/canvas/src/store/workspaceSnapshotStore.ts owns the one WorkspaceDocument.observe call site; components read stable records from that store through selectors.
Rich text lives in content and titles containers. The snapshot store exposes subscribeCardChanges for consumers that need text edits, so no other module subscribes to the document itself.
Zustand holds UI state only: selection, active board, open panels, connection status, transient gestures. Document truth never lives there.
Subsystems
Section titled “Subsystems”| Page | Covers |
|---|---|
| data-model.md | Roots, record shapes, leaf writes, text containers, references, publish and projection |
| canvas.md | Boards, placements, sections, links, gestures, camera, presence |
| editing.md | Tiptap, title/body, fields, templates, cards, references, undo |
| ui.md | Shell, library, panels, dialogs, styles, command palette, focus, accessibility |
| runtime.md | Boot order, persistence, relay, shares, authorization, attachments, diagnostics |
| security-model.md | Trust boundary, credentials, capabilities, what a share exposes |
| billing.md | Stripe events, licence claims, hosted mode |
| environment.md | Every variable the relay and services read |
| limits.md | Every quota, default and enforcing function |
| conventions.md | Code shape, naming, errors, tests |
| glossary.md | Terms used across the pages |
Toolchain
Section titled “Toolchain”pnpm workspaces plus Turborepo. One package per bounded surface, so @suite/graph tests without React and the relay tests without a browser.
Biome handles lint and format. Vitest covers graph, server, auth and canvas. Storybook runs the visual and accessibility stories. Playwright covers end-to-end journeys. Billing sits outside the pnpm workspace and runs under node --test.
Commands and tiers are in ../how-to/test-and-verify.md. Deployment is ../how-to/deploy.md.
Status
Section titled “Status”Local persistence, accounts, live collaboration, share links, publishing and billing are implemented. A browser with no session works locally and syncs nothing; a signed-in account has a personal organization from its first minute.
Two processes are the target shape, behind one Caddy. The relay (apps/collab-server) keeps the socket, its SQLite stores and the projection. A control plane (apps/control-plane) hosts Postgres, identity and billing, and is the only process that depends on pg: it mounts services/auth at /api/auth/*, services/billing at /api/billing/* and /stripe/*, and the relay imports neither. The canvas is a static bundle, so a deployment serves three hosts from that pair (deploy/Caddyfile).
The credential is a session JWT. The relay verifies it against the key set the control plane publishes at <SUITE_AUTH_URL>/api/auth/jwks, keeps that set in a five-minute cache, and answers a join from the token’s claims: apps/collab-server/src/sync/seatGate.ts needs no database read. The owner key is gone (ADR-0076), and a share link stays a separate capability for a visitor with no account. security-model.md states what that means for a deployment.
The product direction and the decisions that are settled are in ../explanation/design.md.