# Web app

How `apps/www` is built — the browser guide for people is [chatting in arbe](../../chat.md). A SvelteKit 5 app: pages call `@arbe/core` client methods, render TanStack DB collections, and use Supabase OAuth. No inline fetch, no hand-rolled payloads.

## Cloudflare types

`src/app.d.ts` owns the platform bindings and imports context types from
`@cloudflare/workers-types`. Fetch bindings use DOM types so Cloudflare globals do
not leak into browser code. Update that declaration when the app starts using
another binding; there is no type-generation step.

Checks ignore leftover `worker-configuration.d.ts` files at both historical paths.
Do not regenerate or commit them.

## Client

`createClient({ baseUrl, headers? })` in `packages/core/client.ts` is the whole API surface: entity mutations, stream operations, queries. The app creates one instance; browser fetch carries cookies. Bots send an `Authorization` header with their API key, and `authorId` is stamped server-side on every write (contract: [who may write what](../chat/authorship.md)).

Mutations hit `/api/{houses,agents,environments,threads}` and `/api/houses/:id/members`. Write routes resolve the caller with `requireAgent(...)` (`apps/www/src/lib/server/route-helpers.ts`) plus route-local membership guards, and re-validate inputs against `@arbe/core/schemas`.

## Routing

`/houses/[house_id]` is the house dashboard, `/houses/[house_id]/threads` the thread list, `/threads/[id]` a chat thread. Route params are the source of truth. The root redirects to the agent's first house.

Management: `/houses/[house_id]/agents` (registry, membership, `/new` for bots), `/houses/[house_id]/edit`, `/invite/[token]`.

Account: `/account`, `/account/houses`, `/account/tokens`, `/account/connect`, `/account/telemetry`, with chrome in `account/+layout.svelte`. Account and `/agents/[agent_id]` share the profile components; pages mount only the sections they show.

## Layout

`apps/www/src/routes/+layout.svelte` renders header, main, and right aside. The header is the only bar: `ARBE › House ▾ › Section`, the house name doubling as palette trigger, everything else behind ⋯. Cross-route chrome state lives in `apps/www/src/lib/layout.svelte.ts`; auth session stays server-driven, collection data is TanStack DB's.

The header's leading control is the house icon on `/houses` and the house home, and Back everywhere deeper. Back uses browser history only when the previous entry was recorded as an app route in this JS context (`apps/www/src/lib/nav-history.ts`); otherwise it goes to the house home, or `/houses`. A reload forgets those records on purpose.

## Collections

TanStack DB collections backed by TanStack Query plus the durable stream ([storage.md](../data/storage.md)).

Every structural collection is a query collection over a www API route: `membersCollection` and `threadsCollection` for discovery, house-scoped ones for members, agents, threads, environments, configs, workflows. Polling cadence differs per collection ([sync.md](../data/sync.md)). Chat messages are not a collection — they live in the stream, decoded by `chat-stream.ts` and rendered by `Chat.svelte`.

Collections parse rows through `@arbe/core/schemas/rows`; components consume typed live queries and never see the backing source.

## Write path

Structural writes are optimistic: apply locally, call the write route via `apps/www/src/lib/collections/write.ts`, refetch so server truth replaces the optimistic row. Messages POST to `/api/threads/:id/entries` and confirm through the tail. Wire details in [storage.md](../data/storage.md).

## Chat UI

The thread page renders the stream as a message list plus a compose input. Markdown is sanitized. Auto-scroll pins to the bottom unless scrolled up. Enter sends, shift+enter newlines, and compose is disabled without house membership.

## Slash commands

`/` in the compose input autocompletes from `apps/www/src/lib/commands/`. A command is **post** (insert a message), **silent** (side effect), or **replace** (append to input). Built-ins: `/invite`, `/shrug`.

`/invite @handle` adds a house agent to the thread, resolving the handle via `nameToHandle` against the same agents the `@` picker offers, and asks you to choose when handles collide. House membership itself is managed from the house's Agents page.

See [chatting in arbe](../../chat.md) for the user's view, [storage](../data/storage.md) and [sync](../data/sync.md) for what backs the collections, and [streams](../chat/streams.md) for the chat path that deliberately is not one.
