Commit Graph
23 Commits
Author SHA1 Message Date
lukasandClaude Sonnet 5 86d1a32dd8 Use neutral dark gray instead of brown-tinted dark mode palette
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:39:05 +02:00
lukasandClaude Sonnet 5 370e77f1a6 Add dark mode based on device color-scheme preference
Move the color palette into CSS custom properties (:root in
globals.scss) with a prefers-color-scheme: dark override, and point the
existing $color-* Sass variables at them so every component picks up
dark mode automatically. Replaced the handful of rgba($color, alpha)
call sites with color-mix(), since Sass can't alpha-blend a color that's
now a CSS variable at compile time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:33:31 +02:00
lukasandClaude Sonnet 5 a716080f7d Give the top nav bar a translucent glass effect
Adds a backdrop blur and semi-transparent background/border so page
content shows through the sticky header while scrolling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:27:58 +02:00
lukasandClaude Sonnet 5 439932c168 Fix admin panel mobile layout and inconsistent page padding
The admin layout's .layout used align-items: flex-start, correct for
the desktop row (sidebar + content) but wrong once it stacks to a
column at mobile width — align-items controls the cross axis, which
flips from height to width, so children were shrinking to fit their
own content instead of stretching to the viewport. This is what caused
the sidebar's nav links to render squashed/tall and the menu list to
overflow horizontally. Same root cause on .sidebar's own flex-basis,
which fixed its height instead of width once stacked.

