# Local development

`bun run dev` previews checkout www/API code and docs against production data and services. All durable background work runs on production backstage on Fly: director passes, bot turns, workflows, file indexing, and cleanup.

## Web app

```sh
bun run dev          # https://arbe.localhost + Vite hot reload + docs preview
```

Open `https://arbe.localhost`. The root command runs Vite through [Portless](https://github.com/vercel-labs/portless), which owns that stable HTTP/2 + HTTPS origin and forwards Vite's WebSocket hot reload. Portless is a repo dependency and starts its proxy automatically; its first run may ask to trust the local certificate authority.

```sh
portless doctor   # proxy, route, DNS, and certificate diagnostics
bun run dev:trust # retry Portless certificate trust
```

Do not copy Vite's `:8888` URL into a browser: it is the deliberate direct-HTTP port used by the CLI and proofs, while `https://arbe.localhost` is the browser contract. Collection polling can exhaust the browser's HTTP/1.1 connection pool on `:8888`; Portless avoids that with HTTP/2. Add `https://arbe.localhost` to Supabase Auth → URL Configuration → Redirect URLs if you log in through it.

Local API handlers execute the checkout code and enqueue into the canonical production queues: `dispatch` for director work and `default` for workflows and file work. Background callbacks use the deployed production API. The dev command starts no backstage and needs no backstage database credentials. Local HTTP logs land in `apps/www/tmp/dev.log`; worker logs are on Fly.

A background-code change must deploy from `main` before live integration proof can exercise it. Local parser and decision tests can run before deployment. Use a fresh test house for chat, uploads, and workflows, and capture `arbe --local thread trace <thread> --house <house>` plus file readings/search or workflow results. Test houses separate records; they do not isolate the effects of deploying faulty worker code.

Local API and deployed worker versions can differ. Every worker validates queued payloads against core schemas before executing them; incompatible payloads fail with task kind/id, validation details, the running backstage SHA, and a deploy-first hint. Shape checks cannot catch every semantic change, so coordinate API and worker deployment. Director decisions and outcomes retain the executing backstage SHA; older unstamped records report an unknown revision.

For raw HTTP development, `cd apps/www && bun run dev` runs Vite directly on `http://localhost:8888` without Portless or docs. In DevTools, `/api/threads` and the other collection routes should show protocol `h2`, and Vite's HMR connection should use `wss://arbe.localhost`.

### Retiring a local backstage

Stop a shared local worker only after coordinating with its owner. Inspect existing per-user queues through the [Supabase CLI](../data/supabase.md) and account for unfinished work before retiring the setup. Do not replay, drop, or delete tasks or queue tables without approval for the production database write. New work and queue provisioning reject development queue targets.

## Docs

`/docs` is a second dev server. `bun run dev` starts it for you — Astro on `http://localhost:4321` — unless something is already answering there, in which case it uses that and says so. To run it alone:

```sh
bun run --filter '@arbe/www-docs' dev
```

www proxies `https://arbe.localhost/docs/*` to it (`apps/www/src/lib/server/docs-proxy.ts`). Astro emits root-absolute URLs (`/@fs/…`, `/@id/…`) in both its HTML and its module bodies; the proxy rewrites them under `/docs` so they come back to Astro. Anything it misses is served by *www's* Vite instead — which resolves the file but not Astro's virtual modules, so the symptom is a Vite overlay saying `Failed to resolve import "virtual:astro:adapter-config/client"`. Compare against `http://localhost:4321/docs/` directly: if that page is fine, the bug is in the rewrite, not in Astro.

Use Wrangler only when you need full Cloudflare bindings:

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

Vite dev does not provide Cloudflare-specific bindings such as the rate limiter. Most routes do not need them. Secrets live in the root `.env.local`, validated against `.env.schema` on every Vite start (`SUPABASE_JWT_SECRET`, `DURABLE_STREAMS_SECRET`, `OPENROUTER_API_KEY`; `ANTHROPIC_API_KEY` is optional). `bunx varlock load --agent` shows what resolved, secrets redacted.

## Checks

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

`check` subsumes `lint` — never run both. Biome handles most packages; `apps/www` uses eslint + prettier because of the Svelte plugin.

A package's own `check` script is the only thing that typechecks it — nothing runs `tsc` globally. The house form is `biome format --write . && oxlint --type-aware --type-check`; dropping the two oxlint flags leaves that package's TypeScript unchecked. New package? Copy the line verbatim. Biome config lives in the root `biome.jsonc`; nested `biome.jsonc` files only `extends: "//"` plus per-package overrides, and `bunx biome migrate --write` from the repo root updates all of them at once.

Code: `apps/www/scripts/dev-stack.sh`, `.env.schema`, `apps/www/.env.schema`, `apps/backstage/.env.schema`.<br>
See [system/environment-variables](../sandboxes/environment-variables.md), [system/deployment](deployment.md), [debugging](debugging.md).
