Commit Graph
54 Commits
Author SHA1 Message Date
lukasandClaude Opus 5.5 d987c71010 Drop the account name from the signed-in checkout button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:04:20 +02:00
lukasandClaude Opus 5.5 1dae53f90a Animate the ETA, hide delays, and tidy the order card actions
- Slide the "Ready in about" value when it changes (new EtaMessage)
- Drop the "Running late" banner; delays only move the ETA now
- Remove the cancel-info tooltip and its icon button
- Shorten the steps toggle to "Steps" / "Hide"
- Add icons and more padding to the order card action buttons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:02:35 +02:00
lukas 7cf286850c 🔧 update docs 2026-10-01 10:06:13 +02:00
lukasandClaude Opus 5.5 a6ac32d783 Show live order progress, delays, and kitchen cancellations
- Order cards derive their label from item progress (Not Started,
  Preparing, Partly Ready, Ready, Picked Up, Cancelled) instead of the
  order's own status; final states are coloured
- Order page: animated status banner per item (ready/picked up check,
  cancelled cross, running-late clock), steps collapse once an item is
  settled with a Show/Hide steps toggle, done steps are faded instead
  of struck through, and the per-item status badge is gone
- New Delay concept: machines can report extra time on a claimed step
  (preparation_step_instances.delay_seconds), which counts toward the ETA
- The kitchen can cancel an item at any point before it's ready, with a
  refund (order_items.cancelled_by records who cancelled)
- Dev kitchen simulation now runs server-side across every order, so a
  step nobody is watching no longer blocks the queue; each settled step
  may randomly be delayed or cancelled
- Humanize snake_case statuses in the UI

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:11:08 +02:00
lukasandClaude Opus 5.5 c124b0415a Add a dev-only kitchen simulation to the order status page
- "Simulate kitchen (dev)" button advances each placed/paid item's
  Preparation Steps as a Machine would, polling fresh status per item
- Each simulated step takes 10–30s, scaled from its estimated duration
- Timeline connector fills in with a Framer Motion animation when a
  step completes; finished steps and the line now use the success green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:25:52 +02:00
