2026-05-27 Gate 2 ship. Item #13 on Narong's test-run Notion doc: "Print Shift Report from POS interface → Receipt Printer". Narrow 80mm thermal-receipt summary distinct from U7's A4 PDF. Prints via HTML iframe + window.print() — same path as order receipts, so the existing thermal-driver setup transfers and any future native-shell wrapper can intercept for silent print.
lib/db/shift_report.ts — 3 DAOs: cashRefundsForSession (best-effort EXISTS-join on cash payments of parent order), salesAggregatesForSession (gross + taxes + refund totals in one transaction), posConfigNameForSession.lib/db/cafe_sessions.ts — createOpenNixSession now wraps the INSERT in a transaction that reads MAX(shift_number) + 1 per pos_config_id before inserting. Concurrent-shifts-collision-proof.lib/db/schema.ts — cafeSessions.shiftNumber added (nullable integer).lib/native-shift-report.ts — pure-fn buildShiftReport assembles ShiftReportData; pure-fn renderShiftReportHtml(data) returns the thermal HTML as a string (added during Gate 2 after Next 15.5 build blocked react-dom/server imports — see Mid-Gate-2 finds below).components/receipt/shift-report.tsx — pure React component, inline styles, 80mm width. Kept for client-side preview / future tests; the route uses the parallel HTML-string renderer.app/api/cafe/sessions/[sessionId]/shift-report/route.tsx — GET handler. Fans out 7 DAOs in parallel, resolves user labels (tenant_users + commerce.pin_identities fallback), formats timestamps in shopTimezone || tenant.timezone (U7.1 pattern), calls renderShiftReportHtml(data), wraps in HTML envelope with @page size: 80mm auto, returns text/html.shift-report-print-button.tsx — fetch HTML → inject hidden iframe → iframe.contentWindow.print() → cleanup. Same flow as openPrintWindow in lib/native-receipt.ts (NIX Cash extension hostname-guard already covers it).close-shift-print-starter)close-shift-print-pro)pos-sessions-print-{id})pos-more-menu.tsx — inline "Print Shift Report" item with same state-machine as the existing PDF + Daily Sale itemsTest ran against closed session b967eb21 on shop Lumière BKK1, register Test 1, shift #30 under tenant Lumière Coffee Roasters:
cafe.sessions.shift_number backfilled across all 3 prod tenants (demo 4/4, get-coffee 72/72, lumiere 408/408).text/html; charset=utf-8 / non-empty body with full HTML envelope + @page size: 80mm auto.| ✓ | Prod: cafe.sessions.shift_number column exists |
| ✓ | Prod: shift_number backfilled across all 3 tenants — demo 4/4, get-coffee 72/72, lumiere 408/408 |
| ✓ | Find a closed lumiere session w/ orders + branch + POS metadata — b967eb21, shift #30, "Lumière BKK1" / "Test 1" |
| ✓ | SSO-login lumiere owner |
| ✓ | Fetch shift-report HTML — 200 status, text/html, > 800 bytes |
| ✓ | HTML contains title + section headings + store/POS metadata (20 expected strings) |
| ✓ | HTML wrapped in @page 80mm thermal envelope |
| ✓ | Gross sales + cash payments in HTML match raw prod SQL: gross=$3.00 cash=$0.00 |
| ✓ | No 5xx HTTP responses during the suite |
Fetched live from lumiere-coffee.nixtech.app/cafe/api/cafe/sessions/b967eb21-…/shift-report via authenticated GET. Opens inline below or in a new tab.
| test-phase1-prod.mjs | 11/11 |
| test-phase2-sso-outdoor-prod.mjs | 6/6 |
| test-m1-prod.mjs | 10/10 |
| test-r7-prod.mjs | 14/14 |
| test-r8-prod.mjs | 4/4 |
| test-phase2-cafe-multishop-prod.mjs (solo) | 6/6 |
narongix chain ran sequentially, first-attempt green. phase2-cafe-multishop ran solo per feedback_phase2_cafe_multishop_solo_retry — first-attempt green (11th validation today).
renderToString(<ShiftReport />) from react-dom/server. Local typecheck + the test probe (npx tsx) both passed cleanly. CF Workers Build failed at next build with "You're importing a component that imports react-dom/server. To fix it, render or return the content directly as a Server Component instead". Fix in commit 7a1162e: added a parallel pure-fn HTML-string renderer renderShiftReportHtml(data) to lib/native-shift-report.ts, swapped the route to call it instead. React component file kept around for future client-side preview / tests. Saved durable rule: feedback_no_react_dom_server_in_route_handlers.6ea7462) per feedback_auto_deploy_on_push; CF build kicked off within seconds, surfaced the renderToString issue, fix push (7a1162e) landed and the deploy succeeded.until [ wrangler deployments list | grep ]; do sleep 15; done pattern from U7.2's Gate 2 polled for the U8 deploy. Auto-fired the prod test as soon as the fix landed. No manual recheck loop needed.