Three structural @lit/context providers. A consumer must be a DOM descendant of its
provider or it silently renders nothing.
| Context | Provided by | Carries |
|---|---|---|
storeContext | <storesynk-store> | token, domain, apiUrl, appNamespace (the Storesynk app’s concrete app--<numeric-app-id> metafield namespace from the app-namespace attribute - Storefront queries can’t use the $app: shorthand; read by app-metafield consumers like <storesynk-bundle>; '' = unset), effective language + country (a persisted buyer choice wins over the attributes), shop (moneyFormat/currency/country), cart (lean store), richCart (observable), client, addToCart(), plus the Markets fields: localization (available countries/currencies/languages, lazily loaded), ensureLocalization(), setLocale({country?,language?}), formatMoney(money) (context-aware - shop currency via moneyFormat, foreign currencies via Intl), and revalidateAdopted. Customer-accounts module (present only when customer-client-id is set): customer (the logged-in customer - email/firstName/lastName/displayName/tags; null = anonymous - read by the show-customer-* displayers and <storesynk-customer-pricing>), customerStatus ('anonymous' | 'pending' | 'loggedIn' - 'pending' holds SSR-stamped member state while the async session restore runs so it never flashes anonymous), accountUrl + ensureAccountUrl() (lazily resolved account destination for <account-link>) |
productContext | <storesynk-product> | the normalized product + runtime selection state: variants, media, options, metafields, selectedVariant, selectedOptions, activeMediaIndex, quantity, selectedSellingPlanId |
cartLineContext | <cart-line> | one cart line (merchandise, cost, quantity) |
discountContext | <cart-discount> | one discount: code, label, amount - read by show-discount-amount |
collectionContext | <storesynk-collection> | (optional collection module) products, facets (live counts), loading, hasNextPage, count, sortKey/reverse, activeFilters, and the methods toggleFilter/setFilters/clearFilters/setPrice/setSort/loadMore - read by product-list + every filter/sort/paging control |
bundleContext | <bundle-offer> | (optional bundles module) one live bundle: entry (title, percentage, the $app:bundle metaobject identity), members (resolved members - product, selected variant id, per-set quantity, unit price, availability, allowedVariantIds - the merchant-allowed variants, [] = all), pricing (total, discountedTotal, savings, percentage, currencyCode) - read by the show-bundle-* displayers, bundle-member-list, and add-bundle-to-cart. Re-provided (re-keyed members + recomputed pricing) whenever a member card’s variant selection changes |
bundleMemberContext | <bundle-member-list> (attached per member-card clone) | (optional bundles module) one resolved bundle member (per-set quantity, unit price, selected variant, allowed variants) - read by show-member-quantity / show-member-subtotal inside that card; bundle-specific data the card’s own productContext can’t carry |
volumeDiscountContext | <storesynk-volume-discount> | (optional volume-discount module) the one live volume discount: entry (title, tiers, the $app:volume_discount metaobject identity), tiers (resolved rows - quantity, title/label/badge templates, pricing recomputed from the current selected variant so rows track the page’s selection), selectedIndex - read by show-volume-discount-title, volume-tier-list, and add-volume-to-cart. Re-provided when the selected variant’s price or the chosen tier changes |
volumeTierContext | <volume-tier-list> (attached per tier-row clone) | (optional volume-discount module) one tier row: tier (resolved quantity/title/label/badge + pricing), index, selected - read by the show-tier-* displayers inside that row and drives the <volume-tier> radio itself |
customerPricingContext | <storesynk-customer-pricing> | (optional customer-pricing widget) the one live rule targeting the product: entry (title, percentage, the $app:customer_pricing metaobject identity), mode ('prompt' = anonymous | 'member' = logged-in customer whose tags match the rule), preview (member mode only - the current variant’s price/original with the rule’s cut off; ADVISORY - the app’s Discount Function applies the real cut at checkout) - read by the show-pricing-* displayers. A logged-in non-member gets no context (the widget renders nothing) |
addonsContext | <storesynk-addons> | (optional product-addons module) the ONE live add-ons offer for the current PDP (the overlap rule’s winner): entry (title, subtitle, discount, the app entry id), rows (resolved add-on rows - each an AddonRow + its fetched product), selection (the ticked rows - preselected rows start ticked), quote (ADVISORY integer-cent totals over the selection) - read by the show-addons-* displayers and <addon-list>. Re-provided on every sf:addon-toggle / row-card variant change |
| (addon row context) | <addon-list> (attached per row-card clone) | (optional product-addons module) one resolved add-on row: resolved (row + product), index, selected, pricing (the ADVISORY discounted ”+ price” delta for the card’s currently selected variant) - read by <select-addon> and the show-addon-* price displayers inside that card |
A convenience optionContext is also provided by <change-option> (carries the option
name, 1-based group, and selected value). show-option-label / show-active-option-title
read it when nested inside a change-option; standalone they fall back to productContext +
a group/name attribute - so it’s optional, and those displayers still need productContext
regardless.
Bubbling sf:* events: child controls dispatch; <storesynk-product> is the state owner
that catches sf:option-change / sf:media-select / sf:quantity-change /
sf:selling-plan-change (the latter from both <change-purchase-option> - the one-time /
subscribe toggle - and <change-selling-plan> - the frequency selector; each carries
detail.sellingPlanId (null for one-time), updating selectedSellingPlanId which
add-to-cart/buy-now read) and re-emits sf:variant-change;
<add-to-cart> emits sf:add-to-cart (detail = { product, variant }), and
<add-bundle-to-cart> emits the same sf:add-to-cart on a successful bundle add
(detail = { bundle }) - so the cart drawer’s open-on-add works for both. <add-volume-to-cart>
likewise emits sf:add-to-cart on a successful volume-tier add. Inside the bundle
widget, <bundle-offer> listens for the sf:variant-change bubbling up from its member cards
(each a full <storesynk-product>) and re-provides the bundle with re-keyed members +
recomputed pricing. Inside the volume-discount widget, each <volume-tier> row dispatches
sf:tier-select (detail = { index }) up to <storesynk-volume-discount>, which records the
chosen tier and re-provides volumeDiscountContext; the widget also re-prices its tiers on every
sf:variant-change so the rows track the page’s selected variant. Inside the product-addons
widget, <select-addon> dispatches a single bubbling sf:addon-toggle
(detail = { rowId, pick }) up to <storesynk-addons>, which runs the pure selection reducer
and re-provides addonsContext; a ticked row’s pick is re-keyed when its row card’s
sf:variant-change bubbles up (the card’s selected variant is what gets added). There is no
dedicated add-addons button: the page’s standard <add-to-cart> cooperates - when the first
<storesynk-addons> in its own product scope holds a ticked selection, the click sends ONE
multi-line updateCart (the untagged host line plus one attributed line per ticked row, stamped
_ss_addons / _ss_addons_host). Cart writes go through
window.Shopify.actions.updateCart / window.Shopify.actions.openCart (installed by core
inside <storesynk-store>) - NOT through events.
The Markets locale pickers follow the same up-dispatch pattern: <change-country> emits
sf:country-change (detail = { country }) and <change-language> emits sf:language-change
(detail = { language }) - both bubble/composed. <storesynk-store> catches them (stopping
propagation), owns the switch via setLocale() (which re-prices the cart’s buyer identity - a
standard shopify:cart:lines-update event re-syncs the cart - rebuilds formatMoney, and
re-provides storeContext), and then emits sf:locale-change (detail = { country, language })
after the switch settles.
The customer-accounts module (loaded when customer-client-id is on the store) drives login
state three ways off <storesynk-store>: the context (ctx.customer - the show-customer-*
displayers and <storesynk-customer-pricing> repaint from it), the ss-logged-in CSS hook on the
store (gates authed/anonymous UI), and the bubbling sf:customer-change event
(detail = { customer }, null on logout) for anything else on the page. <log-in> /
<log-out> are the action verbs (they redirect to / from Shopify’s hosted login); the session’s
buyer token feeds core’s cart runtime so buyer-keyed automatic discounts (customer-based pricing)
fire pre-checkout.
Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.