lukasandClaude Sonnet 5 815939cbc6 Add account settings page and polish avatar/SSO button edge cases
- New /account route: edit name/email, change password, delete account
  (detaches the user's orders instead of leaving a dangling reference,
  since orders.customerId has no DB-level FK to better-auth's user table)
- Better Auth: allow email changes without a verification email (no
  email sending is wired up anywhere in this app) and enable account
  deletion
- Header avatar: fall back to the initials placeholder if the profile
  image fails to load, not just when there's no image URL
- Fix low-contrast "Continue with Google" button in dark mode (it used
  the page background color and a hardcoded dark-gray label, so it
  nearly disappeared against a dark background)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-30 22:23:50 +02:00
lukasandClaude Sonnet 5 7f72baf97d Center both stepper glyphs on the item customization page
The +/- glyphs were wrapped in a flex-centered div, but only the +
button had been converted — the - button still used a plain span and
rendered off-center. Apply the same wrapping to both.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 11:52:48 +02:00
lukasandClaude Sonnet 5 8481032e30 Add menu item allergens, drop nav Help link, animate customizer price
- Menu items can now list allergens (shared catalog + join table),
  shown on the menu grid and item detail pages when applicable, and
  editable from the admin menu item form.
- Removed the top-nav "Help" link now that the footer links to it.
- The item customization page's price now uses the sliding
  AnimatedNumber, matching the basket line price.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 11:50:17 +02:00
lukasandClaude Sonnet 5 917fc275f9 Add a sticky site footer
Adds a Footer with the brand, quick links (Menu, Basket, Your orders,
Help), and a copyright line, rendered on every page. Body is now a
full-height flex column so the footer sits at the bottom of the
viewport on short pages instead of floating right under the content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 11:34:04 +02:00
lukasandClaude Sonnet 5 a8e655a810 Add a Help page with FAQ and contact form
Adds a /help route with an animated FAQ accordion and a contact form
(persisted via a new contact_messages table). The per-order-item "Get
help" link now points here and passes the order/item/status as search
params, which the page uses to show a short, state-specific note and
prefill the message field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 11:24:51 +02:00
lukasandClaude Sonnet 5 7f3728b5bf Animate basket price and item changes
Reflow remaining basket lines with framer-motion when an item is
removed, slide the per-line price when it changes, and tween the
basket total up/down to its new amount instead of snapping to it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 11:05:24 +02:00
lukasandClaude Sonnet 5 3d736769e0 Wrap stepper button glyphs in a span for iOS Safari centering
Flexbox centering of a bare text node inside a <button> is a known
WebKit quirk — Safari (including iOS) doesn't always vertically center
it the same way it centers an element child. Wrapping the +/- glyph in
its own <span aria-hidden="true"> (the button's own aria-label already
conveys the meaning) is the standard workaround.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 21:26:46 +02:00
lukasandClaude Sonnet 5 b0ef917cfc Center the +/- glyphs in the quantity stepper buttons
.stepperButton relied on the browser's default button content
centering, which the appearance: none reset in the previous commit
took away. Explicitly flex-center it (and zero out padding/line-height)
across all three copies (MenuItemCustomizer, MenuList, basket).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 21:24:23 +02:00
lukasandClaude Sonnet 5 05a91bb5c3 Fix buttons rendering with iOS Safari's default blue chrome
globals.scss's button reset only set font: inherit — no appearance
reset, no color fallback. Several buttons only set a background and
relied on inherited text color, which iOS Safari doesn't honor for
unstyled <button> elements, so their labels/icons rendered in Safari's
native blue instead of the theme color:

- .stepperButton in MenuItemCustomizer, MenuList, and basket
- .refreshButton in admin/kitchen
- .addButton in admin/MenuItemForm (color was only set on :hover)

Added explicit `color` to each, and hardened globals.scss's button
reset with `color: inherit` plus `-webkit-appearance: none` /
`appearance: none` as a fallback for any future button that forgets.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 21:22:11 +02:00
lukasandClaude Sonnet 5 0da1f47573 Match the basket's empty-state CTA styling to the orders page
Basket's "Browse the menu" button still had its original plain style
(flat opacity hover, no arrow) from before that button was reworked on
the orders page. Bring it in line: shadow, hover lift, and the shifting
arrow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 20:57:25 +02:00
lukasandClaude Sonnet 5 de158d7c36 Remove the food-icon backdrop, back to a plain empty state
Drop MenuBackdrop entirely and put the icon, title, text, and CTA
directly back on the empty state's own solid background — no separate
overlay card needed once there's no pattern behind it to cover up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 20:54:39 +02:00
lukasandClaude Sonnet 5 04a0e4a708 Zoom the icon pattern in and reduce blur
Scale the background pattern up (background-size 240px -> 340px) and
back off the blur (8px -> 3px) so icons read more clearly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 20:49:45 +02:00
lukasandClaude Sonnet 5 5bc57f0200 Remove the diagonal scroll animation and bump blur to 8px
Static now — drop the background-position keyframe animation and its
now-stale comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 20:48:36 +02:00
lukasandClaude Sonnet 5 c539ef84cd Rework the icon backdrop into a seamlessly scrolling tiled pattern
Replace the per-icon DOM elements (with client-side randomized
placement) with a single small SVG tile rendered once and set as a
repeating CSS background-image. Icons sit in a 4x4 grid within the
tile, evenly spaced and the same size, cycling through the food-icon
shapes and color palette.

Animates via background-position instead of per-element transforms:
moving by exactly one tile size per loop (linear, infinite) scrolls
the whole pattern diagonally forever with a perfectly seamless wrap,
which per-icon placements couldn't do without quadruplicating the DOM.
Also tried a grayscale pass (filter: grayscale(1)) alongside the blur,
now bumped from 2px to 4px.

This also sidesteps the hydration-mismatch concern entirely — there's
no more client-only randomization step, so no server/client mismatch
risk to guard against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvYdJ3os7tumimsLdLu2j9
2026-09-13 20:47:21 +02:00
lukasandClaude Sonnet 5 aa8d40bc4e Fix icon colors being overridden, and expand the palette
The .empty svg rule (styling the OrdersIcon/BasketIcon) was a plain
descendant selector, so it also matched — and, with higher specificity
than inherited color, overrode — the backdrop's own food-icon svgs
nested inside the same .empty box. Scope it to a direct child (> svg)
so it only targets the actual empty-state icon.

Also expands the backdrop palette from 3 to 6 colors (adds basil,
berry, and cocoa) for more variety across the icons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:35:00 +02:00
lukasandClaude Sonnet 5 6a81b305bc Give each backdrop icon a distinct light color
Cycle a small warm palette (terracotta, golden-brown, golden-yellow)
across the icons via a CSS custom property instead of one flat tint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:31:43 +02:00
lukasandClaude Sonnet 5 6bd63b53c6 Soften the empty-state icon backdrop with a slight blur
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:29:12 +02:00
lukasandClaude Sonnet 5 8653418778 Match the empty orders icon style to the empty basket state
Drop the tinted circle badge around the icon and use the same plain,
muted-gray sizing as basket.module.scss's empty state instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:26:27 +02:00
lukasandClaude Sonnet 5 2f37b6bba1 Move the food-icon backdrop onto the empty basket/orders cards and animate it
Confine MenuBackdrop to whatever card renders it instead of the whole
viewport: it's now position: absolute inset:0 within its container
(previously position: fixed, spanning the page) with a gentle floating
animation per icon (staggered delay, respects prefers-reduced-motion).
Wired it into the empty basket and empty orders states instead of the
menu list page.

Also gives .empty on both pages position: relative + z-index: 0 so it
forms its own stacking context — without an explicit z-index, the
backdrop's z-index: -1 child was escaping to the document root and
painting either fully behind everything or (on the basket page, where
motion.div meant `.empty` never picked up a context on its own) above
the card's own text, depending on the surrounding markup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:21:10 +02:00
lukasandClaude Sonnet 5 315491dd5b Add a scattered food-icon backdrop to the menu page and tastier copy
MenuBackdrop renders pizza, burger, and fries stroke icons at fixed,
hand-placed positions (no Math.random(), to avoid an SSR/client
hydration mismatch) as a fixed, pointer-events-none layer behind the
page content. Also tweaks the intro line to call out "your tasty menu".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:11:18 +02:00
lukasandClaude Sonnet 5 c5ed038bca Animate the empty orders state in and make its CTA more inviting
Fades/slides the empty state in on mount, gives the icon a tinted
circle badge instead of a bare stroke icon, and gives the "Browse the
menu" button a shadow, hover lift, and a shifting arrow instead of a
flat opacity hover.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:07:39 +02:00
lukasandClaude Sonnet 5 f4f3c26e1e Expose the dev server on the local network
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:04:11 +02:00
lukasandClaude Sonnet 5 5f375a0966 Animate orders out with a CSS transition instead of the View Transitions API
The prior attempt wrapped the sign-out router.invalidate() in
document.startViewTransition(), but that had two problems: invalidate()
does its own network refetch inside the transition's update callback,
which freezes the page for the round-trip instead of animating
anything, and TanStack Router's invalidate() explicitly opts itself out
of view transitions (sets shouldViewTransition = false) for exactly
that reason. On top of that, OrdersRoute briefly returns null between
the account loader clearing and the guest-orders lookup resolving,
unmounting OrdersListView and wiping any exit-animation state anyway.

Replace it with a plain, self-contained approach:
- OrdersRoute now keeps rendering the last known non-null orders list
  through that brief gap (a lastKnownOrders ref) instead of unmounting
  OrdersListView, so its local animation state survives.
- OrdersListView holds onto the previous non-empty list for
  EXIT_DURATION_MS after it goes empty, applying an `orderExiting`
  class that fades/slides each card out via a plain CSS transition,
  then swaps to the actual empty state once the transition finishes.

This has no dependency on browser view-transition support, network
timing, or router internals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 20:03:33 +02:00
lukasandClaude Sonnet 5 8d7f99848c Animate the Orders list out on sign out via the View Transitions API
Wraps the sign-out-triggered router.invalidate() in
document.startViewTransition() so the cleared order cards cross-fade
out (and the empty state fades in) instead of popping away instantly.
Each order card already carries its own view-transition-name (see
OrdersListView), so this reuses that per-card exit; the existing
::view-transition-group(*) timing in globals.scss applies automatically.

Falls back to a plain invalidate() where the API is unsupported, and
swallows rejection on the transition's ready/finished promises since
the browser can abort it in some states (e.g. a backgrounded tab) —
router.invalidate() still runs regardless.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:55:14 +02:00
lukasandClaude Sonnet 5 f819b0cd46 Invalidate the router on sign out so account orders clear immediately
Signing out only clears the auth session cookie; a route already
loaded (e.g. /orders) keeps its stale loader data until something
triggers a reload. Call router.invalidate() once signOut succeeds so
the current route's loader reruns against the now-signed-out session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:43:58 +02:00
lukasandClaude Sonnet 5 a2e97c293d Clear guest order refs and basket on sign out
Guest order refs and the basket both live in localStorage independent
of the auth session, so they'd otherwise linger for the next person to
use the browser. Add clearGuestOrderRefs (mirroring clearGuestIdentity)
and call it alongside the existing basket clear() from the sign-out
handler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 19:39:10 +02:00
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