www
apps/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? }) in packages/core/client.ts returns the full API surface: per-entity mutations (createHouse / updateHouse / deleteHouse, same shape for environments / threads; updateAgent; addMember / removeMember), stream operations (createEntry, tailThreadStream), and queries (listHouses, listMembers, searchAgents). The app creates a single instance — browser fetch carries cookies automatically. Bots pass an Authorization header with their API key; the www proxy resolves that to the bot’s identity and stamps authorId on stream writes server-side.
Mutation methods POST/PATCH/DELETE per-entity routes under /api/{houses,agents,environments,threads}, plus nested membership routes under /api/houses/:id/members. Write routes run through withWriteRoute(...) (apps/www/src/lib/server/write-route.ts) or explicit route-local membership guards. Inputs are re-validated server-side against the Zod schemas in @arbe/core/schemas.
Routing
/houses/[house_id] (the house’s thread list) and /threads/[id] (a chat thread) are the primary patterns. Route params are the source of truth for navigation. The root redirects to the agent’s first house.
Management routes: /houses/[house_id]/agents (agent registry, membership), /houses/[house_id]/agents/new (bot creation), /houses/[house_id]/agents/[agent_id] (bot edit), /invite/[token] (invite claim). Renaming a house is at /houses/[house_id]/edit.
Account management routes live under /account: /account (overview, export, self-delete), /account/tokens (active API tokens with last-used timestamps), /account/connect (agent ID + API/CLI quickstart), /account/telemetry. The account/+layout.svelte renders the page heading, sub-nav, and breadcrumb for all of them.
Layout
The shell renders a header, main content area, right aside, and status line from apps/www/src/routes/+layout.svelte. Cross-route chrome state lives in apps/www/src/lib/layout.svelte.ts; auth session stays server-driven (SvelteKit load functions), and 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 sync Postgres rows through server-proxied shape routes (/api/shapes/) that inject credentials. Discovery uses membersCollection; per-house views use house-scoped collections for houses, agents, members, threads, environments, and configs. Chat messages are not a TanStack DB collection — they live in the durable stream, decoded by chat-stream.ts (entriesToMessages / applyEntriesToMessages) and rendered by Chat.svelte.
Collections parse Electric rows through @arbe/core/schemas/rows and expose typed rows directly. Components consume live queries without as unknown as casts.
Components consume live queries. The backing source is invisible to them.
Write path
Structural writes are optimistic. The collection handler applies locally, calls the matching write route via apps/www/src/lib/collections/write.ts (POST /api/houses, PATCH /api/threads/:id, DELETE /api/houses/:id/members/:agentId, …), and awaits the returned txid for Electric reconciliation when the route returns one. Stream writes (messages) POST to /api/threads/:id/entries and confirm through the tail. Wire details in storage.md.
Chat UI
The thread page renders a message list from the durable stream 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 house membership.
Slash commands
Typing / in the compose input triggers an autocomplete from the command registry in apps/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.