# Local development

`apps/www` is the only worker. For normal app development, run Vite and Caddy in two terminals, then browse through Caddy:

```sh
bun run --filter '@arbe/www' dev          # terminal 1: vite, HMR, http://localhost:8888
bun run --filter '@arbe/www' dev:proxy    # terminal 2: Caddy h2 → https://localhost:8443
```

Open `https://localhost:8443` for any house/thread page. Caddy is only a reverse proxy; it does not replace the Vite dev server, and it does not provide live reload/HMR itself. If the proxied page does not reload after edits, refresh manually while keeping the Vite dev server running. Use Wrangler instead when you need full Cloudflare bindings:

```sh
bunx wrangler dev -c apps/www/wrangler.jsonc   # full bindings, no HMR
```

The Caddy proxy isn't optional once you touch house/thread pages. Each Electric shape holds one long-poll open against the dev origin, and a typical house view mounts ~6 shapes (members, house, agents, threads, environments, configs). Browsers cap HTTP/1.1 at 6 connections per host, so all sockets pin. SvelteKit's `__data.json` for client-side nav, Vite's HMR ping, and any `fetch` then queue until a long-poll releases one. Production is fine: Cloudflare serves over h2/h3 and multiplexes everything onto one socket. The Electric team [calls this out as the #1 dev gotcha](https://electric-sql.com/docs/guides/troubleshooting#slow-shapes--slow-hmr--slow-dev-server--why-is-my-local-development-slow).

One-time setup: `brew install caddy` (or [caddyserver.com/docs/install](https://caddyserver.com/docs/install)); then run `caddy trust` if your browser does not already trust Caddy's local CA. Verify in DevTools — the Protocol column should show `h2` for `/api/shapes/*`. If you see `http/1.1`, you're on `:8888`, not `:8443`. Shape URLs are origin-relative, so they route through Caddy automatically with no code changes.

With Vite dev, Cloudflare-specific bindings (rate limiter) aren't available — most routes don't need them; the ones that do require `wrangler dev`. Bot replies run in-process via `packages/core/dispatch/` when `POST /api/threads/:id/entries` writes. Secrets live in `apps/www/.env.local` (`SUPABASE_JWT_SECRET`, `DURABLE_STREAMS_SECRET`, `ELECTRIC_*`, `OPENROUTER_API_KEY`; `ANTHROPIC_API_KEY` is optional for explicit direct Anthropic refs); missing or empty values fail fast with a loud error naming the keys.

```sh
bun run check    # format + lint + typecheck across packages (subsumes lint)
bun run test     # all packages
# scope:  bun run --filter '@arbe/cli' check
```

`check` subsumes `lint` — never run both. `bun run lint` still exists for read-only lint without formatting or typechecking. Biome handles lint + format for most packages (`biome.jsonc` at repo root); `apps/www` uses eslint + prettier because of the svelte plugin.

Code: `apps/www/Caddyfile.dev`, `apps/www/.env.example`.<br>
See [system/environment-variables](./environment-variables.md), [system/deployment](./deployment.md), [debugging](./debugging.md).
