← back to index

V0.4 — Product Variants tab (Odoo-shape list, inline-edit 4 cells)PROD

Second half of the V0.3 split. Narong's Telegram, 2026-05-29 mid-V0.3: split Products from Product Variants — Products page becomes template-only; variants need their own surface because some properties are unique to variants (SKU, barcode, cost, on-hand). Shipped same day after V0.3. Hard constraint: no archive on variants — to retire a variant, admins go into the parent template and delete it.

Summary

Status
9/9 prod · 51/51 regression = 60/60 · shipped
Commit
nix-cafe f4175a0
Files
4 new + 3 modified · ~700 LOC net · no migration · no schema · no backend change
Surfaces
New /cafe/products/variants route. Shared ProductsTabs strip atop both Products + Variants pages. Shared FiltersMenu extracted from V0.3 with a basePath prop so both pages reuse it. Inline-editable cells: Internal Reference, Barcode, Sales Price, Cost. Save-on-blur via updateVariantCellsAction; optimistic UI with revert on failure; lazy initializer per row (avoids the U6 hydration trap). Inventory columns (On Hand / Forecasted / Unit) scaffolded as placeholders for the eventual inventory model.

9/9 prod checks

Login via Commerce SSO
Products page renders tab strip including Variants tab (testids products-tabs, products-tab-products, products-tab-variants)
Click Variants tab → URL /cafe/products/variants → list renders with V0.4 testids (variants-title, variants-search-input, variants-list, per-row testid keyed by variantId)
Search input narrows the visible row count (substring match on name / SKU / barcode / variant values)
Inline-edit priceUsd: focus cell → type new value → Tab blur → action fires → success toast → DB confirms the new value on prod Supabase
Reload /products/variants: cell still shows the new value (Hyperdrive-tx wrap on the DAO works as designed — same pattern as V0.3's listProductTemplatesForAdmin)
Click variant Name link → existing /products/[templateId]/variants/[variantId] detail page loads (click-through preserved)
Restore: revert priceUsd to snapshot via direct UPDATE (DB cleanup)
No 5xx observed during the suite

Screenshots

Regression sweep — 51/51

9/9 V0.4 + 51/51 regression = 60/60 prod tests green on karouna-dev.
test-phase1-prod.mjs11/11 (solo-retry — 3rd consecutive demo-SSO cold-isolate flake; rule validated)
test-phase2-sso-outdoor-prod.mjs6/6
test-m1-prod.mjs10/10
test-r7-prod.mjs14/14
test-r8-prod.mjs4/4
test-phase2-cafe-multishop-prod.mjs (parallel)6/6 (19th consecutive first-attempt parallel green)

Mid-Gate-2 finds

prodSql helper mangled multi-line SQL. First prod-test run exited at the variant-snapshot SELECT — the SQL was split across multiple lines for readability, which broke the bash → node -e "…" → knex.raw escape chain (newlines inside the double-quoted argument terminate the command). Documented gotcha in nix-cafe/AGENTS.md: "the prodSql helper escape limits — for complex payloads, write a temp .mjs script that uses knex directly". For one-shot SELECTs, single-line the SQL.
phase1 demo-SSO cold-isolate flake — 3rd consecutive burn. Sweep hit 9/11 with the same "Navigate to Cafe (SSO works)" check bouncing to /auth/login on demo. Solo retry: 11/11. The feedback_phase1_demo_sso_solo_retry rule saved during V0.3 covers the response shape; no new memo needed.

Architectural notes

Followups