- 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>
5.8 KiB
name, description
| name | description |
|---|---|
| run-ghost-kitchen | 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:
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:
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:
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):
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
npm test # vitest run — 27 tests, 2 files, ~3s
npm run typecheck
npm run lint
Gotchas
.env.localdoesn't exist by default and isn't created by any script —cp .env.example .env.localis a required manual step, andBETTER_AUTH_SECRETinside it ships blank. Leaving it blank breaks sign-in/sign-up silently at the Better Auth layer.npm run db:seedis not idempotent. Re-running it against an already-seeded DB throwsDrizzleQueryError: 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:LISTENbefore launching — if something already answerscurl -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:3000may 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 viaread_console_messages, the rest of the app functions normally).- Adding a
NOT NULLcolumn viadb:generateagainst a populated local DB makesdrizzle-kit generateprompt 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 useonnpm run dev: another process already holds it — see the port gotcha above. Either reuse it (curlreturns 200) orlsof -ti:3000 -sTCP:LISTEN | xargs -r killfirst if you own that process.duplicate key value violates unique constraintfromdb:seed: DB already seeded — skip the seed step, don't debug it.