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>
This commit is contained in:
2026-09-10 11:58:24 +02:00
co-authored by Claude Sonnet 5
parent 5059f40d4c
commit 79bd64ce8c
54 changed files with 3514 additions and 301 deletions
+148
View File
@@ -0,0 +1,148 @@
---
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.