← All tests

POS Section Rework — sidebar nav rework (local)

2026-05-14 Gate 1 local. Follow-up after the arc: per user feedback on the shipped Slice 3, the POS submenu should live in the dark sidebar, not as an in-page rail. This replaces the flat "Register" + "Orders" OPERATIONS rows with one expandable "Point of Sale" group, deletes the in-page rail, and re-adds the page header the old shell owned. No migration.

8/8 local test PASS. nix-cafe typechecks clean. No migration. 8 files (2 deleted, 1 new, 5 edited).
BEFORE — flat rows OPERATIONS Dashboard Register → /pos Orders → /orders Products Customers + an in-page rail inside /cafe/pos
AFTER — expandable group OPERATIONS Dashboard Point of Sale v · Registers → /pos · Sessions → /pos/sessions · Orders → /pos/orders · Settings → /pos/registers Products Customers no in-page rail — sidebar owns the nav

Local test — 8/8

✓ filterNavForPermissions probe ran clean
✓ Owner sees the full 'Point of Sale' group (Registers/Sessions/Orders/Settings)
✓ Permission filtering — cashier sees only Registers; no pos.view → no group at all
✓ Old flat 'Register' + 'Orders' OPERATIONS rows are gone
✓ nav-model.ts — NavGroup model + the Point of Sale group, no server import chain
✓ sidebar.tsx — render components + imports the model from nav-model.ts
✓ In-page rail removed — pos-shell + pos-nav deleted, pos/layout.tsx is a passthrough
✓ Landing re-owns its 'Point of Sale' header (the shell that held it is gone)

filterNavForPermissions is a pure exported function (nav-model.ts) —
unit-tested directly: owner → all 4 children; cashier (pos.view only) →
just Registers; no pos.view → no group; pos.view+reports.view → Registers
+ Sessions. The expandable group's render (auto-expand, active highlight,
mobile drawer) is verified structurally + by typecheck; Gate 2 does the
visual + behavioural pass on prod.

Files — 8 touched, no migration

new:
  components/layout/nav-model.ts               buildNav + filterNavForPermissions + nav types
deleted:
  app/(authed)/pos/_components/pos-nav.tsx     the in-page rail
  app/(authed)/pos/_components/pos-shell.tsx   the in-page master-detail wrapper
edited:
  components/layout/sidebar.tsx                + expandable NavGroup support (NavGroupItem),
                                               imports the model from nav-model.ts
  app/(authed)/pos/layout.tsx                  PosShell/Suspense → plain requirePermission passthrough
  app/(authed)/pos/pos-landing.tsx             re-added the "Point of Sale" header
  app/(authed)/pos/page.tsx                    re-added the header to the empty state
  app/(authed)/pos/loading.tsx                 re-added the header skeleton

Gate 2 plan

1 commit to nix-cafe (8 files, no migration) → push → CF deploy.
Prod test on get-coffee + lumiere:
  • the sidebar shows the "Point of Sale" group; it expands; the 4
    children navigate to /pos, /pos/registers, /pos/sessions, /pos/orders
  • the old flat "Register" / "Orders" rows are gone
  • /cafe/pos renders the landing with its own header, no in-page rail
  • /cafe/pos?config=N register terminal still renders full-bleed
  • Playwright screenshots both tenants
  • 59/59 regression sweep (r1-2-landing + phase1 route sweep exercise
    the sidebar on every page)

Note: this reverses Slice 3's in-page-rail decision — that was the
user-approved choice at scoping time, changed after seeing it live.