← All tests

POS Section Rework — Slice 3 (local)

2026-05-14 Gate 1 local. Third slice of the POS Section Rework arc (Narong spec item 9): the master-detail "Point of Sale" submenu shell. A new pos/layout.tsx gates the section and renders a left nav rail — Dashboard / Registers / Sessions / Orders — copying the NIX-OS-87 Settings pattern. The dark sidebar's "Register" item is untouched. No migration.

8/8 local test PASS. nix-cafe typechecks clean. No migration. 6 new files + 4 edits.

The shell — and the register-view escape hatch

Normal sub-route (/cafe/pos, /pos/registers, …)        ?config=N register terminal
┌──────────────────────────────────────────┐          ┌──────────────────────────────┐
│ Point of Sale                            │          │  (full-bleed POS terminal —  │
│ Registers, sessions, and orders…         │          │   PosShell returns children  │
│ ┌──────────┬───────────────────────────┐ │          │   bare, no header / rail /   │
│ │ Dashboard│  pos-detail               │ │          │   wrapper, so its own -m-8   │
│ │ Registers│  (the page's content      │ │          │   still cancels the layout   │
│ │ Sessions │   renders here)           │ │          │   padding exactly as before) │
│ │ Orders   │                           │ │          │                              │
│ └──────────┴───────────────────────────┘ │          └──────────────────────────────┘
└──────────────────────────────────────────┘

pos/layout.tsx (server)         → requirePermission('nix_cafe.pos.view')
                                  → {children}
pos/_components/pos-shell.tsx    → useSearchParams().get('config') != null
                                  → if register view: return <>{children}  (bare)
                                  → else: header +  + 
{children}
pos/_components/pos-nav.tsx → the 4-item rail, active row from usePathname()

Local test — 8/8

✓ Sessions placeholder renders via react-dom/server
✓ All Slice 3 source files present
✓ pos/layout.tsx gates on nix_cafe.pos.view + wraps PosShell in Suspense
✓ PosShell passes the ?config= register view through bare (no rail/header)
✓ PosNav rail has the 4 sub-items with correct hrefs
✓ Registers + Orders sub-routes re-export the existing pages
✓ Section h1 removed from the landing pages (the shell owns it now)
✓ registers-client shop selector is path-relative (works under /pos/registers)

PosShell / PosNav depend on Next navigation hooks so they can't be
renderToString'd standalone, and /cafe/pos is authed (can't render
locally without the SSO stack). Gate 1 therefore renders the one
hook-free component (the Sessions placeholder) + makes structural
assertions against the source. Gate 2 does the real behavioural pass on
prod — rail renders + navigates, ?config= still full-bleed, sub-routes
load inside the shell.

Files — 6 new, 4 edited, no migration

new:
  app/(authed)/pos/layout.tsx                  section shell + perm gate
  app/(authed)/pos/_components/pos-shell.tsx   client: rail+header / ?config= passthrough
  app/(authed)/pos/_components/pos-nav.tsx     client: the 4-item rail
  app/(authed)/pos/registers/page.tsx          re-exports settings/registers/page
  app/(authed)/pos/orders/page.tsx             re-exports orders/page
  app/(authed)/pos/sessions/page.tsx           placeholder (Slice 4 builds the real one)
edited:
  app/(authed)/pos/pos-landing.tsx             dropped its own "Point of Sale" h1
  app/(authed)/pos/page.tsx                    dropped the h1 from the empty state
  app/(authed)/pos/loading.tsx                 dropped the fake-h1 skeleton block
  app/(authed)/settings/registers/registers-client.tsx
                                               shop selector now path-relative

Gate 2 plan + Slice 5 carry-overs

Gate 2 (prod, get-coffee + lumiere):
  • /cafe/pos renders the header + 4-item rail; Dashboard is the landing
  • click Registers / Sessions / Orders — rail stays, detail pane swaps,
    each sub-route loads inside the shell
  • /cafe/pos?config=N still renders the full-bleed terminal (no rail)
  • 59/59 regression (r1-2-landing + r7 in the blast radius)

Carry-overs for Slice 5 (the memo already scopes these):
  • /pos/registers re-exports the Settings page, which still
    requirePermission('nix_cafe.settings.view') — combined with the
    layout's nix_cafe.pos.view that's a stricter gate than the old
    /cafe/settings/registers. Slice 5 settles permission gating.
  • Slice 5 also drops the Registers row from the Settings nav + adds a
    redirect stub, and relocates Orders off the top-level sidebar.