--- name: run-ghost-kitchen description: Build, run, and drive Ghost Kitchen (TanStack Start ordering app). Use when asked to start the app, run its tests, seed its database, take a screenshot of its UI, or interact with the running app (menu, basket, sign-in/sign-up). --- Ghost Kitchen is a TanStack Start web app (Vite dev server on port 3000, Postgres via Docker, Better Auth). There is no `chromium-cli` binary in this environment — drive it with the `mcp__claude-in-chrome__*` tools (load them via `ToolSearch` if deferred; see the `claude-in-chrome` skill). All paths below are relative to the repo root. ## Prerequisites Docker (for Postgres) and Node — both already present in this environment. No OS packages were needed. ## Setup One-time, per fresh checkout / fresh Postgres volume: ```bash docker compose up -d # starts Postgres on localhost:5433 cp .env.example .env.local ``` `BETTER_AUTH_SECRET` in `.env.example` ships empty — Better Auth needs a real value or auth routes fail. Generate one and fill it in: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" # paste the output as BETTER_AUTH_SECRET= in .env.local ``` Then apply migrations and seed data: ```bash npm run db:migrate # this project's Drizzle migrations npm run auth:migrate # Better Auth's own tables (users, sessions, ...) npm run db:seed # sample Brand, menu, Machines — SKIP if DB already seeded, see Gotchas ``` Against a genuinely empty database, `auth:migrate` prompts interactively ("Are you sure you want to run these migrations?"). Non-interactively, answer it with `yes | npm run auth:migrate`. ## Build No separate build step for local dev (Vite serves TS directly). For a production bundle: `npm run build`. ## Run (agent path) Start the dev server in the background and wait for it to actually serve (don't `sleep` — poll the port): ```bash npm run dev & timeout 30 bash -c 'until curl -sf http://localhost:3000 >/dev/null; do sleep 1; done' ``` Stop it with `lsof -ti:3000 -sTCP:LISTEN | xargs -r kill` before relaunching, or the next run hits `EADDRINUSE`. Check for an already-running instance first (`lsof -i :3000 -sTCP:LISTEN`) — in this project's dev container a server is often already up from a prior session; reuse it instead of starting a duplicate. Drive it with the `mcp__claude-in-chrome__*` tools: ``` tabs_context_mcp { createIfEmpty: true } navigate { url: "http://localhost:3000", tabId: } computer { action: "screenshot", tabId: } ``` One representative flow that proves the app works end-to-end (menu -> basket -> sign-up): ``` # Menu page: bump a quantity stepper find { query: "increment button for Pizza", tabId: } computer { action: "left_click", coordinate: [,], tabId: } # Basket icon (top-right) -> /basket shows line items + total computer { action: "left_click", coordinate: [1338, 29], tabId: } # icon position at 1400px width # Sign up (/signup): Name, Email, Password fields -> "Create account" navigate { url: "http://localhost:3000/signup", tabId: } # fill Name/Email/Password via computer left_click + type, then click "Create account" ``` Close tabs you created when done (`tabs_close_mcp`). ## Run (human path) `npm run dev` -> serves on http://localhost:3000, Ctrl-C to stop. Useless headless without a browser to point at it. ## Test ```bash npm test # vitest run — 27 tests, 2 files, ~3s npm run typecheck npm run lint ``` --- ## Gotchas - **`.env.local` doesn't exist by default** and isn't created by any script — `cp .env.example .env.local` is a required manual step, and `BETTER_AUTH_SECRET` inside it ships blank. Leaving it blank breaks sign-in/sign-up silently at the Better Auth layer. - **`npm run db:seed` is not idempotent.** Re-running it against an already-seeded DB throws `DrizzleQueryError: duplicate key value violates unique constraint "machine_tags_name_idx"` and exits 1. If Postgres already has data (e.g. a long-lived dev container), skip seeding rather than treating the error as a setup failure. - **Port 3000 is often already occupied** by a dev server started outside the current session (e.g. the user's own terminal). Check `lsof -i :3000 -sTCP:LISTEN` before launching — if something already answers `curl -sf http://localhost:3000`, drive that instead of fighting over the port. - **Session/basket state persists across browser tab reloads** (basket contents and signed-in user both survived a fresh tab + navigate in testing) — a new tab hitting `localhost:3000` may already show a logged-in user or a non-empty basket from a prior run in the same container. Not a bug; just don't assume a fresh tab means fresh state. - **`[TanStack Devtools] Failed to load: TypeError: Failed to fetch dynamically imported module ...`** shows up in the console on first load. It's a stale Vite dep-cache issue with the devtools panel, not an app error — harmless, ignore it (verified via `read_console_messages`, the rest of the app functions normally). - **Adding a `NOT NULL` column via `db:generate` against a populated local DB** makes `drizzle-kit generate` prompt interactively for a default value — awkward to script. Reset the Postgres volume first (`docker compose down -v && docker compose up -d`, then recreate the DB, migrate, `yes | npm run auth:migrate`, reseed) so the column is added to an empty table and no prompt appears. ## Troubleshooting - **`Error: Port 3000 is already in use` on `npm run dev`**: another process already holds it — see the port gotcha above. Either reuse it (`curl` returns 200) or `lsof -ti:3000 -sTCP:LISTEN | xargs -r kill` first if you own that process. - **`duplicate key value violates unique constraint` from `db:seed`**: DB already seeded — skip the seed step, don't debug it.