# Dev-mode roadmap — hot reload for the multi-stack local workflow

**Date:** 2026-07-07
**Companion deck:** [`efficient-dev.html`](./efficient-dev.html) (slides 16–18 summarize this doc)
**Code:** `local/multi-stack.sh`, `local/docker-compose{,.multi,.dev}.yml` in the mono repo

## Where we are

`multi-stack.sh` gives every branch / shim / experiment its own isolated, loginable
platform (compose project per branch, ephemeral router port, auto-seeded admin).
What it does **not** give you is a fast inner loop: every stack runs **prebuilt
images**, so seeing a code change means rebuild image → point `.env` at it →
force-recreate. Minutes per iteration.

The Tilt flow (`Tiltfile` + `local/docker-compose.tilt.yml`) runs the real web
from source, but:

- it serves a **production standalone build** — after each change you manually
  run `pnpm build` (~30s+) and `tilt trigger web`; no HMR;
- it is **single-stack** (fixed ports: web 3007, auth 9022, app-host 9131);
- it exists because `next dev` (webpack era) compiled heavy authenticated routes
  (`/auth/callback`) on first request and stalled for minutes.

**Dev-mode** = a mode of a multi-stack stack where the service you're editing
live-reloads as you type, while every other stack stays a frozen, stable baseline.

## Design decision: host-run dev web, wired per-stack

Two candidate architectures:

| | A. Host-run `next dev` (chosen) | B. `next dev` inside a container |
|---|---|---|
| HMR speed | Native FS events, fastest | Bind-mount watching on macOS — slower, sometimes flaky |
| Setup cost | Zero — reuses the repo's existing `node_modules` | Per-stack `pnpm install` into named volumes (minutes, cross-arch) |
| Containment | Web process lives outside compose; script must manage it | `down` cleans everything; router keeps working unchanged |
| Proven? | Yes — the Tilt flow already runs the web this way | No |

**Chosen: A.** It is the Tilt architecture generalized per-stack: publish the
stack's backends on **ephemeral** host ports (same trick as the router), discover
them, and run `next dev --turbo` on the host with its service URLs pointed at
that stack. B (full containment) stays on the menu as a later phase if we want
`down` to own the web process too.

### The redirect alignment problem (same as `up`, one more variable)

Auth bakes `FRONTEND_URL` / `PUBLIC_BASE_URL` / `OAUTH_ALLOWED_REDIRECT_ORIGINS`
into login redirects, and app-host proxies web paths via `PLATFORM_WEB_URL`.
In dev-mode those must point at the **host-run dev web**, and the web must point
at the stack's **discovered** auth/app-host ports. Order of operations:

1. Pick a free host port for the dev web *first* (we control this one).
2. `compose up` with the dev overlay: auth's redirect env → the dev web URL;
   auth/app-host/postgres/gcs published on ephemeral ports.
3. Discover the assigned ports (`docker port …`).
4. Re-up with `APP_HOST_BASE_DOMAIN=localhost:<apphost-port>` so auth's
   app-redirect policy and app-host's origin logic agree (only the services
   reading it are recreated — the ports stick).
5. Run `next dev` with `AUTH_SERVICE_URL`/`APP_HOST_SERVICE_URL`/secrets wired,
   warm the heavy routes, and hand the developer the URL.

### Known risk: `next dev` stalls

The reason Tilt serves a production build. The bet: Next 16 + turbopack
(`next dev --turbo`, already the repo's `dev` script) plus a **route warm-up**
(hit `/login` and `/auth/callback` right after boot) makes the dev server usable.
**Fallback if it still stalls:** watch → `next build --webpack` → auto-restart the
standalone server (slower loop, ~30s, but fully reliable and still automatic).
Phase 1 explicitly validates this before we build more on top.

## Phases

### Phase 0 — Plumbing (started 2026-07-07)

- [x] `local/docker-compose.dev.yml`: ephemeral publishes for auth/app-host/
      postgres/gcs; auth redirect env + app-host `PLATFORM_WEB_URL` pointed at
      the host dev web.
- [x] `multi-stack.sh dev [shim]`: free-port pick → up with overlay → port
      discovery → env re-alignment → seed → run the dev web with wired env.
- [x] `local/README.md` dev-mode section.

**Exit:** one command gives a working from-source web against any stack.

### Phase 1 — Web hot reload

- [x] `next dev --turbo` as the dev server, with warm-up of `/login` +
      `/auth/callback` after boot.
- [x] Validated end-to-end (2026-07-07, this branch): password login →
      select-org → dashboard against a dev-mode stack; edit a component →
      Fast Refresh applied in **~2.4s**. Measured: Turbopack ready in 0.8s,
      `/login` first compile 6.6s (absorbed by the warm-up), warm loads ~60ms.
      **The historical `next dev` stall is gone with Next 16 + Turbopack** —
      the watch→build→restart fallback was not needed.
- [ ] Dogfood on a real feature branch for a week; only build the
      `MULTI_STACK_DEV_SERVER=build-watch` fallback if stalls reappear.

**Exit:** save a file, see it in the browser in seconds, without restarting anything. **← met**

### Phase 2 — Backend fast reload

- [ ] `multi-stack.sh reload <svc> [shim]`: `nx run <svc>:docker-build` (NX cache
      makes unchanged rebuilds cheap) → `compose up -d --no-deps --force-recreate <svc>`
      scoped to that stack. Warm reload in ~10–30s.
- [ ] True watch-mode for Go services (auth, app-host): dev overlay variant
      running a `golang:1.25` container with the repo mounted and an air-style
      watcher (rebuild + restart in-container on save). Opt-in per service:
      `dev a --watch auth`.

**Exit:** a Go change is live in its stack in < 30s without touching other services.

### Phase 3 — Whole-stack ergonomics

- [ ] Ephemeral-port treatment for `docker-compose.services.yml` (orchestrator,
      assistant-go, kafka, …) so *full* stacks — not just the base 4 services —
      can run multi + dev.
- [ ] Persist a stack's dev-mode (compose label or marker file) so plain `up`
      re-runs keep the mode without re-passing flags.
- [ ] Optional containment mode (architecture B) if we want `down` to own the
      web process.
- [ ] Docs polish + team demo; fold the sharp edges into `local/README.md`
      troubleshooting.

**Exit:** dev-mode is the default way engineers run the platform locally.

## Non-goals

- Replacing the k3d/helm local-deploy flow — that remains the integration-fidelity
  path (charts, migrations fan-out, kafka topics). Dev-mode optimizes the
  *inner loop*, not deployment realism.
- Hot reload for every service on day one — web first (highest iteration
  frequency), then auth/app-host, then the services overlay.
