← All tests

POS Section Rework — Slice 4 (local)

2026-05-14 Gate 1 local. Fourth slice of the POS Section Rework arc (Narong spec item 9.2): the Sessions history page. Replaces the Slice 3 placeholder at /cafe/pos/sessions with a real paginated, register-filterable table — Session · Point of Sale · Opened By · Opened/Closed · Starting/Ending balance · Theoretical Closing · Status. No migration.

9/9 local test PASS. nix-cafe typechecks clean. No migration. 3 files (1 edited + 2 new).

The table (structure asserted by the Gate 1 test)

SessionPoint of SaleOpened ByOpenedClosedStartingEndingTheoretical ClosingStatus
#00063Register 1NarongMay 13 · 6:19 PMMay 14 · 9:02 AM$142.25$142.25$142.25Closed
4f2e05feRegister 2SokMay 14 · 7:10 AM$200.00$665.38Open
Newest sessions first. "Point of Sale" = register name. The register
<select> filter scopes to one register; prev/next paginate (50/page).
Theoretical Closing = beginning_cash + cash sales + cash in − cash out,
the same formula the landing cards use (now a shared helper).

Local test — 9/9

✓ listSessions probe ran clean against local nix-db
✓ listSessions returns a page of rows + an accurate total
✓ SessionListRow carries every column the table needs
✓ Pagination — page 1 and page 2 are disjoint, newest-first
✓ Register filter — every returned session belongs to the filtered register
✓ theoreticalClosing matches an independent recompute from the tables
✓ Regression — fetchLatestSessionSummaries still works after the helper extraction
✓ sessions-client.tsx — desktop table + mobile cards, all 9 columns, filter + pagination
✓ sessions page — reports.view gated, calls listSessions, gathers the register filter

Probe ran against local lumiere-coffee (5 registers, 364 sessions):
  • listSessions total = 364, exactly matching a raw COUNT
  • page 1 = 50 rows, page 2 = 50 rows, 0 overlap, newest-first
  • register filter → 91 sessions for that register, 0 belonging elsewhere
  • theoreticalClosing recompute: $665.38 actual == $665.38 expected
  • fetchLatestSessionSummaries still returns 4 summaries post-refactor

SessionsClient uses useRouter (the filter + pagination navigate), so it
can't be renderToString'd standalone — its structure is asserted against
the source. Gate 2 does the real visual + behavioural pass on prod.

Files — 1 edited, 2 new, no migration

nix-cafe:
  lib/db/cafe_sessions.ts                      + aggregateSessionMetricsBySessionIds
                                               + listSessions / SessionListRow / SessionListResult
                                               fetchLatestSessionSummaries rewired to the helper
  app/(authed)/pos/sessions/page.tsx           placeholder → real page (listSessions + filter)
  app/(authed)/pos/sessions/sessions-client.tsx  new — table + cards + filter + pagination

Gate 2 plan + arc status

Gate 2 (prod, get-coffee + lumiere):
  • /cafe/pos/sessions renders the table inside the POS shell
  • register filter narrows the list; pagination prev/next works
  • theoretical-closing column matches a prod recompute
  • Playwright screenshots both tenants
  • 59/59 regression sweep

Arc: Slice 1 ✅ · Slice 2 ✅ · Slice 3 ✅ · Slice 4 (this) · Slice 5 —
Registers/Orders relocation cleanup + the section's permission gating
(still: /pos/sessions needs reports.view on top of the layout's pos.view).