Skip to content

fix(products): variant SKU collisions & save-flow error UX - #91

Merged
bighadj22 merged 1 commit into
mainfrom
fix/variant-sku-errors
Sep 6, 2026
Merged

fix(products): variant SKU collisions & save-flow error UX#91
bighadj22 merged 1 commit into
mainfrom
fix/variant-sku-errors

Conversation

@bighadj22

Copy link
Copy Markdown
Owner

Summary

A merchant editing a product hit POST /api/products/:id/variants returning 500 "An unexpected error occurred" (production requestId aa6d6370). Root cause traced through the server log: a raw D1 UNIQUE constraint on product_variants.sku — the variant save carried a SKU that already existed. Products already had a friendly 409 for this; variants never did. This PR fixes the crash and the surrounding error-UX gaps in the product save flow.

Why the tests missed it: no test anywhere covered variant SKU uniqueness — mock-based tests cannot hit real SQL constraints, and the previous real-D1 e2e suites covered orders/stock/money paths, not variant CRUD.

Fixes

Server — friendly 409 instead of raw 500

  • variants/queries.ts wraps the shared createVariant/updateVariant with a SKU availability pre-check, plus a defensive unique-violation mapping for the check→write race
  • A collision now returns 409 DUPLICATE_SKU carrying the offending SKU — the same treatment product creation already had
  • Regression-locked on real D1 (variants.sku-e2e, 4 tests): create-dup → 409, update-rename-dup → 409, self-rename → OK, fresh SKU → OK with opening-stock ledger intact
  • Verified through the full HTTP stack: the exact endpoint that 500'd now returns 409 with the SKU named

Dashboard — errors that say what actually happened

Gap Before Now
Duplicate SKU "Something went wrong. Please try again." SKU "X" is already used by another variant
Server validation detail (API sends full field list) discarded toast lists every failing field: price: must be an int · sku: required
500s no follow-up message carries the requestId — "if it persists, quote reference {id} to support"
Multi-request save failure (product → N variants → N images) one generic blob names the failing step: "Variant أبيض / 128GB could not be saved — …"
Double-create retry trap — createProduct succeeds, a variant fails, merchant clicks Save again silently creates a duplicate product form redirects to the edit page with a toast — retry becomes an update, never a second product
Client validation name/price-presence/SKU-presence only price must be a whole number; duplicate SKUs across variant rows caught before any request

7 new locale keys × ar/en/fr (i18n parity guard passes).

Test plan

  • New real-D1 suite variants.sku-e2e (4 tests) — includes the exact failing endpoint shape
  • Mock fixtures updated for the new SKU pre-check read
  • cod-server: 118 files / 1709 tests green, tsc --noEmit clean
  • cod-client-astro: 142 tests green, typecheck clean, three-locale parity green
  • Deployed and smoke-verified before opening this PR (original 500 endpoint → 409 with SKU)

Follow-up from #90 still open (cosmetic): storefront rates map displays fees for disabled delivery types — server refuses those orders, theme01 shows the fee before rejection.

Root cause (from production requestId aa6d6370): POST
/api/products/:id/variants crashed with a raw D1 UNIQUE constraint on
product_variants.sku → HTTP 500 → generic toast. No test anywhere
covered variant SKU uniqueness — mock tests cannot hit real SQL
constraints, and the prior e2e suites covered orders/stock/money only.

Server
- variants/queries.ts now wraps shared createVariant/updateVariant
  with a SKU availability pre-check (and a defensive unique-violation
  mapping for the check→write race). A collision returns 409
  DUPLICATE_SKU carrying the offending SKU — the same friendly
  treatment product creation already had.
- Regression-locked on real D1 (variants.sku-e2e): create-dup,
  update-dup, self-rename OK, fresh SKU OK + opening-stock ledger
  intact. The exact failing HTTP call now returns 409 with the SKU.
- variants/CONTEXT.md: "SKU collisions crash late" is no longer true.

Dashboard
- productErrorMessage surfaces real API detail instead of collapsing
  everything to "Something went wrong": DUPLICATE_SKU names the SKU,
  VALIDATION_FAILED lists every failing field/path, 500s carry the
  requestId so support can correlate with server logs.
- ProductForm save flow: failures name the failing step ("Variant
  أبيض / 128GB could not be saved — …") instead of one generic blob.
- Double-create retry trap closed: when a new product saves but a
  variant/image step fails, the form redirects to the edit page with a
  toast — retry becomes an update, never a duplicate product.
- Client validation hardened: price must be a whole number; duplicate
  SKUs across variant rows are caught before any request is sent.
- 7 new locale keys × ar/en/fr (i18n parity guard passes).

Verified: cod-server 118 files / 1709 tests green + tsc clean;
cod-client-astro 142 tests + typecheck + i18n parity green; deployed
and smoke-verified (the original 500 endpoint now 409).
@bighadj22
bighadj22 merged commit 2ae692f into main Sep 6, 2026
3 checks passed
@bighadj22
bighadj22 deleted the fix/variant-sku-errors branch September 6, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant