Appearance
Cart support guide
Diagnostic and troubleshooting reference for the cart drawer + cart page + checkout bridge — elevated discount UX, three-tier upsell cascade (metafield → API → collection), free-shipping progress bar, inline cart attributes (delivery date, marketing/SMS consent, gift wrapping, free-form cart-attribute blocks), optimistic quantity update with rollback, pre-emptive inventory cap, OOS-during-checkout banner, single-code discount semantics, and sticky mobile checkout footer.
For when something does not work the way it should. The merchant-facing setup guide is at Cart customization guide.
FAQ
Q1: Why isn't my discount applying?
Check the discount code is enabled in Shopify admin → Discounts. The theme calls Shopify's /discount/{code} endpoint; it sets a discount cookie if the code is valid + active. If you see "That code isn't valid", the code itself isn't matching Shopify's eligibility rules (date range, customer eligibility, minimum order, etc.) — verify in Shopify admin.
To diagnose:
- Visit
/discount/{your_code}directly in the browser. Does it redirect to/cart? If no — code isn't valid in Shopify admin. - Open DevTools → Network → click Apply. Does the request hit
/discount/{code}? Does the subsequent/cart.jsGET showcart_level_discount_applicationspopulated? If no — discount eligibility issue (Shopify side). - Run the console diagnostic snippet below. Inspect
cart.cart_level_discount_applications. If empty after a successful redirect — Shopify rejected eligibility silently.
Q2: Why is my upsell empty?
The cart upsell uses a 3-tier cascade:
- Per-product
product.metafields.uisce_cart.cross_sells(list of handles). - Shopify Recommendations API (
intent=complementary). - Merchant-set
upsell_collectionsetting on cart section.
If all 3 sources yield nothing, the upsell-grid stays empty. Check:
- Is the
upsell_collectionsetting populated? (Theme editor → Cart section → Upsell →upsell_collection) - Do your products have related/complementary items in Shopify admin → Products → Recommendations?
- Have you created the
uisce_cart.cross_sellsmetafield definition? (Admin → Settings → Custom data → Products)
You can override the cascade via upsell_source_tier setting (cascade / metafield_only / api_only / collection_only).
Q3: Why is the checkout button disabled?
Two possible gates:
- Policy-acknowledgement checkbox is unchecked (existing behavior — must check Terms & Conditions before checkout).
- OOS-during-checkout banner is active (an item in cart became unavailable mid-session). Click the OOS banner's "Try again" or remove the offending line.
Use the diagnostic snippet to read OOS banner mounted + OOS banner visible. If both true, the OOS banner is the gate.
Q4: What does "Only N available" mean?
The shopper tried to bump a quantity past inventory. Shopify's inventory tracking has determined N units are in stock. The theme caps the input at N + shows the inline message.
If you see this for an item that should have more inventory:
- Double-check
inventory_tracked+inventory_quantityon the variant in Shopify admin. - Confirm
inventory_policy = deny(otherwise Shopify allows backorder; the cap doesn't apply). - Run the diagnostic snippet → inspect
inventoryAttrsto see whatdata-inventory-quantity+data-inventory-policyare rendered on each line.
Q5: Why does the optimistic quantity update sometimes "flash back"?
Two cases:
- The mutation failed on the server side (rare; usually inventory drifted between cart-render and click — see Q4).
- The latest-wins guard rejected the response because a newer mutation superseded it (rapid
+/+/+/+clicks coalesce to a single mutation; intermediate UI states are discarded).
Both are expected behavior. Use the diagnostic snippet to read mutationTokens count + debounceTimers count — non-zero values mean a mutation cycle is in flight.
Q6: How do I customize the gift-wrap label?
The simple enable_gift_wrapping toggle uses a fixed label from your locale file ({{ 'cart.gift_wrap.label' | t }}). To customize:
- Disable
enable_gift_wrappingon the cart section. - Add a
cart-attributeblock. - Set
key=gift_wrapping,input_type=checkbox, and your customlabel.
Multiple cart-attribute blocks can be stacked for richer gift-wrap variants (e.g., gift_wrap_message + gift_recipient_name).
Q7: How do I add a per-line attribute (vs cart-level)?
- Cart-level attributes post as
attributes[*](order-level — visible on order confirmation under "Notes"). - Per-line attributes post as
properties[*]and bind to a specific line item (e.g., per-recipient name on a multi-gift order).
The free-form cart-attribute block defaults to cart-level. To make per-line, place the block inside a per-line section. (Note: 06.6 v1 ships cart-level for cart-attribute; per-line variants are the niche-specific blocks like cart-service-location for Uisce.)
Q8: How does SMS consent work?
Setting enable_sms_consent=true renders a checkbox that posts as attributes[sms_consent]=true if the shopper checks it. You connect this to your SMS marketing app (e.g., Klaviyo, Postscript) via the Shopify admin app integration — the theme just captures consent.
Same applies to enable_marketing_consent for email — posts as attributes[marketing_consent].
Q9: Why is delivery date required on Uisce only?
Uisce ships the Services niche; merchants on Uisce sell appointments + bookings, where a delivery date IS the service date. So templates/cart.uisce.json ships with delivery_date_required=true by default. Other presets default to OFF (you can toggle ON if your products require a delivery date — e.g., live-plant Talamh shops or perishable Neart products).
Q10: Why is the cart-drawer slide direction wrong in Arabic?
Verify the storefront has Arabic published (Shopify admin → Settings → Languages → Add language → Arabic). The theme uses inset-inline-end + a [dir='rtl'] keyframe override to slide from inline-start in RTL. If the slide is still wrong:
- Check
<html lang>and<html dir>are correctly set on the page (use the diagnostic snippet'scart.tokenline — it confirmswindow.CartAPIis reachable; fordir, inspectdocument.documentElement.dir). - Confirm the storefront URL has the locale path
/ar/cart(or your store's RTL locale path) — the locale routing must be active. - Report the dev-store URL + Shopify-admin language settings + locale-routing path to support.
Decision tree: "Why is X not working?"
Discount-code not applying
- Visit
/discount/{your_code}directly in browser. Does it redirect to/cart? If no — code isn't valid in Shopify admin. - Open DevTools → Network → click Apply. Does the request hit
/discount/{code}? Does the subsequent/cart.jsGET showcart_level_discount_applicationspopulated? If no — discount eligibility issue (Shopify side). - Run console snippet (below). Inspect
cart.cart_level_discount_applications.size. If 0 after a successful redirect — Shopify rejected eligibility silently.
Upsell cards not showing
- Open
/recommendations/products.json?product_id={any_product_id}&intent=complementary&limit=4directly. Does it return products? If no — Shopify hasn't computed complementary recommendations yet (requires sales history). - Run console snippet. Inspect
upsell tier1 handles(per-product metafield) +upsell tier3 handle(fallback collection). Tier 1 yielded? Tier 3 set? - Verify
upsell_collectionsetting is populated on the cart section. - If
upsell_source_tier=metafield_onlyis set and no products haveuisce_cart.cross_sellspopulated, the upsell will stay empty by design — switch back tocascadeor populate the metafield.
OOS-during-checkout banner not appearing
- Verify a product variant has
inventory_quantity=0 + inventory_management=shopify + inventory_policy=deny. Withoutpolicy=deny, Shopify allows backorder. - Run console snippet. After clicking Check out, inspect
OOS banner mountedandOOS banner visible. Ifmounted=false, the banner element didn't render — confirm the cart-page has[data-checkout-bridge-banner]in the DOM. - Inspect
checkout-retry-counterin sessionStorage. Ifnullinitially, the bridge hasn't run yet; click Check out to trigger it.
Optimistic UI rolls back unexpectedly
- Run console snippet to read
mutationTokens count+debounceTimers count. - Check Network tab for failed
/cart/change.jsPOST. Inspect 422 description — should match the inventory-cap regex (/^You can only add (\d+)/). - If the rollback only happens on rapid
+/+/+/+clicks, this is the latest-wins guard working as designed (intermediate UI states are discarded).
Cart-attribute not posting to order
- Confirm the
cart-attributeblock is inside acart-attributesblock container (or the cart section directly). - Inspect the rendered
<input name="attributes[your_key]">— confirm thenameattribute isattributes[*](cart-level) orproperties[*](per-line). - Run the diagnostic snippet → inspect
cart.attributes. Should reflect the field's value after a/cart/update.jsPOST. - If
cart.attributesis{}, the field's value didn't post — check that the form's<input type="hidden" name="form_type" value="cart-attributes">(or equivalent) is present.
Sticky mobile checkout footer not appearing
- Confirm viewport is
< 1024 px(DevTools device emulation). - Run diagnostic snippet → inspect
sticky-footer present. Iffalse, the[data-sticky-mobile-checkout]element isn't in the DOM — confirm cart.liquid renders the sticky-footer markup. - Confirm the OOS banner is NOT active (the sticky footer is suppressed when OOS banner is visible — by design).
- iOS Safari sometimes mis-computes
env(safe-area-inset-bottom)— try toggling between portrait + landscape orientation.
Shopify checkout rejection mappings
When Shopify checkout itself rejects a cart at /checkout commit time (after the theme's cart layer), Shopify shows error messages on the checkout page. Common rejections + their cart-side root causes:
| Shopify message | Likely cause | Fix |
|---|---|---|
| "Some items are no longer available" | OOS variant in cart at /checkout commit | Theme banner should have caught this PRE-redirect; if it didn't, check OOS pre-validate flow + cart.items[N].available + variant_available. Run the console snippet to inspect inventory state. |
| "Discount code [X] is no longer valid" | Discount expired / max-uses-reached / customer-ineligible | Verify in Shopify admin → Discounts. Check date range, customer eligibility, minimum order requirements. |
| "Cart is empty" on /checkout | Cart cleared post-add (race) | Network race during ATC; rare; refresh page. Check the diagnostic snippet's cart.item_count — should match what the shopper sees. |
| "Inventory is no longer sufficient" | Inventory drifted after cart-render but before checkout | Optimistic UI should rollback to cap; if checkout still rejects, the cap drifted further. Refresh cart and retry. |
| "Required attribute X is missing" | cart-attribute required=true field not filled | Find the missing field on cart page; fill before checkout. The theme blocks checkout client-side via required validator on the input. |
Console diagnostic snippet
Paste this into your browser DevTools Console on /cart or any page with the cart-drawer mounted. Produces a single console.table of cart state — safe, read-only, and adds zero theme code.
javascript
(async function () {
const cart = await window.CartAPI.getCart();
const drawer = document.querySelector('cart-drawer');
const page = document.querySelector('cart-page');
const upsellSlot = document.querySelector('[data-cart-upsell-slot]');
const oosBanner = document.querySelector('[data-checkout-bridge-banner]');
const stickyFooter = document.querySelector('[data-sticky-mobile-checkout]');
const mutationTokens = drawer && drawer._mutationTokens ? Array.from(drawer._mutationTokens.entries()) : [];
const debounceTimers = drawer && drawer._debounceTimers ? Array.from(drawer._debounceTimers.entries()) : [];
const inventoryAttrs = Array.from(document.querySelectorAll('[data-inventory-quantity]')).map((el) => ({
key: el.dataset.lineKey,
inventoryQuantity: el.dataset.inventoryQuantity,
inventoryPolicy: el.dataset.inventoryPolicy,
}));
const upsellTier1 = upsellSlot ? upsellSlot.dataset.tier1Handles : null;
const upsellTier3 = document.querySelector('[data-cart-upsell-cards]')?.dataset.tier3Handle;
const retryCounter = sessionStorage.getItem('uisce.state.v1.checkout-bridge-retry-counter');
const freeShippingDebounce = sessionStorage.getItem('uisce.state.v1.free-shipping-announce');
console.table({
'cart.token': cart.token,
'cart.item_count': cart.item_count,
'cart.total_price (cents)': cart.total_price,
'cart.note (length)': (cart.note || '').length,
'cart.attributes': JSON.stringify(cart.attributes || {}),
'cart.cart_level_discount_applications.size': (cart.cart_level_discount_applications || []).length,
'drawer mounted': drawer !== null,
'page mounted': page !== null,
'upsell-slot present': upsellSlot !== null,
'upsell tier1 handles': upsellTier1,
'upsell tier3 handle': upsellTier3,
'OOS banner mounted': oosBanner !== null,
'OOS banner visible': oosBanner && !oosBanner.hidden,
'sticky-footer present': stickyFooter !== null,
'inventoryAttrs count': inventoryAttrs.length,
'mutationTokens count': mutationTokens.length,
'debounceTimers count': debounceTimers.length,
'checkout-retry-counter': retryCounter,
'free-shipping-debounce': freeShippingDebounce,
'window.uisceStrings size': Object.keys(window.uisceStrings || {}).length,
});
console.log('Full cart:', cart);
console.log('Inventory attrs:', inventoryAttrs);
console.log('Mutation tokens:', mutationTokens);
})();This dumps cart state + drawer/page mount status + upsell tier resolution + OOS banner state + inventory cap state + attribute state + sessionStorage retry counters + uisceStrings registry size. Send the table to support.
What to look for in the output:
drawer mounted === trueANDpage mounted === true(on/cart) — both Custom Elements connected.cart.item_count > 0— cart has line items (otherwise upsell cascades to fallback collection, OOS banner is moot).upsell-slot present === trueifenable_upsell=true; iffalse, upsell setting is OFF.upsell tier1 handlespopulated whenuisce_cart.cross_sellsmetafield is set on a line-item product.upsell tier3 handleset whenupsell_collectionsetting is populated.OOS banner mounted === truealways (the element exists hidden);visibleflips totruewhen an item is no longer available.sticky-footer present === trueon mobile (< 1024 px);falseon desktop (by design).inventoryAttrs count > 0for any line-item withinventory_tracked + policy=deny.mutationTokens count+debounceTimers count— non-zero means a mutation cycle is in flight; should drop to 0 once the network settles.checkout-retry-counterincrements on each Check-out failure (max 3); resets tonullafter successful redirect.cart.cart_level_discount_applications.size > 0when a discount is applied.
Embedded diagnostic tool: A full embedded diagnostic UI is planned for a future update. The console snippet above provides the core diagnostic value.
Curl cheat-sheet
Use these one-liners to verify your live store's cart flow. Replace {store} with your storefront domain (e.g., uisce.sionnach.solutions) and capture the storefront-password cookie first if your store is a dev store.
For dev stores, capture the auth cookie via:
bash
curl -sI "https://{store}/password" -d "form_type=storefront_password&password={password}" \
-L -c cookies.txt | head -5
# Then add `-b cookies.txt` to all subsequent curl commands belowFor live production stores (no password protection), no cookie is needed.
Get cart state
bash
curl -s 'https://{store}/cart.js' | jq .Expected: JSON with token, note, attributes, total_price, item_count, items[], cart_level_discount_applications[].
Get cart with bundled section refresh
bash
curl -s 'https://{store}/cart.js?sections=cart,cart-drawer§ions_url=/cart' | jq .Expected: JSON with sections key containing rendered HTML for cart + cart-drawer (the bundled-section-refresh pattern used by the optimistic-UI flow).
Apply discount via direct redirect (verifies Shopify accepts the code)
bash
curl -sILw '%{http_code} %{redirect_url}\n' 'https://{store}/discount/WELCOME20'Expected: HTTP 302 + Location header redirecting to /cart.
Change item quantity
bash
curl -s -X POST 'https://{store}/cart/change.js' \
-H 'Content-Type: application/json' \
-d '{"line": 1, "quantity": 3}' | jq .Expected: JSON with the updated cart state. If line 1 has inventory < 3, the response will include description with the inventory-cap message.
Update cart attributes + note
bash
curl -s -X POST 'https://{store}/cart/update.js' \
-H 'Content-Type: application/json' \
-d '{"note": "Please ring doorbell", "attributes": {"delivery_date": "2026-05-01", "gift_wrapping": "true"}}' | jq .Expected: JSON with note + attributes reflecting the posted values.
Apply discount via /cart/update.js (alternative to redirect)
bash
curl -s -X POST 'https://{store}/cart/update.js' \
-H 'Content-Type: application/json' \
-d '{"discount_code": "WELCOME20"}' | jq .Expected: JSON with cart_level_discount_applications[] populated if code is valid.
Remove the currently-applied discount
bash
curl -s -X POST 'https://{store}/cart/update.js' \
-H 'Content-Type: application/json' \
-d '{"discount_code": ""}' | jq .Expected: JSON with cart_level_discount_applications[] empty.
Get product recommendations (tier 2 of cascade)
bash
curl -s 'https://{store}/recommendations/products.json?product_id={id}&intent=complementary&limit=4' | jq .Expected: JSON with products[] array of recommended products. Empty when Shopify has not yet computed complementary recommendations (requires sales history).
Verify metafield definition exists (Admin API)
bash
curl -s 'https://{store-myshopify-domain}/admin/api/2024-10/metafield_definitions.json?owner_resource=product&namespace=uisce_cart' \
-H "X-Shopify-Access-Token: $UISCE_TOKEN" | jq .Expected: JSON with metafield_definitions[] containing the uisce_cart.cross_sells definition (and any other uisce_cart.* definitions you have created).
Empirical inventory check
bash
curl -s 'https://{store}/products/{handle}.json' | jq '.product.variants[] | {id, sku, inventory_quantity, inventory_management, inventory_policy}'Expected: per-variant inventory state. Use this to confirm inventory_policy=deny + inventory_quantity matches what the cart cap is showing.
Verify cart attributes post correctly (round-trip)
bash
# Set
curl -s -X POST 'https://{store}/cart/update.js' \
-H 'Content-Type: application/json' \
-d '{"attributes": {"test_key": "test_value"}}' | jq '.attributes'
# Read back
curl -s 'https://{store}/cart.js' | jq '.attributes'Expected: both responses show {"test_key": "test_value"}.
Need more help?
- For deep diagnostic beyond what the console snippet captures, a full embedded diagnostic UI is planned for a future update.
- File a GitHub issue with the console-snippet output + browser/OS + preset.
- See the cart customization guide for configuration walkthrough.
Related docs
- Cart customization guide — settings catalog + per-preset block composition + metafield walkthrough
- PLP support — Phase 06.5 collection page support
- PDP support — Phase 06.4 product page support
- Header support — Phase 06.3 header support
- Translations (50 locales)
- Support form — submit a ticket if the above does not resolve your issue