← All tasks

H4-P5 — Retire R10 + cafe.products sync hooks PROD · GATE 2

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.

🎉 H4 arc complete. Five slices (P1 schema → P2+P3 admin/backfill → P4 read-path → P5 retire) shipped 2026-05-19→20. The NIX Cafe product model has been migrated from R10 plural ("product_attributes" with per-product assignments) to the Odoo singular shape ("product_template" with template-level attribute lines + product_product variants + Cartesian generator). Get-coffee's 17 dup-named products are now correctly materialized as 9 templates + 17 variants (preserved SKUs that would otherwise have collapsed). Admin manages attributes exclusively at /cafe/settings/attributes.
8/8 prod test · 51/51 regression green · 59/59 total. Backend 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.

Prod DB state

Before H4-P5: After H4-P5: ────────────── ──────────── cafe.product_attributes ✓ cafe.product_attributes ✗ DROPPED cafe.product_attribute_values ✓ cafe.product_attribute_values ✗ DROPPED cafe.product_attribute_assignments ✓ cafe.product_attribute_assignments ✗ DROPPED cafe.product_attribute ✓ cafe.product_attribute ✓ cafe.product_attribute_value ✓ cafe.product_attribute_value ✓ cafe.product_template ✓ cafe.product_template ✓ cafe.product_product ✓ cafe.product_product ✓ cafe.product_template_attribute_line ✓ cafe.product_template_attribute_line_value_rel ✓ cafe.product_variant_combo ✓ cafe.products ✓ cafe.products ✓ (still authoritative)

Sync hook end-to-end on prod

A tsx probe invoked the createProduct/updateProduct/deleteProduct DAOs against the live prod DB on lumiere-coffee, exercising each branch of the sync helper:

Probe sequence on prod lumiere-coffee ───────────────────────────────────── 1. createProduct({name: "H4P5PROBE_P5SyncSmoke", priceUsd: "3.25"}) → cafe.product_template "H4P5PROBE_P5SyncSmoke" created → cafe.product_product with cafe_product_id back-ref + priceUsd=3.25 created ✓ create sync 2. updateProduct(price="4.00") → product_product.priceUsd updated to 4.00 ✓ price sync 3. updateProduct(name="H4P5PROBE_P5SyncRenamed") → new template "H4P5PROBE_P5SyncRenamed" created → product_product.productTmplId migrated to new template → old empty template "H4P5PROBE_P5SyncSmoke" deleted ✓ rename sync + empty-template cleanup 4. deleteProduct(...) → product_product row deleted (BEFORE cafe.products delete to avoid FK SET NULL) → renamed template (now empty) deleted ✓ delete sync + empty-template cleanup Steady-state row count post-probe: lumiere=25 templates+25 variants (unchanged from H4-P4).

Screenshots

/cafe/settings/attributes post-cutover
01 · /cafe/settings/attributes renders cleanly post-cutover

Checks — 8/8 prod

Raw: 02-prod-sync-probe.json · result.json

51/51 regression green — no behavioural regressions from this push.
test-phase1-prod.mjs11/11
test-phase2-sso-outdoor-prod.mjs6/6
test-phase2-cafe-multishop-prod.mjs (parallel on demo)6/6
test-m1-prod.mjs10/10
test-r7-prod.mjs14/14
test-r8-prod.mjs4/4
Plus prod feature test (above)8/8
Total59/59

Minor finding (cosmetic, accepted)

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.

H4 arc — done

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):

Files

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).