Skip to content

feat(examples): add several products to the cart in one atomic request - #645

Merged
field123 merged 3 commits into
mainfrom
feat/534-bulk-add-to-cart
Oct 1, 2026
Merged

field123 merged 3 commits into
mainfrom
feat/534-bulk-add-to-cart

Conversation

@field123

@field123 field123 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #534.

Search results in examples/core get a checkbox on each card. One action adds every selected product in a single manageCarts call with a BulkAddItemsRequest body and options.add_all_or_nothing: true set explicitly. If the cart refuses any product, it takes none of them.

The lesson is atomicity, not batching. A browser loop of single adds leaves a half-filled cart when item 3 of 4 fails.

What can be selected

  • A standard product is selected as itself.
  • A family card becomes selectable once the shopper chooses every option. The card then adds the child that the options resolve to (the selectedVariant from resolveCardState). The cart rejects a parent product with 422 "is a base product", which was confirmed live.
  • A family card without a full choice of options, and every bundle, has a disabled checkbox. The reason sits beside it, and the checkbox is linked to it with aria-describedby.

Bundles are not this feature. A bundle is one cart line holding a pre-authored configuration. The README says so.

The checkbox sits outside the card's LocaleLink, so clicking it never navigates. Its label carries the product name for screen readers.

Decisions

  • The selection clears on any change to page, query, refinement or sort, and after a successful add. The bar says so. The reason is that a family card keeps its chosen options only while it is on screen. A selection carried across pages would point at cards that no longer exist. Covered by reducer tests, including "returning to the earlier results does not bring the old selection back".
  • Quantity is 1 per product.
  • After an add, the storefront does what the product page does: a toast on success, revalidateTag("cart") in the action, and invalidation of the cart query. A failure shows inline in the bar instead of as a toast, because it lists products.

Failure message, from a live check (integration store, guest cart)

  • A rejected batch answers 400, 404 or 422 with one errors entry per refused product. Each entry carries meta.id, which the generated ResponseErrorItem type declares. Reading back the cart showed it unchanged every time.
  • The shopper sees "Nothing was added to your cart." and one line per refused product with its title, for example "Insufficient stock".
  • Any other failure, such as a 5xx, an empty error body or a thrown request, does not prove the cart is unchanged. For those the shopper is asked to check the cart instead.
  • With add_all_or_nothing: false the cart answers 201 with data, errors and meta, and adds the valid line. The 201 type has no errors field, which is why partial adds stay out of scope.

Also in this PR

  • f92a7fe0: the search page's grid-cols-[1fr_3fr] had no breakpoint, so at phone width the results column ran off-screen. It now stacks below md. This is a separate commit and is pre-existing on main.

Checks

  • vitest run passes: 152 tests, 28 of them new, in bulk-selection.test.ts and bulk-add-to-cart.test.ts.
  • tsc --noEmit is clean.
  • next build succeeds.
  • I walked the flow in the browser against the integration store with next start.

Screenshots

1. Checkboxes, with incomplete families and a bundle disabled

1-checkboxes-with-disabled-cards

2. Four selected, including a resolved variant (Green T-Shirt, lg), with the action enabled

2-four-selected-action-enabled

3. The cart after the add, showing all four

3-cart-after-add-shows-all-four

4. A rejected batch

An out-of-stock variant is refused:

4a-rejected-out-of-stock-variant

A product the cart cannot add is refused. Digital Test was valid and was not added either:

4b-rejected-batch-message

The cart afterwards is unchanged: the same four lines and the same total.

4c-cart-unchanged-after-rejection

5. Nothing selected, with the action disabled (just after a successful add)

5-nothing-selected-action-disabled

6. Phone width

6-phone-width

…n core

The search page's two-column grid had no breakpoint, so at phone width the
results column ran off the right edge of the viewport.
…t from search results

Search result cards get a checkbox, and one action adds every selected product
in a single Cart API request with options.add_all_or_nothing set to true. If
the cart refuses any product, it takes none, and the shopper is told which
product was refused and why.

A family card is selectable once its options resolve to a child product, which
is what gets added. Bundles and incomplete families are disabled with the
reason shown. The selection clears on any change to the results.

Closes #534
…atch

A server error or an empty error body does not prove the cart is unchanged,
so the shopper is asked to check the cart instead. A successful add now
clears only the cards it sent, so a card ticked while the request was in
flight stays selected. The README names Catalog Search as a requirement.
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
composable-frontend-core Ready Ready Preview Oct 1, 2026 5:31pm UTC
5 Skipped Deployments
Project Deployment Actions Updated
commerce-essentials Ignored Ignored Preview Oct 1, 2026 5:31pm UTC
composable-frontend-algolia Ignored Ignored Oct 1, 2026 5:31pm UTC
composable-frontend-docs Ignored Ignored Preview Oct 1, 2026 5:31pm UTC
composable-frontend-simple Ignored Ignored Preview Oct 1, 2026 5:31pm UTC
composable-frontend-subscriptions Ignored Ignored Preview Oct 1, 2026 5:31pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 1, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0288b96

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@field123
field123 merged commit 1f1f393 into main Oct 1, 2026
9 checks passed
@field123
field123 deleted the feat/534-bulk-add-to-cart branch October 1, 2026 17:42

This branch was successfully deployed

1 active deployment
Preview – composable-frontend-core — 0288b965 Deployed Oct 1, 2026 by vercel[bot]
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.

Add several products to the cart in one atomic request

1 participant