www
packages/www is a SvelteKit 5 fullstack application. Pages mostly call @arbe/core client methods, render TanStack DB collections, and wire up Supabase OAuth. No inline fetch, no hand-rolled payloads.
Client
createClient({ baseUrl, headers?, agentId? }) in packages/core/client.ts returns the full API surface: mutations (createHouse, createRoom, grantPermission, deleteRecord, etc.), stream operations (postMessage), and queries (listHouses, searchAgents, getOwnPermission). The app creates a single instance — browser fetch carries cookies automatically. Bots pass an Authorization header and agentId to auto-fill author_id on posted messages.
Mutation methods call typed builders in packages/core/mutations/ for Zod validation, then POST to /api/mutate. Builders are pure functions producing MutationRequest objects — the same code validates on both client and server.
Routing
/house/[house_id]/room/[room_id] is the primary pattern. Route params are the source of truth for navigation. The root redirects to the agent’s first house.
Management routes: /house/[house_id]/agents (agent registry, bot creation), /house/[house_id]/agents/[agent_id] (bot edit), /house/[house_id]/room/[room_id]/agents (room access control), /invite/[token] (invite claim). Edit routes at /house/[house_id]/edit and /house/[house_id]/room/[room_id]/edit handle renaming.
Layout
The shell has two regions: a sidebar and a main content area. The sidebar shows the current house’s room list, a house switcher, and a “new room” action (requires w). Visibility toggles via appState.sidebar_visible. On mobile, the sidebar overlays content.
State
appState in packages/www/src/lib/state.svelte.ts is a Svelte 5 $state object — flat, directly mutable, persisted to localStorage via $effect.root. Components import and write to it. Auth session stays server-driven (SvelteKit load functions). Collection data is TanStack DB’s concern.
Collections
TanStack DB provides reactive collections backed by two sync sources (see storage.md for the sync architecture).
Electric SQL collections — recordsCollection, permissionsCollection — sync Postgres rows through server-proxied shape routes (/api/shapes/) that inject credentials. Stream collections carry durable stream content (messages), created per scope via createStreamDB and populated by tailing through /api/streams/follow/.
The records collection exports typed record variants (HouseRecord, RoomRecord, AgentRecord) as a discriminated union on type. The Zod schema stays loose for Electric parsing, but each variant narrows content to the matching core schema (HouseContent, RoomContent, AgentContent). Components cast once at the query boundary — e.g. as HouseRecord | undefined — and get full type narrowing on content fields from there. No as string casts or defensive optional chaining needed.
Components consume live queries. The backing source is invisible to them.
Write path
Structural writes are optimistic. The collection handler applies locally, POSTs to /api/mutate, and awaits a Postgres txid for Electric reconciliation. Stream writes (messages) POST to /api/streams/[scope_id] and confirm through the tail. Wire details in storage.md.
Chat UI
The room page renders a message list from the stream collection and a compose input. Messages render markdown (sanitized). Auto-scroll pins to the bottom unless the agent has scrolled up. The compose input sends on enter (shift+enter for newline) and is disabled without w on the scope.
Slash commands
Typing / in the compose input triggers an autocomplete from the command registry in packages/www/src/lib/commands/. Each command declares name, args, and behavior: post (insert a message), silent (side effect only), or replace (append to input). Built-in set: /invite, /shrug, /rename, /leave.