Final phase of the H4 Product/Variant rework. Drops the 3 R10 plural tables, deletes the legacy
/cafe/settings/modifiers admin route + per-product Attributes section, adds
cafe.products→singular sync hooks so admin edits flow into POS immediately.
f4907bb + nix-cafe b2b8d3d. Migration applied to prod Supabase via
node migrate.js; 3 R10 tables dropped cleanly. Sync hooks end-to-end on prod
verified: createProduct upserts template + variant, updateProduct rename shifts variant,
deleteProduct cleans up; lumiere steady state (25/25) restored post-probe.
A tsx probe invoked the createProduct/updateProduct/deleteProduct DAOs against the live prod DB on lumiere-coffee, exercising each branch of the sync helper:
/cafe/settings/modifiers retired (R10 admin UI no longer renders)/cafe/settings/attributes still renders post-cutoverRaw: 02-prod-sync-probe.json · result.json
| test-phase1-prod.mjs | 11/11 |
| test-phase2-sso-outdoor-prod.mjs | 6/6 |
| test-phase2-cafe-multishop-prod.mjs (parallel on demo) | 6/6 |
| test-m1-prod.mjs | 10/10 |
| test-r7-prod.mjs | 14/14 |
| test-r8-prod.mjs | 4/4 |
| Plus prod feature test (above) | 8/8 |
| Total | 59/59 |
Navigating to /cafe/settings/modifiers on prod returns inconsistent status codes
(observed both 200 with not-found body and 500) — CF Pages + opennextjs Next.js routing for
a deleted segment is non-deterministic. The functional contract (R10 admin UI is no longer
rendered) is honored. The prod test asserts on UI absence rather than status code. If we
later add a global not-found.tsx in app/(authed)/, all paths would
return a deterministic 404 + branded page.
P1 (2026-05-19) — schema (7 new dark tables)
P2+P3 (2026-05-19→20) — Attributes admin UI + R10/products backfill + Cartesian generator
P4 (2026-05-20) — POS read-path cutover + multi-variant backfill (dup-name fix)
P5 (2026-05-20) — Retire R10 + sync hooks
Future H4-adjacent work (not blocking, not in scope):
name_kh columns to product_attribute + product_attribute_value (currently null on the new model)cafe.products → view over (product_template JOIN product_product) + migrate orders.product_id FK to product_product.id. Lets us fully drop cafe.products.product_product row gets its own price + default_code). Currently variants inherit price from the template's first variant.
New:
nix-outdoor-sales-backend/migrations/20260520120000_h4_p5_drop_r10_plural_tables.ts ·
nix-cafe/lib/db/product_template_sync.ts
Modified:
nix-outdoor-sales-backend/migrate.js ·
nix-cafe/lib/db/schema.ts ·
nix-cafe/lib/db/products.ts ·
nix-cafe/app/(authed)/settings/_components/settings-nav.tsx ·
nix-cafe/app/(authed)/products/page.tsx ·
nix-cafe/app/(authed)/products/starter-products-client.tsx
Deleted:
nix-cafe/lib/db/product_attributes.ts ·
nix-cafe/lib/actions/product_attributes.ts ·
nix-cafe/app/(authed)/settings/modifiers/ (page.tsx + modifiers-client.tsx)
Migrations: 1 (3 DROP TABLE in reverse-cascade order, idempotent).