- Wire up Better Auth (email/password + Google/Apple SSO) with a sign-in/sign-up UI, header account menu, and a basket that follows the signed-in-or-guest state. - Give the order status page a vertical timeline per preparation step (with a distinct customer-facing name separate from the machine tag, a done/in-progress/queued/pending look, and a sonar-ping highlight on the active step), a formatted ETA (relative under 3h, absolute clock time beyond that), and Buy again / Get help / Cancel actions. - Expand Pizza's recipe into 6 steps across a shared prep station and the existing oven/assembly stations, with matching schema, seed, and domain changes plus updated/added test coverage. - Add an Orders list (account orders via session, guest orders via a locally remembered access-token list) with a shared getOrdersForCustomer domain summary, and remember a guest's name/contact after their first order so checkout doesn't ask again. - Improve page transitions with the View Transitions API: a shared element morph between an order card and its detail page, a stable (non-jumping) header across navigations, and reduced-motion handling. - Add a Ghost Kitchen wordmark (Space Grotesk, faded "Ghost", ghost icon standing in for the "o") and shrink the basket icon to match the rest of the header controls. - Add a run-ghost-kitchen agent skill documenting local setup, the dev-server driver, and the gotchas hit along the way. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
149 lines
5.8 KiB
Markdown
149 lines
5.8 KiB
Markdown
---
|
|
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: <tab> }
|
|
computer { action: "screenshot", tabId: <tab> }
|
|
```
|
|
|
|
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: <tab> }
|
|
computer { action: "left_click", coordinate: [<x>,<y>], tabId: <tab> }
|
|
|
|
# Basket icon (top-right) -> /basket shows line items + total
|
|
computer { action: "left_click", coordinate: [1338, 29], tabId: <tab> } # icon position at 1400px width
|
|
|
|
# Sign up (/signup): Name, Email, Password fields -> "Create account"
|
|
navigate { url: "http://localhost:3000/signup", tabId: <tab> }
|
|
# 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.
|