Commit 870610b
authored
* dev: expose dev env via TryCloudflare tunnel with same-origin API proxy
- next.config.ts: dev-only rewrites /api -> :8787 and /chat -> :8788,
mirroring production's Pages Function proxy, so one tunnel to :3000
covers app + API + chat. Allow *.trycloudflare.com dev origins.
- dev.sh: start next dev with NEXT_PUBLIC_API_URL=/api and
NEXT_PUBLIC_CHAT_WORKER_URL=/chat (process env wins over .env.local),
and spawn a cloudflared quick-tunnel tab that prints the public URL.
* feat(schemas): staff links, tables, table-session types; optional tableSessionId on submit (#15)
* feat(backend): waiter mode - staff links, tables, table sessions, staff-gated intents (#15)
- migration 0014: staff_links, tables, table_sessions, orders.table_session_id
- one-use link exchange (public) -> session token; revocable staff session middleware
- staff floor/session/serve endpoints; move order-intents review+consume from admin to staff gating
- shared createOrder accepts optional tableSessionId (validated: open session only); no new submit path
- /orders authenticates staff session when tableSessionId present, bypassing diner submitMode gate
- admin staff-links + tables CRUD; kitchen board GET /orders now returns tableName
* feat(web): waiter mode UI - /staff floor, table detail, staff gate, /order-review migration (#15)
- /staff floor view + table detail (open/close session, mark served, add order)
- StaffGate consumes ?token, stores session, cleans URL; ask-personnel screen otherwise
- move /admin/order-review to top-level /order-review, staff-gated; diner QR points there
- staff-context selection submits with tableSessionId + staff header, returns to table
- admin staff-links (link/QR once, list, revoke) + tables CRUD pages + nav
- kitchen board shows table name; real translations for all 10 locales
- e2e: waiter mode flows; updated handoff spec for /order-review + staff session
* test(e2e): pin NEXT_PUBLIC_API_URL to direct backend in playwright config
The admin/catalog specs assert direct localhost:8787 URLs (as in CI); the
same-origin /api value the cloudflared dev tunnel writes to .env.local was
leaking into local e2e runs and failing 4 specs.
* test(e2e): real-backend smoke for staff-links, tables, /staff mounting (#15)
The waiter-mode e2e suite mocks every API call at the browser level, so a
backend that 500s on /admin/staff-links (as the stale local dev server did)
passed the suite. These specs follow the admin-api.spec.ts pattern: they run
against the seeded DEMO_MODE backend and catch route-mounting and schema
regressions the mocked specs can't see.
* Demock menu selection e2e
* Demock older admin e2e specs
* Demock order and staff e2e
* test(e2e): remove Playwright app-state seams
* test(e2e): remove backend mocks from Playwright suite
Playwright e2e now runs only against the real DEMO_MODE backend. Mocked chat-worker specs moved out of web/e2e; app-state seams and page.route fulfils were removed from order, staff, admin, and menu specs. E2E_MODE is an explicit test-only Worker binding used by CI/local e2e to bypass rate limiting without allowing browser spoofed Cloudflare headers.
* feat(orders): waiter table pick at QR review, board elapsed time, UI polish (#15)
- /order-review: waiter selects the target table before submitting; picking a
table without an open session opens one, order lands on it and the kitchen
board shows the table name
- kitchen board: show elapsed time since last status change per order
- selection: replace native confirm() with inline clear confirmation
- item detail: rounded sheet corners so the add-to-selection footer no longer
overflows the popup
* feat(demo): seed Cucina/Bar departments, entry routing, and 10 tables (#15, #11)
Departments Cucina/Bar with food->Cucina, drink glasses->Bar assignments via
order_destinations/entry_destinations. 10 tables split Sala 1..6 / Terrazza 1..4
(area encoded in the flat name). Demo reset wipes tables/sessions then restores
the seed.
* feat(staff): consume intent with optional lines override (#15)
The waiter's edited selection is created instead of the frozen snapshot; lines
validated with SubmitOrderLineSchema and run through createOrder's stale-item
path (409 stale_items lists ids, claim released so the intent stays reusable).
The intent row itself stays immutable.
* feat(review): waiter edits order at review + post-submit navigation (#15)
Order review page now lets the waiter change line quantities, remove lines
(including stale ones that blocked submit), and add items from a searchable flat
catalog picker. Edited lines submit as a consume override. Success screen offers
Go to floor and, when bound to a table, Go to table. Real translations for the 8
new keys in all 10 locales.
* feat(selection): show waiter QR in a modal instead of replacing the view (#15)
The QR handoff now opens as a headlessui Dialog overlay (same pattern as
MenuItemDetail); the selection stays mounted behind it and dismissing returns
without losing cart state.
* test(e2e): ride the demo seed for tables/departments; cover review edit flow (#15)
Flow specs use seeded Sala 1 and Cucina/Bar assignments instead of creating
their own via API. New handoff test edits the intent (bump qty, add item, bind
table) and asserts the override on the kitchen board plus post-submit nav.
* feat(selection): open "my selection" as a modal over the menu; reuse QR intent (#15)
Task 1: extract the selection UI into a shared SelectionContent component used
by both the /selection page (chrome + back link) and a new bottom-sheet Dialog
on the menu. The menu pill is now a button that opens the modal (no navigation,
menu stays mounted behind); staff add-order still deep-links to /selection.
Task 2: cache the created order intent (url + expiresAt + line fingerprint);
reopening the QR with an unchanged cart and >2min left reuses it instead of
POSTing again, so a diner toying with the button no longer burns the 10/min
per-IP limit. Added a 429-specific selection.qrRateLimit string in all 10 locales.
Tests: extended SelectionPageClient unit tests (intent reuse, 429 error); added
a MenuPageClient modal-open test; updated e2e specs for the pill-as-button and a
no-navigation assertion. web+backend+chat unit suites green, lint clean,
i18n-completeness green, full Playwright suite green (119).
* feat(staff): order lifecycle changelog on table detail; fix euro/cents mixup at review (#15)
- order_events table (0015): one row per status event with actor
(diner/staff/admin), written at createOrder, admin status patch, staff serve
- GET /staff/sessions/:id returns each order's events; TableDetail renders the
changelog (time, status, actor)
- fix: /order-review showed 0.07 EUR for a 7.50 item - the public catalog
serves euros while order lines are cents; normalize at the seam (the only
crossing point) with a regression test using real euro-shaped catalog data
- QR consume without a table now logs actor 'staff' (explicit actor arg),
demo reset clears order_events
* fix(staff): require table when consuming waiter QR intent (#15)
Waiter consume now requires tableSessionId at the shared schema/route boundary, so 'no table' is not a valid backend path. The review UI disables submit until a table is selected and uses a disabled placeholder instead of a no-table option. Tests cover missing table 400 without claiming the intent, invalid lines with a valid table, and the real e2e helper opens a seeded table before API consume.
* feat(demo): seed reusable waiter links for stable QR access (#15)
Demo reset now seeds Marco/Giulia waiter links with fixed tokens and fixed session tokens. In DEMO_MODE only, consumed demo tokens can be exchanged again, so /staff?token=demo-staff-link-marco is safe to print as a stable QR while normal staff links stay one-use. Tests cover seeded rows and demo-only reuse.
* fix(demo): keep seeded waiter QR one-use (#15)
Demo reset still seeds stable waiter tokens for printable QR URLs, but consuming them now follows the normal staff-link rule: first device wins, second scan is consumed. Removed the DEMO_MODE reuse branch and stopped seeding a pre-consumed session token.
* feat(staff): show waiter names and clearer item-done state (#15)
Order lifecycle events now store actorName so kitchen/table views can show the waiter who submitted/served an order. Waiter table submits and QR consumes pass the staff session name through the shared createOrder path. Department checkboxes now visibly mark done rows and all-order cards show destination done state.
* feat(#15): floor-plan model — areas + table positions (backend)
Introduce a floor-plan layer on top of the flat table list:
- New `areas` table (name, sort_order); `tables` gains area_id (FK, no
cascade — an area can't be dropped while tables reference it), x/y on a
1000x700 virtual canvas, and shape ('rect'|'circle'). Migration 0017.
- Admin areas CRUD (GET/POST/PUT/DELETE); area delete returns 409
{error:'has_tables'} when tables still reference it. Table create now
requires areaId (400 invalid_area if unknown) + shape; PATCH
/admin/tables/:id/position saves drag-end coordinates.
- /staff/floor returns {areas, tables}; each table carries area_id/x/y/shape
and oldestSubmittedAt (createdAt of the oldest still-'submitted' order in
the open session, else null) to drive tile colour/aging on the client.
- Order/table names are composed 'Area · Name' on the kitchen board and
session detail via a left join.
- Demo seed reworked into two areas (Sala rect, Terrazza circle) with baked
positions and short numeric table names.
- Schemas: Area, TableShape, Create/UpdateArea, UpdateTablePosition
(x:0..1000,y:0..700); CreateTableBody requires areaId+shape.
* feat(#15): floor-plan editor + staff view (web)
Build the client on top of the backend floor-plan model:
- Shared FloorCanvas (web/src/components/floor): absolutely-positioned tiles
rendered by percentage into an aspect-ratio 1000/700 container so it scales
for free. Read-only for staff; pointer-drag (setPointerCapture, snap-25,
no dnd lib) with optimistic PATCH auto-save for admin. Staff tile colours:
gray (no session), green (open, no submitted), amber (<15min), red
(>=15min), with an elapsed-minutes label.
- Admin /admin/tables: area management (add/rename/delete with a friendly
has_tables message), area tabs, add-table form (name + shape), and the
editor canvas + per-area table list. Area is fixed after creation.
- Staff /staff: replaces the tile grid with area tabs + read-only canvas;
tap opens/continues the session. Keeps table-<id> testids; adds a 30s tick
for the minutes label.
- api.ts: fetchAreas/createArea/updateArea/deleteArea/updateTablePosition;
createTable now takes areaId+shape; /staff/floor typed as {areas,tables}.
OrderReviewPage composes 'Area · Name' in the table select.
- i18n: new admin.tables.* keys in all 10 locales.
- Tests: FloorCanvas colour/minutes unit test, TablesPage area-tab/create/
409 test; e2e updated for renamed tables + composed labels, plus a floor
tab/tap spec and a real-backend position-PATCH spec.
* Rework admin tables page to canvas-first colour-coded floor (#15)
Owner feedback: the admin tables page should be canvas-first and colour-coded
like the staff floor; the separate table list was redundant.
Backend:
- Extract shared floor-state builder into lib/floor.ts (buildFloorState with
includeInactive). Staff /floor uses it (active tables only, same response
shape). New admin GET /admin/floor returns the same shape including inactive
tables with an `active` flag.
Web:
- TablesPage: remove the table list. Poll /admin/floor (10s), render tiles with
the same colour coding as staff (inactive dimmed gray). Tap a tile (pointer
travel < 5px) opens an inline action panel: rename, activate/deactivate,
delete (inline confirm). Drag still moves + autosaves position.
- FloorCanvas: tap-vs-drag threshold in editable mode; colour tiles in both
views via the shared tileVisual/COLOR_CLASS.
- api: fetchAdminFloor(); AdminFloorTable schema type.
- i18n: canvasHint + status strings (free/open/waiting) across all 10 locales;
dropped the now-unused dragHint.
Tests:
- backend: /admin/floor returns state and includes inactive tables while
/staff/floor excludes them (and carries no active flag).
- web unit: tap-opens-panel rename/delete; canvas renders colour-coded tiles.
- e2e: expect /admin/floor call, tiles visible, tap opens panel, rename
round-trips (demo reset after).
* fix(backend): drop blanket admin/staff rate limits, scope the rest
A single venue NAT IP shared one per-IP counter across all mounts, so
staff polling exhausted the budget and 429ed the admin UI. /admin/* is
behind Cloudflare Access and /staff/* requires a session token (consume
has its own limiter), so the blanket limits added nothing. Remaining
public limiters (/catalog/view, /orders) now key on ip+scope so they
cannot starve each other.
* feat(checks): backend conto lifecycle + admin table detail (#15)
- checks table (0018): open/settled/voided, frozen line snapshot, discount + adjustments JSON
- computeCheckTotals shared helper (schemas): subtotal/total, percent rounding, clamp >=0
- admin-checks routes: GET /admin/tables/:id detail, POST /sessions/:id/check, PATCH /checks/:id, settle (closes session), void, POST /sessions/:id/close (manual)
- orders createOrder guard: 409 check_open while an open check freezes the session
- remove staff close-session route (waiters no longer close tables)
* feat(checks): admin table page + conto UI, view/edit toggle (#15)
- new /admin/tables/[tableId] page: session orders, check card (discount/adjustments/settle/void/print), history with reprint
- TablesPage: default tap navigates to the table page; "Edit layout" toggle gates drag + add/area/panel actions
- api.ts: fetchAdminTableDetail, createCheck, updateCheck, settleCheck, voidCheck, adminCloseSession; drop closeTableSession
- staff TableDetail: remove close-session button (admin owns checks)
- surface 409 check_open on diner/waiter submit paths
- print CSS, i18n across 10 locales, unit + e2e coverage
* feat(demo): showcase seed - live table + settled-check history (#15)
Demo reset now seeds (outside E2E_MODE only, since e2e asserts a clean
floor): Sala 2 with an open 25-min session and two active Marco Demo
orders spanning Cucina+Bar, and 4 closed sessions with settled checks
per remaining table spread over the past 8 days (waiters alternating,
one in four with a 10% discount). Deterministic ids, daily numbers
computed per UTC day so live submits continue from the seeded count.
* feat(admin): add table orders and full demo history (#15)
Sala 2 now keeps the live showcase session and still gets the same settled-check history as every other table. Admin table detail can open a free table and append orders to the current session through admin-only endpoints backed by the shared createOrder path, so Cucina/Bar routing and changelog semantics stay consistent. Orders remain blocked while a conto is open.
* Fix static export routes and stabilize e2e
* Run CI e2e serially
1 parent 0fd54ca commit 870610b
102 files changed
Lines changed: 7652 additions & 2474 deletions
File tree
- .github/workflows
- backend
- drizzle
- src
- __tests__
- db
- lib
- middleware
- routes
- docs
- packages/schemas/src
- web
- e2e
- admin
- fixtures
- messages
- public
- src
- app
- [locale]
- selection
- admin
- order-review
- staff-links
- tables
- detail
- order-review
- staff
- table
- components
- admin/pages
- floor
- staff
- lib
- stores
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
| 79 | + | |
79 | 80 | | |
80 | 81 | | |
81 | 82 | | |
| |||
97 | 98 | | |
98 | 99 | | |
99 | 100 | | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
0 commit comments