Files
Ghost_Kitchen/.claude/skills/run-ghost-kitchen/SKILL.md
T
lukasandClaude Sonnet 5 79bd64ce8c Add guest/account auth flows, order tracking, and order history UI
- 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>
2026-09-10 11:58:24 +02:00

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.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.