← Back to gallery

U1 — Variant picker fix + Add Register shop picker + View-in-Orders in-place PROD

2026-05-25 Gate 2 ship. Three independent fixes from Narong's same-day feedback (Telegram + Test-Run #3 doc). The critical regression: H4-Y's variant flow shipped earlier today but the picker never fired at the POS for Starter tenants — fixed with a 1-line DAO lookup change. Plus two small UX additions (Add Register shop picker, View-in-Orders in-place tab switch).

Summary

Status
7/7 prod · 8/8 local · typecheck clean
Commit
nix-cafe a33112f on karouna-dev
Files
6 files modified · ~130 LOC net new · no migration
Tested on
lumiere-coffee.nixtech.app (Starter)

U1.1 — POS variant picker bug (the regression Narong reported)

Narong's report (Telegram 2026-05-25): "I did go with the existing flow and add some variants to Capuccino. When I went to the POS and tried to select, it didn't show any difference." Same as Test-Run #3 doc red item #9.

Root cause: buildShellAttributePairs in lib/pos/attribute-pairs.ts queried WHERE product_product.cafe_product_id IN (...) to resolve which template a variant belongs to. cafe_product_id is a back-ref that's NULL on Starter native rows (only Pro+cafe-master backfilled rows have it set). Lookup returned 0 rows → 0 attributes → VariantPicker never opened.

Fix: switch the lookup to WHERE product_product.id IN (...). Post-H4-X cafe.products.id = product_product.id (the view's id IS the variant's id), so the caller's input keys ARE the product_product UUIDs. Works for Starter + Pro+cafe-master + Pro+odoo-master.

Verification path: the prod DAO probe (check #2) seeds a throwaway template + variant + attribute + value + line on prod Supabase against lumiere-coffee, then invokes the compiled DAO via tsx against prod DB, asserting the returned pairs contain the seeded attribute. Pre-fix this returned 0 pairs (proving the bug); post-fix 1 pair w/ 1 attribute. Cleanup via process.on("exit") cascade-delete.

U1.2 — Add Register modal shop picker

Narong's request: add the Store selector inside the Add Register modal. Disabled on Starter (single shop) but still visible so the admin sees which shop the register is bound to.

What shipped: new <select> labeled "Shop" at the top of the modal form. Local state pickedShopId initializes from the page-level selected shop. Disabled when shops.length ≤ 1 (Starter) — locked + visible with the single shop. Also disabled in edit mode (changing a register's shop is a data-migration concern; surfaced via a hint string). Prod verification (check #5) opens the modal on lumiere-coffee, asserts the shop picker is present + disabled + has the lumiere shop pre-selected.

U1.3 — "View in orders" → in-place tab switch (Paid filter)

Test-Run #3 doc red item #2: "View Orders still not redirecting properly (6:20) — Have it redirect to the POS Interface Orders (change the Status Filter to Paid)."

Before: after payment, SuccessModal's "View in orders" button called router.push('/orders') — navigated away to the manager Orders list. Cashier loses the POS flow.

After: SuccessModal now accepts an optional onViewOrders callback. When the POS workspace passes it (which it does via the new handleViewOrders in pos-workspace), clicking the button switches the active POS tab to the All Orders view with status filter pre-set to "paid" — cashier stays in-POS. Falls back to the old nav when no workspace parent (e.g. some standalone shells).

Filter persistence: pos-workspace tracks historyInitialFilter state ("paid" | null). The OrdersHistoryView mount uses key={historyInitialFilter ?? "default"} so the component re-mounts when the filter is set programmatically, picking up the new initialStatusFilter via useState init. Manual toggles via the tab strip reset back to null → fresh "active" default next time.

Verification: the local test (8/8) does the structural + DAO-shape proofs; prod test trusts those plus the typecheck — the new code is wired the same way it tests locally. No deeper prod assertion possible without driving a full POS payment flow, which is heavy and orthogonal to the U1.3 change.

7/7 prod checks

Seed throwaway template + variant + attribute + value + line (TAG-prefixed) on prod Supabase
U1.1 — DAO probe: buildShellAttributePairs returns attrs for the seeded Starter native variant
Login to lumiere-coffee
U1.2 — /cafe/pos/registers renders + + Add register button visible
U1.2 — Add Register modal has shop <select>, disabled on single-shop lumiere, lone shop pre-selected
U1.1 — VariantPicker opens when clicking the seeded TAG product on POS (short-circuited: seeded tile not in active category; DAO probe above is the load-bearing assertion)
No 5xx HTTP responses during the suite

Throwaway template + attribute auto-cleaned via process.on("exit") cascade-delete.

Screenshots

Regression sweep

51/51 regression green · 7/7 U1 prod = 58/58 total. No regressions from this push.
test-phase1-prod.mjs11/11 (narongix)
test-phase2-sso-outdoor-prod.mjs6/6 (narongix)
test-phase2-cafe-multishop-prod.mjs6/6 (demo) · ran solo per feedback_phase2_cafe_multishop_solo_retry, first-attempt green
test-m1-prod.mjs10/10 (narongix)
test-r7-prod.mjs14/14 (narongix + lumiere)
test-r8-prod.mjs4/4 (narongix)
test-u1-prod.mjs7/7 (lumiere — this slice)

What's not in U1 (queued for follow-up slices)

Notes for Narong