# controller/.test — UI regression gate

Two layers, run them in this order.

## 1. `gate-parse.mjs` — static, zero dependencies, run ALWAYS

```bash
node controller/.test/gate-parse.mjs
```

Extracts every inline `<script>` block from `controller/cmd/controller/static/{new,index,login}.html`
and parses each one (`acorn` if installed locally, else `node --check` on
extracted temp files). Exit 0 = all blocks parse; non-zero prints the file,
block index, and the parser error.

This is the codified **2026-05-08 blank-page deploy gate**: the panel HTML is
embedded via `//go:embed`, so `go build` succeeds even when a JS syntax error
would render the entire panel blank. Run this before every controller build
that touched `static/*.html`. Needs nothing but Node — no npm install, no
controller, no browser.

## 2. `ui.spec.mjs` — Playwright browser suite, needs a RUNNING controller

```bash
cd controller/.test
npm install && npx playwright install chromium   # one-time
BASE_URL=http://127.0.0.1:8080 \
TEST_EMAIL=you@example.com TEST_PASSWORD=... \
npx playwright test ui.spec.mjs
```

Prereqs (see `memory/build.md`): Postgres dev container up
(`docker compose -f docker-compose.dev.yml up -d`), controller built and
running on `BASE_URL` (default `http://127.0.0.1:8080`).

| Check | Needs |
|---|---|
| `/login` renders form, 0 console errors | controller only |
| `/` without session redirects to `/login` | controller only |
| `/` serves new.html by default, 0 hard console errors | controller + `TEST_EMAIL`/`TEST_PASSWORD` |
| `panel_ui=classic` cookie serves index.html, **no redirect loop** (the FileServer 301 gotcha, see `memory/gotcha-fileserver-index-html-redirect-loop.md`) | controller + creds |
| Command palette opens (Ctrl+K and `#topbar-cmdk`) | controller + creds |
| Hero-stat IDs (`#stat-running/players/agents/cpu` + hints, `#status`, `#status-dot`) attached | controller + creds |

Without `TEST_EMAIL`/`TEST_PASSWORD` the authenticated tests **self-skip**
(Playwright annotation, not failure). Console errors that are clearly
missing-live-agent-shaped (websocket/agent connection noise) are annotated
`needs-live-agent` rather than failed — a live agent is never required.

## Status honesty

`gate-parse.mjs` has been run for real on this repo. `ui.spec.mjs` was
authored against the real DOM IDs in `static/new.html` / `static/login.html`
and the root-route logic in `httpapi.go`, but a full run requires a local
Postgres + controller — if your box doesn't have the dev Postgres container,
the browser suite is unverified there.
