← All tests

Receipt preview modal PROD

In-shell receipt preview before the print dialog fires. Operators get to eyeball the receipt before handing it to a customer or before committing to a reprint. Bundled with this Gate cycle: the full Render→Supabase prod database migration after Render's free tier auto-suspended.

8/8 prod checks passed for the receipt preview modal.
78/78 total prod tests green — no regressions, full Supabase migration verified.
test-receipt-preview-prod.mjs (this bundle)8/8
test-r2-followups3-prod.mjs (round 3)7/7
test-r2-followups2-prod.mjs (round 2)11/11
test-r2-followups-prod.mjs (round 1)19/19
test-nix-os-r2-4b-prod.mjs (Starter UI)12/12
test-nix-os-70-2-prod.mjs (R2 uploads)10/10
test-phase1-prod.mjs (route smoke + SSO)11/11

Render → Supabase migration (executed during this Gate cycle)

Trigger: Render free-tier Postgres auto-suspended on inactivity, blocking
prod tests. Migrated to Supabase (aws-1-ap-northeast-1.pooler.supabase.com)
during the Gate 2 cycle for receipt preview.

Steps executed:
  1. Patched nix-outdoor-sales-backend/knexfile.ts + migrate.js to
     enable SSL for any non-localhost host (was hardcoded "render.com").
     Committed as fix(db): 76e9df5.
  2. Ran 99 knex migrations against Supabase via session-mode pooler (port 5432).
     Migrations hung on port 6543 — pgbouncer transaction mode kills knex's
     prepared statements during apply.
  3. Ran migrate.js to layer on cafe/commerce + discount columns. All
     idempotent passes confirmed.
  4. Seeded get-coffee tenant + Narong owner via env-driven seed script
     (scripts/seed-supabase-getcoffee.ts — env-overridable). Used the same
     DB_ENCRYPTION_KEY as the prod Worker so encrypted Odoo creds round-trip.
  5. Seeded demo tenant for phase1 SSO tests.
  6. wrangler secret put DATABASE_URL — Cafe Worker now points at Supabase.
  7. wrangler hyperdrive update 47d252536c3746fcbdf39d332043810c —
     INITIALLY targeted port 6543 (transaction mode); /cafe/dashboard 500'd
     with "Failed query" on prepared statements. Switched to port 5432
     (session mode) — Hyperdrive does its own edge pooling so the
     session-mode origin doesn't add connection pressure.
  8. Aligned DB_ENCRYPTION_KEY between local .env.local and Worker secret —
     local key was different, so encrypted Odoo creds didn't decrypt.
     getTenant returned null and /cafe/pos showed "Cafe is not configured".
  9. Updated PROD_DB constant in 32 test-*-prod.mjs files (sed batch) +
     reference_prod_database.md memory.

Heads-up captured in memory:
  - Use port 5432 for EVERYTHING (Hyperdrive, runtime, migrations, seeds).
    pgbouncer transaction mode (6543) breaks Drizzle/pg/knex prepared
    statements.
  - DB_ENCRYPTION_KEY must match between local .env.local and the Worker
    secret, or encrypted columns (cafe.tenant_config.odoo_api_key_enc)
    won't round-trip.

What's new — code surface (receipt preview)

nix-cafe (commit cdfe197):
  components/receipt/receipt-preview-modal.tsx        — NEW shared modal
  app/(authed)/pos/starter-register-client.tsx        — LastOrderBanner uses modal
  app/(authed)/orders/starter-orders-client.tsx       — per-row Print uses modal

nix-outdoor-sales-backend (commit 76e9df5, ride-along):
  knexfile.ts + migrate.js — SSL enabled for any non-localhost host
                              (supports Supabase migration off Render)

Operator follow-ups (not blocking)

• "Skip preview" path on the success banner — currently every Print opens
  the modal first. If operators want fast-path printing immediately after
  a sale (since they just rang it up and know what's on it), one-line if.
• Receipt preview is currently rendered at desktop width but uses the
  thermal 72mm template — looks a bit narrow on a 24" monitor. Could
  scale up the visual preview while keeping the print at 80mm.
• Render free-tier database is permanently dead. Memory now points at
  Supabase. Old Render URL "dpg-d739ftoule4c73eup080-a..." in any of
  Narong's notes / scripts / .env files needs to be replaced.
• Hyperdrive caching at the Cafe Worker layer is unchanged from before —
  still ~60s SELECT TTL bypassed via db.transaction() in getTenant.
  Same gotchas apply.