Skip to content

feat(cart): add weighted products and surface server error messages #55 - #60

Open
rsdouglas wants to merge 3 commits into
yashiels:mainfrom
rsdouglas:feat/weighted-items
Open

rsdouglas wants to merge 3 commits into
yashiels:mainfrom
rsdouglas:feat/weighted-items

Conversation

@rsdouglas

Copy link
Copy Markdown

Addresses items 1 and 2 of #55.

Weighted products

Per-kg butchery lines failed on /api/v3/carts/update with Missing weight range. Captured from lines the app wrote to a live cart:

  • selectedWeightRange is { minimumValue, maximumValue } in grams, taken from the product's variableWeightOptions.
  • Line price is the per-kg price at the range maximum, rounded down: R124.99/kg × 0.5kg → 6249, R99.99/kg × 0.9kg → 8999, R229.99/kg × 1.1kg → 25298.

Changes:

  • add <id> [qty] --weight <min-max>. Defaults to the product's only range; errors with the list when there are several.
  • An add merges into an existing line only when the range matches; otherwise it adds a new line.
  • Quantity changes on existing weighted lines are no longer refused.
  • Search-resolved adds re-fetch product details, since the mapped search result drops the weight fields.
  • Products flagged as weighted but without variableWeightOptions are still refused.

Server error messages

safeErr() now appends the server's JSON message field (only that field, single line, truncated to 160 chars). For example, an opaque HTTP 498 Unknown now reads HTTP 498 Unknown: The item is currently out of stock and cannot be added to your basket.

Testing

  • make ci passes: tsc --noEmit, plus 434 vitest tests including new ones for range selection, pricing, line merging and safeErr.
  • Live: added a weighted line (Simple Truth BBQ Espetadas, 800–1000g) to a 60-minute cart; the line matched the app-written shape and price.

Not included: items 3 (cart cap message) and 4 (stored mobile fallback) from #55.

…ashiels#55

Weighted (per-kg) butchery lines were rejected by /carts/update with
"Missing weight range". New lines now carry selectedWeightRange
({minimumValue, maximumValue} in grams, from the product's
variableWeightOptions) and are priced at the range maximum, rounded
down, which matches what the app writes (R229.99/kg x 1.1kg -> 25298).

- add --weight <min-max>; defaults to the product's only range and
  requires a choice when there are several
- adds merge into an existing line only when the range matches
- quantity changes on existing weighted lines are no longer refused
- safeErr surfaces the server's JSON message field (only that field),
  e.g. HTTP 498 now reads "The item is currently out of stock..."
@rsdouglas
rsdouglas requested a review from yashiels as a code owner October 26, 2026 14:58

@sentinel-skyner sentinel-skyner Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Sentinel review

The weighted-range confirm path, reconcile comparison, and server-message sanitization need fixes before this lands.

2 inline comments below.

Other notes:

  • src/lib/cart-mutate.ts — modeSets aggregates quantity by productId only, so for weighted products with different selectedWeightRange a server change that collapses or swaps ranges still matches the intended totals and snapshotEquivalent reports success, leaving the wrong pack range/price.

Automated review via deepseek:deepseek-flash + deepseek:deepseek-v4-pro verifier. Findings only — not an approval. Push new commits to re-review.

Comment thread src/lib/cart-mutate.ts Resolved
Comment thread src/commands/add.ts Resolved
- --confirm no longer resolves the weight range: the plan already holds
  it, so a multi-range product confirms without repeating --weight
- reconcile keys weighted lines by product and range, so a server swap
  or collapse of ranges is divergent instead of a matching total
- server messages have control characters stripped before display
Comment thread src/lib/cart-mutate.ts Resolved
Comment thread src/commands/add.ts Resolved
@rsdouglas

Copy link
Copy Markdown
Author

On the modeSets note in the review summary: fixed in 0c03034. Reconcile now keys lines by productId@min-max for weighted lines (unweighted lines are unchanged), so a server that swaps or collapses ranges no longer matches the intended per-product total. Test added: the server moves both lines onto one range. It now reports divergent, and it was success before the change.

Codex review on yashiels#60:
- remove takes --weight; with one product in several ranges it refuses
  without one, and only touches the chosen range's lines (the gate also
  refuses an unscoped weighted remove)
- the preview -> confirm fingerprint includes each line's range, so a
  range change since the preview refuses instead of merging elsewhere
- product lookup by id uses the exact id, never another display variant
@rsdouglas

Copy link
Copy Markdown
Author

Second review pass: fixed in 2e566c0.

  • Weighted remove: remove now takes --weight. If the product is in the cart in several ranges, it refuses without one, and it only touches the chosen range. Before, it decremented whichever line came first, or dropped every range. The gate in applyMutation also refuses an unscoped weighted remove.
  • Preview → confirm fingerprint: it now includes each line's range, so a range change since the preview raises PlanStaleError instead of merging the add into a different line.
  • Lookup by id: it uses an exact id match instead of details[0], because showAllDisplayVariants can return other variants first.

The new tests cover the first two and fail on the previous commit. The exact-id fix has no test: resolvePick would need a fake API that this suite doesn't have yet.

Not applied here: unaddressed fresh carts reading as divergent. That is #61.

@sentinel-skyner sentinel-skyner Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Sentinel review

Weighted add support works, but confirm fingerprint and partial weighted removal need attention.

Other notes:

  • src/lib/cart-mutate.ts — The confirm fingerprint only includes id, productId, quantity, and price. Because cart.add merge now depends on selectedWeightRange, a concurrent change that only alters a line's range won't be detected, so confirm can merge or append against cart state the user never previewed.
  • src/lib/cart-mutate.ts — cart.remove still groups matches only by productId, so a partial remove of a product with multiple weighted ranges decrements whichever line appears first. Since weighted lines are no longer refused, this can remove the wrong pack range. Partial weighted removals need a range selection or an explicit rejection.

Automated review via cursorbridge:gpt-5.6-sol + deepseek:deepseek-v4-pro verifier. Findings only — not an approval. Push new commits to re-review.

@sentinel-skyner sentinel-skyner Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Sentinel review

No issues found.

Automated review via openrouter:deepseek/deepseek-chat-v3.1 + deepseek:deepseek-v4-pro verifier. Findings only — not an approval. Push new commits to re-review.

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