← All tests

POS Navigation + Displays via Registers STAGING

Two items from the Next Phase page, both created 2026-07-24. Point of Sale Navigation fixes the section's contents and order to Orders Management · Registers · Sessions · Discounts & Promotions · Refunds. Accessing Displays via Registers Page asks for Kitchen Display, Preparation Display and QR Menu to be reachable from the Registers page. No migration.

Three of the five nav rows already existed. The other two are real work: Discounts & Promotions moved out of Configurations, and Refunds is a new page that had no equivalent anywhere in the product.

The nav

The order is Narong's, not alphabetical, and the test asserts it as a sequence — a set-membership check would have passed on the old nav the moment the two new rows existed.

the Point of Sale group in the sidebar
The five children, in his order.

Discounts & Promotions moved

It now lives at /cafe/pos/discounts. /cafe/settings/discounts redirects, so existing links and bookmarks still land — the same treatment /pos/registers got when the registers admin moved the other way. Its old group, Discount & Loyalty, is gone entirely: this was its last row, since Loyalty Mobile App Settings went top-level on 07-22.

discounts under Point of Sale
The promotions admin, unchanged, under Point of Sale.
configurations rail without discounts
Configurations, with the emptied group removed.

Refunds — a ledger, not a filtered order list

The obvious implementation is "orders where state = refunded". That would be wrong twice over: it cannot show a partial refund as its own event, and an order refunded more than once collapses into a single row. The page reads cafe.order_refunds — the per-event table that already existed for the refund action — so each refund is its own line with its own number, amount, reason and cashier.

Order Management shows one business date at a time, so a refund's order number links to /cafe/orders?date=… for the day that order was rung up. Rows too old to carry a business date render as plain text rather than a link that would silently land on today.

refunds empty state
Before: staging had no refund events at all.
refunds with one event
After a refund taken through the real refund route: number, parent order, branch, cashier, reason, amount.

Those two screenshots are a genuine before/after. staging had zero refund events, so the ledger could only ever have shown its empty state; the test refunds demo's one paid order through POST /cafe/api/cafe/orders/:id/refund — the same endpoint the Order Management modal posts to — and then asserts the ledger lists it. A hand-inserted row would only have proved the SELECT.

Displays on the Registers page

Until now the Kitchen Display was only reachable from inside an open register terminal, so setting up a kitchen screen meant opening a till first. The shop's displays now sit on the Registers page itself. The URL carries the shop's display secret, minted lazily on first render and stable afterwards, so a bookmarked kitchen tablet stays authenticated.

display chips on Pro
Demo (Pro). Kitchen Display is live; the other two are disabled.
the kitchen display board
Following the chip's URL actually loads the board — the minted secret authenticates.

Preparation Display and QR Menu render disabled, not built. Both are marked NOT YET IMPLEMENTED on the Next Phase page — neither exists anywhere in the product. They're shown greyed so the shape of the feature is visible, the same convention the tier locks use. Building either is its own slice.

display chips on Starter
Lumière (Starter). Kitchen Display is Pro-only, so it's greyed with a Pro tooltip rather than hidden — and it carries no href to follow.

One decision worth flagging

Refunds is gated on reports.view (manager-level), the same permission Sessions uses — not orders.view. A POS User can already see Order Management, but that screen is scoped to a single business date, whereas the refund ledger spans thirty days by default and offers "All". Gating on orders.view would have quietly handed a POS User a wider financial window than any screen they have today. There's a negative control for it: a user demoted to pos_user sees neither new row and is redirected off both routes.

Checks

SuiteResult
Local — nav, both pages, the redirect, displays across both tiers, permission boundary16/16
Staging — real SSO on the deployed Worker, Pro and Starter12/12
Staging smoke5/5
tsc --noEmit (nix-cafe)clean

Every claim carries a negative control: the nav is asserted as an ordered sequence, Discounts is checked gone from its old rail, Kitchen Display is checked both live on Pro and locked-with-no-href on Starter, and the date filter is proven to exclude in one direction (local fixtures older than the window are hidden) and include in the other (a same-day refund appears under the default). Expected counts and totals are read from the database at run time, not hardcoded, and the suite fails loudly if the fixture stops discriminating.

The local suite lied three times before it was trustworthy

Worth recording, because all three are shapes that produce false confidence rather than false alarms:

  1. Fixed sleeps against first-compile. Brand-new routes compile on first request in dev, so a 3-second wait failed on whichever route happened to be cold — and passed moments later on the same assertion. Replaced with waits on each page's root marker.
  2. A timezone shift in the test's own expectation. new Date(business_date).toISOString() moved the expected day one west of the stored value. The app was right; the assertion was wrong. Now compares against ::text from the database.
  3. The permission probe raced its own setup. auth() memoises the resolved permission set per token, so the cookie minted before the role change kept serving the old permissions — and the route looked unguarded when it wasn't. Minting the cookie after the demotion made it deterministic.

A fourth failure was the dev server itself degrading after repeated edits; a restart cleared it, and the suite then ran clean repeatedly.

nix-cafe staging b30214d (Worker 8528ba5b) · localization main 2beb2a3 (37 new strings; km falls back to English until Narong fills it). No migration. Two new routes. Parked for the scheduled prod promote.