Also stacks the menu item form's option/step rows to a column below
the tablet breakpoint (they didn't fit and forced horizontal scroll),
and removes AdminOrdersView's leftover standalone-page padding/max-width
— it was still wrapping its content in the styling meant for a
top-level page, giving "All orders" visibly more padding than Menu/
Kitchen, which just use the admin layout's own padding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 23:34:42 +02:00
lukasandClaude Sonnet 5 c8d1721358 Add an admin panel: orders dashboard, menu management, kitchen status
Introduces a role-gated /admin section (sidebar layout, requireAdmin
checked both in route loaders and independently in every admin server
function) with three sections:
- Orders: every order across all customers, and an admin-only order
  detail view nested under /admin so the sidebar persists and admin
  access bypasses the guest-token check.
- Menu: create/edit/delete menu items, their options, and their
  preparation steps; deleting an item with past orders is refused
  (protected by the orderItems FK) instead of crashing.
- Kitchen: read-only live view of every machine's busy/idle status and
  per-tag queue depth, with a manual refresh.

Also:
- Adds a `role` field to the user table (Better Auth additionalFields,
  input:false so it can't be self-assigned) and a db:seed-admin script
  to create/promote an admin account.
- Fixes a real bug found while building this: src/server/auth.ts's
  shared session helpers were plain exported functions using a
  server-only import, which the framework couldn't tree-shake out of
  the client bundle once shared across multiple server/*.ts modules —
  this silently broke hydration app-wide. Fixed with createServerOnlyFn.
- Removes the now-fully-unused @tanstack/devtools-vite plugin.
- Disables view transitions for in-admin navigation so the persistent
  sidebar doesn't cross-fade/shift on every click.
- Gives the "your orders" empty state the same visual treatment as the
  basket page's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 23:14:28 +02:00
lukasandClaude Sonnet 5 18a7d2030e Fix crashes on unknown menu item / order routes
Both loaders threw a plain Error that the router doesn't treat as a
route-level not-found, so visiting /items/<bad-id> or /orders/<bad-id>
(or a valid order with a missing/wrong ?token) crashed instead of
showing the NotFound page. Menu item lookups now throw notFound()
directly; the order loader catches the server function's error and
maps both "order not found" and "wrong access token" to notFound(),
so a guessed id can't be used to probe which orders exist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 21:21:29 +02:00
lukasandClaude Sonnet 5 927f6ff56e Add a NotFound page and remove the TanStack devtools panel
Configures a notFoundComponent on the root route so unmatched paths get a
branded empty state instead of TanStack Router's generic "Not Found" text,
and drops the always-on devtools panel from the root layout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 21:09:43 +02:00
lukasandClaude Sonnet 5 d66e09f515 Add menu item customization/detail pages and configurable currency formatting
Lets customers pick options on a menu item detail page before adding to the
basket, and makes displayed prices respect a configurable currency/locale
via VITE_CURRENCY/VITE_LOCALE. Also crossfades the basket's empty/filled
states with AnimatePresence in wait mode instead of animating both at once.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 21:00:45 +02:00
lukasandClaude Sonnet 5 e98b0d86d2 Animate quantity steppers and make the nav bar sticky
- Add an AnimatedNumber component that slides a quantity stepper's
  count horizontally — left on increment, right on decrement — and
  wire it into both the menu and basket steppers.
- Make the top nav bar position: sticky, with an explicit background
  so scrolled content doesn't show through it.
- Reserve the scrollbar's gutter on every page (scrollable or not) so
  the sticky header doesn't shift width when navigating between short
  and tall pages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 12:30:56 +02:00
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
lukasandClaude Sonnet 5 5059f40d4c Add an auth:migrate npm script for Better Auth's own migrations
Installs @better-auth/cli as a devDependency instead of fetching it via
npx each time, and updates the README to use the new script.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:22:17 +02:00
lukasandClaude Sonnet 5 cacfce8eae Update README styling section to match the SCSS Modules convention
Was still describing Tailwind, which was removed from the project.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:14:27 +02:00
lukasandClaude Sonnet 5 0d8f8f8148 Bring the codebase in line with CLAUDE.md conventions
- Convert getRouter, OrderingPage, and OrderStatusRoute from function
  declarations to arrow functions
- Migrate header-user.tsx off raw Tailwind classes onto a co-located
  SCSS module, and remove Tailwind entirely (deps, Vite plugin,
  styles.css) since it was the only consumer and duplicated the
  documented SCSS Modules system
- Fix CLAUDE.md's stale ~/ alias example and structure diagram to
  match the project's actual #/ and @/ aliases and directories

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:06:22 +02:00
lukasandClaude Sonnet 5 ff8fc388c0 Add docker-compose for local Postgres and fix the setup docs
Adds docker-compose.yml (Postgres on host port 5433, to avoid clashing
with another project's container already using 5432 on this machine)
and .env.example so `docker compose up -d` + `cp .env.example .env.local`
gets a working local DB. Adds a db:seed script alongside the existing
db:* scripts.

Rewrites README's "Adding a Database" section, which still described
Better Auth's DB as optional/hypothetical even though this project
already requires Postgres for both its own data and Better Auth's
tables. Fills in CLAUDE.md's Data layer connection-details placeholder
and fixes two more stale pnpm references and a wrong migrations path
(src/db/migrations/ instead of the actual drizzle/) found nearby.

Also fixes a real bug the new docs surfaced: `npm run db:seed` didn't
load .env.local the way drizzle-kit does, so DATABASE_URL was silently
unset when run as documented. seed.ts now loads env the same way
drizzle.config.ts already does.

Verified the full documented flow end-to-end against a fresh container:
migrate, better-auth migrate, seed, dev server, real page render.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 21:05:07 +02:00
lukasandClaude Sonnet 5 ea459e72b3 Add the customer ordering page
Builds the main customer-facing flow: browse the Brand's menu, add
items to a cart, and check out either as a guest (name + contact) or,
if signed in, as the account customer — both call the existing
placeOrder server function, which already distinguishes the two. On
success, navigates to a new /orders/$orderId status page showing each
Order Item's Preparation Steps, queue position, ETA, and (per the
domain rules already established) a Cancel button while cancellable
and a pickup-confirmation button once ready.

Styled with SCSS Modules per CLAUDE.md; added the sass dependency and
the src/styles/ structure it describes (globals.scss imported once in
__root.tsx, alongside the existing Tailwind stylesheet the not-yet-
mounted header-user.tsx component depends on).

Verified end-to-end against a throwaway local Postgres: menu renders
with seeded items, cart quantities update, guest checkout places a
real order, and the status page reflects a real cancellation.

Also fixes CLAUDE.md's Commands section, which documented pnpm even
though this repo only has npm (package-lock.json, no pnpm installed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:31:32 +02:00
lukasandClaude Sonnet 5 b7fdbd4779 Don't let a compensating payment call swallow the original error
Both placeOrder and cancelOrderItem call a compensating payment action
(refund / re-charge) after catching a failure, then rethrow. If that
compensating call itself threw, its error replaced the original one,
hiding the actual root cause behind whatever the payment provider
happened to fail with on the second attempt. Wrap each compensation in
its own try/catch, log the compensation failure, and always rethrow
the original error.

Also fixes a stale comment left over from renaming
createFirstStepForOrderItem to initializeOrderItemRecipe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:07:14 +02:00
lukasandClaude Sonnet 5 506319ce4f Refund before cancelling, not after, with compensation on failure
cancelOrderItem committed the cancellation (freeing the Machine slot,
marking the item cancelled) before calling the payment provider's
refund — so a refund failure (gateway timeout, provider outage) left
the item cancelled with no refund ever issued, and no record that one
was owed. placeOrder already avoids the equivalent problem by charging
before its DB transaction and refunding if the transaction then fails;
apply the same pattern here for consistency.

Refund now happens first; the DB transaction's atomic queued-status
check (added for the earlier cancellation race) doubles as the signal
that the refund needs to be undone if this transaction loses the race
or otherwise fails, so the catch block re-charges to compensate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 20:01:55 +02:00
lukasandClaude Sonnet 5 9c3ad51686 Freeze an Order Item's Recipe at order time
Price was already snapshotted per Order Item so later menu changes
don't rewrite history, but the Recipe (Step list) wasn't: advancing an
Order Item looked up its next Step from the live menuItemSteps table,
so an operator editing a Menu Item's Recipe mid-cook could silently
reshape an Order already in flight (different Machine Tag, duration,
or Step order than what the customer paid for).

Adds order_item_steps as a per-Order-Item copy of the Recipe, taken at
placement time; placeOrder, advanceOrderItemStep, and getOrderStatus
now all read from that snapshot instead of the live menu. Recorded as
docs/adr/0005 and added Recipe as a formal CONTEXT.md term.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 19:55:05 +02:00
lukasandClaude Sonnet 5 d291312139 Sharpen ordering domain model and close a cancellation race
CONTEXT.md described Order Item cancellability and Order completion
in ways the code didn't actually implement (cancellability only
covered the first Preparation Step; completion required pickup, not
any terminal state). Corrected the glossary to match intended
behavior, added Guest Access Token and Charge as formal terms, and
recorded the single-Charge-per-Order design as an ADR.

Also closes a real race in cancelOrderItem: a Machine could claim a
queued Step between the cancellability check and the delete, letting
an already-cooking item get cancelled and refunded. The delete now
atomically re-checks status = 'queued'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 19:48:04 +02:00
lukasandClaude Sonnet 5 456c3acda5 Implement ordering MVP domain layer per spec (issue #1)
Scaffolds the TanStack Start + Drizzle/Postgres + Better Auth app and
implements the core ordering domain: menu browsing, guest/account order
placement with online payment, per-Order-Item independent cooking and
cancellation, dynamic Machine-Tag pool assignment, and multi-step recipes
(see docs/adr/0001-0003). Adds a guest-access-token / session-based
ownership check on every read/mutation so an order can't be viewed,
cancelled, or picked up by anyone but its owner.

Covered by 14 tests running the domain layer directly against a real,
ephemeral Postgres (testcontainers) — createServerFn couldn't be called
outside a real request context, so tests exercise the domain functions the
server functions thinly wrap instead of the server functions themselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 18:46:58 +02:00
lukas e532a95f0b Merge pull request '🔧 update docs' (#2) from feat/update-docs into master
Reviewed-on: #2
2026-09-09 18:12:51 +02:00
lukas d2806818d2 🔧 update docs 2026-09-09 18:11:50 +02:00
lukas 9dee2a4013 🎉 initial commit 2026-09-09 17:49:49 +02:00