create-cartbase 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.js +3 -3
  2. package/package.json +1 -1
  3. package/template/app/docs/BUILD-A-STOREFRONT.md +6 -6
  4. package/template/app/docs/README.md +77 -76
  5. package/template/app/docs/auth.md +1 -1
  6. package/template/app/docs/carts.md +6 -6
  7. package/template/app/docs/categories.md +1 -1
  8. package/template/app/docs/checkout.md +127 -76
  9. package/template/app/docs/collections.md +1 -1
  10. package/template/app/docs/components.md +59 -60
  11. package/template/app/docs/consent.md +1 -1
  12. package/template/app/docs/content.md +1 -1
  13. package/template/app/docs/customers.md +1 -1
  14. package/template/app/docs/deploy.md +5 -5
  15. package/template/app/docs/gift-cards.md +1 -1
  16. package/template/app/docs/integrations.md +1 -1
  17. package/template/app/docs/menus.md +1 -1
  18. package/template/app/docs/metaobjects.md +1 -1
  19. package/template/app/docs/orders.md +1 -1
  20. package/template/app/docs/platform.md +126 -0
  21. package/template/app/docs/products.md +1 -1
  22. package/template/app/docs/redirects.md +1 -1
  23. package/template/app/docs/regions.md +5 -6
  24. package/template/app/docs/reviews.md +1 -1
  25. package/template/app/docs/search.md +1 -1
  26. package/template/app/docs/subscriptions.md +1 -1
  27. package/template/app/docs/variables.md +315 -0
  28. package/template/app/next-env.d.ts +6 -0
  29. package/template/app/next.config.ts +3 -3
  30. package/template/app/package.json +1 -1
  31. package/template/app/smoke.mjs +1 -1
  32. package/template/app/src/app/checkout/checkout-page-client.tsx +26 -10
  33. package/template/app/src/app/checkout/mypos-demo-tab.tsx +101 -0
  34. package/template/app/src/app/checkout/page.tsx +5 -6
  35. package/template/app/src/app/layout.tsx +113 -105
  36. package/template/app/src/app/order/[id]/confirmed/page.tsx +5 -4
  37. package/template/app/src/lib/cart-actions.ts +47 -43
  38. package/template/app/src/lib/config.ts +10 -5
  39. package/template/app/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- # Components — `@cartbase/storefront` UI families
1
+ # Components
2
2
 
3
3
  The component layer of `@cartbase/storefront`: what each family ships, the SDK
4
4
  calls it requires, the admin settings that change its behavior, and its mount
@@ -20,8 +20,7 @@ app's CSS; no component hardcodes a color.
20
20
 
21
21
  ---
22
22
 
23
- ## Family: tracking (`@cartbase/storefront/tracking/*`) — SHIPPED
24
-
23
+ ## Family: tracking (`@cartbase/storefront/tracking/*`)
25
24
  Meta Pixel + GA4 + Rybbit + Consent Mode v2. The Cartbase split of duties:
26
25
  **this package fires client events and writes attribution; server-side CAPI /
27
26
  GA4 Measurement Protocol sending is Cartbase-backend-owned** (the
@@ -154,8 +153,7 @@ Meta side) and `gtag('set','user_data',…)`. Call alongside
154
153
 
155
154
  ---
156
155
 
157
- ## Family: primitives (`@cartbase/storefront/primitives/*`) — SHIPPED
158
-
156
+ ## Family: primitives (`@cartbase/storefront/primitives/*`)
159
157
  Generic shadcn-style building blocks. No SDK calls, no admin settings —
160
158
  pure UI over the theme tokens.
161
159
 
@@ -172,8 +170,7 @@ pure UI over the theme tokens.
172
170
 
173
171
  ---
174
172
 
175
- ## Family: lib (`@cartbase/storefront/lib/*`) — SHIPPED
176
-
173
+ ## Family: lib (`@cartbase/storefront/lib/*`)
177
174
  Pure helpers — no React except `dual-price`, no fetches. The SDK never
178
175
  invents server truths: prices/totals arrive computed from the API
179
176
  (`variant.calculated_price`, `cart.total`); these helpers only select and
@@ -195,7 +192,7 @@ format.
195
192
  - `lib/get-product-price` — `getProductPrice({product, variantId})` /
196
193
  `getPricesForVariant` → formatted `VariantPrice` (cheapest + selected)
197
194
  from server-computed `calculated_price`. Reads Cartbase's flat
198
- `price_list_type` with the Medusa nested shape as fallback. Affected by:
195
+ `price_list_type` with a legacy nested shape as fallback. Affected by:
199
196
  price lists / B2B pricing context (what `calculated_price` contains),
200
197
  currency + region query context.
201
198
  - `lib/get-percentage-diff` — sale badge math.
@@ -204,21 +201,23 @@ format.
204
201
  - `lib/sort-products` — client-side re-sort of a fetched page
205
202
  (`price_asc|price_desc|created_at`); the API's `order` param stays the
206
203
  authority for paginated listings.
207
- - `lib/payment-constants` — `isStripeLike` / `isPaypal` / `isManual` +
208
- `paymentInfoMap`. Translated to Cartbase ids: matches `pp_stripe` exactly
209
- (code truth `src/lib/stripe/providers.ts`) plus the Medusa-era prefixes;
210
- `pp_system_default` = COD. Affected by: enabled payment providers +
211
- checkout rules (which ids ever reach the client).
204
+ - `lib/payment-constants` — `isStripeLike` / `isPaypal` +
205
+ `paymentInfoMap`. Two tender shapes since the pp_* kill: a processor
206
+ `provider_id` (`pp_stripe` exact, code truth
207
+ `src/lib/stripe/providers.ts`, plus legacy Medusa-era prefixes as
208
+ fallbacks) or a merchant METHOD rendered by its snapshot NAME — a
209
+ method never has a provider id to bucket. Affected by: connected
210
+ processors + enabled methods + checkout rules (which entries ever
211
+ reach the client).
212
212
  - `lib/store-api-error` — `storeApiError(err)`: display-boundary normalizer
213
- (capitalized message + terminal period), successor of `medusaError`.
213
+ (capitalized message + terminal period).
214
214
  Catch `StoreApiError` directly instead when branching on `status`/`code`.
215
215
  - `lib/hooks/use-intersection`, `lib/hooks/use-toggle-state` — viewport +
216
216
  toggle micro-hooks (client).
217
217
 
218
218
  ---
219
219
 
220
- ## Family: checkout (`@cartbase/storefront/checkout/*`) — SHIPPED
221
-
220
+ ## Family: checkout (`@cartbase/storefront/checkout/*`)
222
221
  The full checkout page family, production-proven (deferred-
223
222
  intent architecture — no payment session exists until Buy click) onto the
224
223
  Cartbase orchestration endpoints. The flow every component serves
@@ -227,7 +226,7 @@ Cartbase orchestration endpoints. The flow every component serves
227
226
  ```
228
227
  listShippingOptions(cart_id) + listPaymentProviders(cart_id) (render pickers)
229
228
  → Buy click → prepareCheckout (ONE atomic, compensated call)
230
- → pp_stripe: stripe.confirmPayment(client_secret) | pp_cod/pp_manual: skip
229
+ → pp_stripe: stripe.confirmPayment(client_secret) | merchant method: skip
231
230
  → completeCart → navigate to the confirmed page
232
231
  ```
233
232
 
@@ -264,21 +263,24 @@ code-first through the error-copy maps, never raw API strings.
264
263
  call REACTIVELY from Elements `loaderror` only), `carts.completeCart`.
265
264
  - **Props contract** — `{client, cart, customer, availableShippingMethods,
266
265
  availablePaymentMethods, countryCode?, countries?, paymentMethodFilter?,
267
- codConfig?, orderConfirmedPath?, onOrderPlaced?,
266
+ orderConfirmedPath?, onOrderPlaced?,
268
267
  resolveTrackingMetadata?, logError?}`. `countries` is caller-supplied
269
- (Cartbase regions embed NO countries array). `codConfig` = the
270
- integrations `cod` block the COD fee prediction is never hardcoded.
271
- `logError` replaces the legacy log writers (all production log
272
- points preserved). Returns the full orchestration surface
273
- (`performBuyClick`, `optimisticTotal(Cents)`, `deliveryReady`, …).
268
+ (Cartbase regions embed NO countries array). Fee prediction is never
269
+ hardcoded: each method LISTING entry carries its own
270
+ `fee_amount`/`fee_label`. `logError` replaces the legacy log writers
271
+ (all production log points preserved). Returns the full orchestration
272
+ surface (`performBuyClick`, `optimisticTotal(Cents)`, `deliveryReady`, …).
274
273
  - **Cartbase specifics** — zero-remainder gift path: `prepareCheckout`
275
- returning `client_secret:null` + `provider_id:null` SKIPS Stripe and
276
- completes on the gift session ([gift-cards.md](gift-cards.md)). Provider
277
- ids are `pp_stripe`/`pp_cod`/`pp_manual` exactly; `pp_manual` doubles as
278
- the offline tab when no true COD provider exists (fee predicted only for
279
- `pp_cod`). COD fee reads the cart-level `cod_fee_total` decoration.
274
+ returning `client_secret:null` + `provider_id:null` +
275
+ `payment_method_id:null` SKIPS Stripe and completes on the gift session
276
+ ([gift-cards.md](gift-cards.md)). Processors are `pp_stripe` exactly;
277
+ merchant methods list as `{payment_method_id, name, kind, instructions,
278
+ fee_amount, fee_label}` entries the COD-kind method wins the offline
279
+ tab, else the first manual method. The charged fee reads the cart-level
280
+ `payment_method_fee_total` decoration.
280
281
  - **Settings** — checkout rules (filter the listings + complete guard),
281
- COD integration (fee), Stripe credentials, gift cards, `accounts_mode`.
282
+ the methods' own fee configuration (Payments settings), Stripe
283
+ credentials, gift cards, `accounts_mode`.
282
284
 
283
285
  ### `<CheckoutClient />` — `checkout/checkout-client`
284
286
 
@@ -303,7 +305,7 @@ code-first through the error-copy maps, never raw API strings.
303
305
  `StripeElementsScope` mounts `<Elements mode:"payment">` where needed
304
306
  (`passthrough` renders children scope-less on COD-only stores);
305
307
  `StripeContext` boolean = "Stripe.js ready".
306
- - **SDK calls** — none (env: `NEXT_PUBLIC_STRIPE_KEY`, Medusa-era names
308
+ - **SDK calls** — none (env: `NEXT_PUBLIC_STRIPE_KEY`; legacy env names
307
309
  kept as fallbacks).
308
310
  - **Mount rules** — `PaymentWrapper` wraps the page ONCE with
309
311
  `amount={optimisticTotalCents}` (cents at this Stripe boundary only);
@@ -383,11 +385,11 @@ code-first through the error-copy maps, never raw API strings.
383
385
  badge. `LineItemCard` is the standalone card variant.
384
386
  - **SDK calls** — `carts.updateLineItem` (quantity); child widgets below.
385
387
  Totals are rendered STRAIGHT from the cart decoration: `item_total`,
386
- `shipping_total`, `cod_fee_total`/`cod_fee_label`, `discount_total`,
388
+ `shipping_total`, `payment_method_fee_total`/`payment_method_fee_label`, `discount_total`,
387
389
  `tax_total`, `total`, `gift_card_total`, `gift_card_remainder`.
388
390
  - **Props contract** — `{client, cart, optimisticShippingCost,
389
391
  onOptimisticShippingClear?, optimisticCodFee?, onOptimisticCodFeeClear?,
390
- codFeeLabel?, showGiftCards?, onCartChange?}`. Optimistic values clear
392
+ methodFeeLabel?, showGiftCards?, onCartChange?}`. Optimistic values clear
391
393
  automatically once the server cart catches up.
392
394
  - **Settings** — COD integration (fee row), gift cards, promotions.
393
395
 
@@ -445,8 +447,7 @@ extra modules beyond the per-file exports, imported from
445
447
 
446
448
  ---
447
449
 
448
- ## Family: cart-drawer (`@cartbase/storefront/cart-drawer/*`) — SHIPPED
449
-
450
+ ## Family: cart-drawer (`@cartbase/storefront/cart-drawer/*`)
450
451
  Sliding cart UI, production-proven layout. All
451
452
  components are `"use client"`. Money is EUR decimal major units everywhere
452
453
  (cart totals are SERVER truth from the decorated cart — components render,
@@ -570,8 +571,8 @@ Domain doc for every call: [carts.md](carts.md); gift-card tender:
570
571
  - **Purpose** — full totals breakdown rendered EXACTLY from the decorated
571
572
  cart: `subtotal`, `discount_total` (>0, negated for display),
572
573
  `shipping_total` (once a shipping method is set; 0 renders FREE; before
573
- that "calculated at checkout"), `tax_total` (>0), `cod_fee_total` (>0,
574
- labeled by the server's `cod_fee_label`), `total`, then one row per
574
+ that "calculated at checkout"), `tax_total` (>0), `payment_method_fee_total` (>0,
575
+ labeled by the server's `payment_method_fee_label`), `total`, then one row per
575
576
  applied gift card (masked `last4`, negated; a depleted card stays listed
576
577
  at 0) and `gift_card_remainder` — what the remainder provider charges.
577
578
  Row selection is the pure `selectSummaryRows(cart)` (unit-tested).
@@ -609,8 +610,7 @@ Domain doc for every call: [carts.md](carts.md); gift-card tender:
609
610
 
610
611
  ---
611
612
 
612
- ## Family: products (`@cartbase/storefront/products/*`) — SHIPPED
613
-
613
+ ## Family: products (`@cartbase/storefront/products/*`)
614
614
  The PDP + product-card family, production-proven. All prices
615
615
  render the SERVER-computed `variant.calculated_price` via
616
616
  `lib/get-product-price` (Cartbase's flat `price_list_type` wire shape) — no
@@ -668,7 +668,7 @@ components read copy only through the context or explicit `labels` props).
668
668
  `optionsAsKeymap` / `optionsMatch` / `findMatchingVariant` — the
669
669
  option-choice → variant resolution extracted from product-actions, reading
670
670
  Cartbase's option-value LINK shape (`variant.options[].value.{option_id,value}`)
671
- with the Medusa flat-row fallback. Unit-tested
671
+ with a legacy flat-row fallback. Unit-tested
672
672
  (tests/unit/storefront-catalog.test.ts).
673
673
 
674
674
  ### `<ImageGallery images />` — `products/image-gallery`
@@ -754,8 +754,7 @@ with the Medusa flat-row fallback. Unit-tested
754
754
  `ProductLabelsProvider` for i18n; `addToCart`/`openCart` seams as on
755
755
  `ProductActions`.
756
756
 
757
- ## Family: store (`@cartbase/storefront/store/*`) — SHIPPED
758
-
757
+ ## Family: store (`@cartbase/storefront/store/*`)
759
758
  The listing family: paginated grids, sort, collection/category/search
760
759
  templates. Sorting discipline: the API's `order` param is the authority for
761
760
  paginated listings; client-side re-sort (`lib/sort-products`) exists ONLY
@@ -859,9 +858,9 @@ search UIs (chips, drawers) that want the same URL contract.
859
858
 
860
859
  ---
861
860
 
862
- ## Family: order (`@cartbase/storefront/order/*`) — SHIPPED
863
-
864
- Order confirmation + account order views, production-proven. The Medusa `StoreOrder` was ONE object carrying computed line
861
+ ## Family: order (`@cartbase/storefront/order/*`)
862
+ Order confirmation + account order views, production-proven. Some
863
+ platforms ship ONE `StoreOrder` object carrying computed line
865
864
  totals, order totals, shipping methods and payments; Cartbase splits those
866
865
  across surfaces, so the family takes them as separate props — the Cartbase
867
866
  `StoreOrderDetail` (`api/orders`) carries items as version-pivot rows
@@ -893,7 +892,7 @@ component also takes a `labels` prop pick.
893
892
  rybbit, deduped by `order.display_id`) is APP-OWNED: fire it from the
894
893
  confirmation route exactly once per order per the tracking family's
895
894
  dedupe contract — the template deliberately does NOT fire it.
896
- - **Settings** — COD settings (fee row presence + `cod_fee_label`),
895
+ - **Settings** — COD settings (fee row presence + `payment_method_fee_label`),
897
896
  checkout rules (which provider ids appear), store locales (labels pack).
898
897
 
899
898
  ### `<OrderConfirmationHeader order />` — `order/order-confirmation-header`
@@ -916,19 +915,19 @@ component also takes a `labels` prop pick.
916
915
  COD fees are never line items, so on pure Cartbase data the filter is a
917
916
  no-op safety net. Newest-first sort by `createdAt` when present.
918
917
 
919
- ### `<OrderTotals totals currencyCode items? codFeeLabel? />` — `order/order-totals`
918
+ ### `<OrderTotals totals currencyCode items? methodFeeLabel? />` — `order/order-totals`
920
919
 
921
920
  - **Purpose** — the money breakdown: Subtotal / Shipping (FREE badge at
922
921
  0) / COD fee / Discount (negated) / Tax / Total, all via `DualPrice`.
923
922
  - **Data seam** — `OrderTotalsSource` (the decorated cart satisfies it:
924
923
  `item_subtotal`, `shipping_subtotal`, `discount_total`, `tax_total`,
925
- `total`, `cod_fee_total`, `cod_fee_label`); the summary snapshot adapts
924
+ `total`, `payment_method_fee_total`, `payment_method_fee_label`); the summary snapshot adapts
926
925
  via `orderTotalsFromSummary`. Row policy is the pure, unit-tested
927
- `selectOrderTotalsRows`: native `cod_fee_total` wins over a legacy fee
926
+ `selectOrderTotalsRows`: native `payment_method_fee_total` wins over a legacy fee
928
927
  LINE; a legacy fee line's net is subtracted from the visible subtotal
929
- (v2.3.1 production fix); COD label preference `codFeeLabel` prop →
930
- server `cod_fee_label` → fee-line title → `labels.codFee`.
931
- - **Settings** — COD settings (`cod_fee_total`/`cod_fee_label`),
928
+ (v2.3.1 production fix); COD label preference `methodFeeLabel` prop →
929
+ server `payment_method_fee_label` → fee-line title → `labels.paymentMethodFee`.
930
+ - **Settings** — COD settings (`payment_method_fee_total`/`payment_method_fee_label`),
932
931
  promotions (discount row).
933
932
 
934
933
  ### `<OrderAddressCard order />` — `order/order-address-card`
@@ -953,11 +952,13 @@ component also takes a `labels` prop pick.
953
952
 
954
953
  ### `<OrderPaymentCard providerId cardLast4? />` — `order/order-payment-card`
955
954
 
956
- - Payment method card; `resolvePaymentTitle` buckets the provider id via
957
- `lib/payment-constants` (`pp_stripe` card, `pp_system_default` COD)
958
- through the locale pack, falling back to `paymentInfoMap` then the raw
959
- id. Payment internals never cross the Cartbase store surface — the id
960
- arrives via props from checkout state.
955
+ - Payment method card; `resolvePaymentTitle(providerId, titles,
956
+ methodName?)` the method's merchant NAME wins verbatim when present
957
+ (the snapshot from session data); otherwise the processor id buckets
958
+ via `lib/payment-constants` (`pp_stripe` = card) through the locale
959
+ pack, falling back to `paymentInfoMap` then the raw id. Payment
960
+ internals never cross the Cartbase store surface — both arrive via
961
+ props from checkout state.
961
962
 
962
963
  ### `<OrderTimeline fulfillmentStatus? />` — `order/order-timeline`
963
964
 
@@ -974,8 +975,7 @@ component also takes a `labels` prop pick.
974
975
 
975
976
  ---
976
977
 
977
- ## Family: common (`@cartbase/storefront/common/*`) — SHIPPED
978
-
978
+ ## Family: common (`@cartbase/storefront/common/*`)
979
979
  Shared storefront chrome, production-proven.
980
980
 
981
981
  ### `<LocalizedLink href … />` — `common/localized-link`
@@ -1027,8 +1027,7 @@ Shared storefront chrome, production-proven.
1027
1027
 
1028
1028
  ---
1029
1029
 
1030
- ## Family: reviews-ui (`@cartbase/storefront/reviews-ui`) — SHIPPED
1031
-
1030
+ ## Family: reviews-ui (`@cartbase/storefront/reviews-ui`)
1032
1031
  Verified-purchase review components, ported from a production storefront,
1033
1032
  over `api/reviews`. ONE barrel export seam: everything imports from
1034
1033
  `@cartbase/storefront/reviews-ui`. Endpoint truth:
@@ -1,4 +1,4 @@
1
- # Consent — Consent Mode v2 banner config
1
+ # Consent
2
2
 
3
3
  The store's CMP configuration for the built-in Consent Mode v2 banner
4
4
  (consent-management card). Defaults are **always applied server-side**, so
@@ -1,4 +1,4 @@
1
- # Content — pages + blogs
1
+ # Content
2
2
 
3
3
  Shopify Storefront `Page` / `Article` shapes **verbatim** so storefront code
4
4
  ports 1:1. **PUBLISHED only** — drafts and deleted rows 404 (lib status
@@ -1,4 +1,4 @@
1
- # Customers — profile, addresses, documents
1
+ # Customers
2
2
 
3
3
  The signed-in customer surface. Every endpoint here requires a **customer
4
4
  session**: `authorization: Bearer <supabase jwt>` (minted by the
@@ -1,4 +1,4 @@
1
- # Deploy & hosting
1
+ # Deploy
2
2
 
3
3
  Cartbase builds and hosts your storefront. You send the app's source files
4
4
  — one snapshot of the whole project — and Cartbase turns it into a
@@ -25,7 +25,7 @@ headers the rest of this corpus uses. In practice you'll deploy through
25
25
  the admin UI (**Storefront** in the sidebar), the Cartbase CLI, or an
26
26
  agent connection — all three are wrappers over exactly these calls.
27
27
  Users who belong to several stores name the target store with an
28
- `x-barter-store: <client-id>` header.
28
+ `x-cartbase-store: <client-id>` header.
29
29
 
30
30
  ## What a deploy is
31
31
 
@@ -62,9 +62,9 @@ anyway):
62
62
 
63
63
  | Variable | Value |
64
64
  |---|---|
65
- | `NEXT_PUBLIC_BARTER_URL` | The store's API origin |
66
- | `NEXT_PUBLIC_BARTER_CLIENT_ID` | The store's client id |
67
- | `NEXT_PUBLIC_BARTER_PUBLISHABLE_KEY` | The store's publishable key, when one exists |
65
+ | `NEXT_PUBLIC_CARTBASE_URL` | The store's API origin |
66
+ | `NEXT_PUBLIC_CARTBASE_CLIENT_ID` | The store's client id |
67
+ | `NEXT_PUBLIC_CARTBASE_PUBLISHABLE_KEY` | The store's publishable key, when one exists |
68
68
 
69
69
  Only these public values ever reach a storefront build — secret keys are
70
70
  never injected, so code that expects one is a design error.
@@ -1,4 +1,4 @@
1
- # Gift cards — tender on carts
1
+ # Gift cards
2
2
 
3
3
  Gift-card redemption is a **payment tender** (`pp_giftcard`), never a
4
4
  discount: cart totals and VAT compute first and **never move**; applied
@@ -1,4 +1,4 @@
1
- # Store integrations config — carriers, COD, tracking, lockers
1
+ # Integrations
2
2
 
3
3
  Store-public integration configuration for render/checkout time
4
4
  (couriers-port + tracking-integrations cards). The payload is **composed**
@@ -1,4 +1,4 @@
1
- # Menus — backend-owned navigation
1
+ # Menus
2
2
 
3
3
  Shopify Storefront `Menu` shape **verbatim** (content-navigation card).
4
4
  Items store a typed resource reference; `url` is **computed at read time**
@@ -1,4 +1,4 @@
1
- # Metaobjects — merchant-defined content types
1
+ # Metaobjects
2
2
 
3
3
  Merchant-defined structured content (size charts, brand profiles, FAQ
4
4
  blocks) served by TYPE + HANDLE (metaobjects card). **ACTIVE entries only —
@@ -1,4 +1,4 @@
1
- # Orders — customer reads + transfers
1
+ # Orders
2
2
 
3
3
  The authenticated customer's order surface: list, detail (items +
4
4
  fulfillments with tracking + addresses) and order transfers. There is **no
@@ -0,0 +1,126 @@
1
+ # Platform fingerprints
2
+
3
+ Every Cartbase-powered storefront emits a small, deliberate set of signals
4
+ so platform-detection tools (Wappalyzer, BuiltWith) classify the site as
5
+ **Ecommerce** — the same mechanism Shopify uses (`window.Shopify`,
6
+ `X-ShopId`, `_shopify_s`). Full spec: `docs/cards/platform-fingerprints.md`.
7
+ Category discipline: these are the ONLY signals emitted — nothing that
8
+ reads as a framework, CMS, or website builder, and NEVER on `/api/admin/*`.
9
+
10
+ SDK modules: `@cartbase/storefront/platform`, `@cartbase/storefront/lib/cookie-names`.
11
+
12
+ ---
13
+
14
+ ## Response header — `x-cartbase-version`
15
+
16
+ - **Purpose** — every `/api/store/*` response carries the platform version,
17
+ so a single HTTP response proves the platform without loading a page.
18
+ - **Auth** — none required to observe the header; it's stamped regardless
19
+ of whether the underlying call succeeds or errors (both `withLogging`
20
+ return paths).
21
+ - **Value** — the platform's own semver (`src/lib/platform/identity.ts` →
22
+ `PLATFORM_VERSION`).
23
+ - **Scope** — `/api/store/*` only. `/api/admin/*` never carries it (admin
24
+ is not a storefront).
25
+
26
+ ```bash
27
+ # A successful call carries the header.
28
+ HEADERS=$(curl -sfD - -o /dev/null "$BASE/api/store/consent" -H "x-client-id: $CLIENT_ID")
29
+ echo "$HEADERS" | grep -qi '^x-cartbase-version: '
30
+
31
+ # An ERROR response (missing x-client-id → 400 missing_client_id) still
32
+ # carries it — the fingerprint is not a happy-path-only afterthought.
33
+ ERR_HEADERS=$(curl -sD - -o /dev/null "$BASE/api/store/consent")
34
+ echo "$ERR_HEADERS" | grep -qi '^x-cartbase-version: '
35
+
36
+ # Admin surface never carries it (even though the same withLogging wrapper
37
+ # runs the admin route too, once auth resolves).
38
+ ADMIN_HEADERS=$(curl -sD - -o /dev/null "$BASE/api/admin/users/me")
39
+ ! echo "$ADMIN_HEADERS" | grep -qi '^x-cartbase-version: '
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Meta tag — `<meta name="generator" content="Cartbase" />`
45
+
46
+ - **Purpose** — the classic "what built this site" signal, read by every
47
+ detection tool's HTML scanner.
48
+ - **SDK** — `createStorefrontMetadata(overrides?: Metadata): Metadata`
49
+ (`@cartbase/storefront/platform`). Call it in the root layout instead of
50
+ hand-writing `export const metadata` — `generator` always resolves to
51
+ `PLATFORM_NAME` ("Cartbase") and cannot be shadowed by a stray key in
52
+ `overrides` (the helper's own assignment applies AFTER the spread).
53
+ - **Components** — every storefront's `app/layout.tsx` (see
54
+ `examples/storefront/src/app/layout.tsx` for the reference wiring).
55
+
56
+ ```ts
57
+ // app/layout.tsx
58
+ import { createStorefrontMetadata } from "@cartbase/storefront/platform"
59
+
60
+ export const metadata: Metadata = createStorefrontMetadata({
61
+ title: "My Store",
62
+ description: "...",
63
+ })
64
+ // → renders <meta name="generator" content="Cartbase" /> on every page
65
+ ```
66
+
67
+ This is a build-time Next.js Metadata API call, not a store-API endpoint —
68
+ no curl to demonstrate; verified by
69
+ `tests/unit/storefront-lib.test.ts` ("platform — generator metadata +
70
+ window.Cartbase").
71
+
72
+ ---
73
+
74
+ ## JS global — `window.Cartbase`
75
+
76
+ - **Purpose** — the strongest detector signal (how `window.Shopify` works):
77
+ a synchronous inline script sets `window.Cartbase = {version, storeId}`
78
+ before hydration.
79
+ - **Shape** — `{ version: string, storeId: string }`. No PII, no email, no
80
+ secret — store id + platform version only.
81
+ - **SDK** — `<PlatformInit storeId={...} />` (`@cartbase/storefront/platform`),
82
+ a server component shaped exactly like `<ConsentInit />`
83
+ (`@cartbase/storefront/tracking/consent-init`): one synchronous inline
84
+ `<script>`, mounted once near the top of `<body>`.
85
+ - **Components** — mount once per app, next to `<ConsentInit />`. Never
86
+ mount in an admin bundle.
87
+
88
+ ```tsx
89
+ // app/layout.tsx, inside <body>, first children:
90
+ <ConsentInit />
91
+ <PlatformInit storeId={CARTBASE_CLIENT_ID} />
92
+ ```
93
+
94
+ Also build-time/render-only — no store-API curl to demonstrate; the exact
95
+ script shape (`window.Cartbase={"version":"...","storeId":"..."};`, no
96
+ extra keys) is verified by `tests/unit/storefront-lib.test.ts`.
97
+
98
+ ---
99
+
100
+ ## Cookies — `_cartbase_cart` (was `_barter_cart_id`)
101
+
102
+ - **Purpose** — the cart-id cookie. The app still OWNS *setting* it (the
103
+ SDK never persists the cart — see [carts.md](carts.md)); the library
104
+ owns the NAME so every consumer emits the same fingerprint instead of
105
+ inventing its own prefix.
106
+ - **SDK** — `CART_COOKIE`, `LEGACY_CART_COOKIE`, `readCartCookie(get)`
107
+ (`@cartbase/storefront/lib/cookie-names`). `readCartCookie` prefers the
108
+ new name and falls back to the legacy `_barter_cart_id` name, so an
109
+ existing visitor's cart survives the rename. Every WRITE uses the new
110
+ name only.
111
+ - **`SESSION_COOKIE` (`_cartbase_session`)** — the name is reserved for a
112
+ future cookie-backed customer session. Nothing sets it today: auth is
113
+ pure Bearer-JWT, persisted by the consuming app (see
114
+ [auth.md](auth.md)). Defined now so a future session mechanism launches
115
+ with the fingerprint-correct name.
116
+
117
+ ```ts
118
+ import { CART_COOKIE, readCartCookie } from "@cartbase/storefront/lib/cookie-names"
119
+
120
+ const cartId = readCartCookie((name) => jar.get(name)?.value) // reads either name
121
+ jar.set(CART_COOKIE, cart.id, { path: "/", maxAge: THIRTY_DAYS }) // writes the new name only
122
+ ```
123
+
124
+ No store-API curl to demonstrate (this is a client-cookie contract, not a
125
+ server response); verified by `tests/unit/storefront-lib.test.ts`
126
+ ("platform — cookie names + back-compat read").
@@ -1,4 +1,4 @@
1
- # Products & variants
1
+ # Products
2
2
 
3
3
  The catalog read surface. The product object documented here is THE
4
4
  canonical shape every discovery endpoint reuses (search results, collection
@@ -1,4 +1,4 @@
1
- # URL redirects — 404-path lookup
1
+ # Redirects
2
2
 
3
3
  Exact-match redirect lookup (seo-listings card 21). **Hot-path rule: call
4
4
  this ONLY from the storefront's not-found handler** — never on regular page
@@ -1,4 +1,4 @@
1
- # Regions, currencies, locales
1
+ # Regions
2
2
 
3
3
  Catalog-context primitives a storefront resolves at boot: regions feed the
4
4
  pricing context (`region_id` → region currency), currencies tell you what the
@@ -29,8 +29,8 @@ SDK module: `@cartbase/storefront/api/regions`.
29
29
  ```
30
30
 
31
31
  - **Response** — ordered by name. NOTE: Cartbase regions carry NO embedded
32
- `countries` array (divergence from Medusa's Store API — country/tax scope
33
- lives server-side in `tax_regions`).
32
+ `countries` array — country/tax scope lives server-side in
33
+ `tax_regions`.
34
34
 
35
35
  ```jsonc
36
36
  {
@@ -202,6 +202,5 @@ echo "$LOCALES" | grep -q '"en"'
202
202
  - **Errors** — 400 `missing_client_id`.
203
203
  - **SDK** — `listLocales(client)`.
204
204
  - **Components** — locale switcher; `StorefrontClient`'s `getLocale` hook.
205
- - **Settings** — Admin → Settings → Store → locales (per-store
206
- `store_locales` manager; Cartbase's intentional divergence from Medusa's
207
- global locale catalog).
205
+ - **Settings** — Admin → Settings → Store → locales (each store manages
206
+ its own locale list in `store_locales`).
@@ -1,4 +1,4 @@
1
- # Reviews — widget, token wizard, photo rewards
1
+ # Reviews
2
2
 
3
3
  Verified-purchase reviews. Reviews exist **only** via a
4
4
  single-use, order-scoped, expiring **token** minted by the request scanner
@@ -1,4 +1,4 @@
1
- # Search & related products
1
+ # Search
2
2
 
3
3
  Configurable storefront search (search-discovery card) plus the PDP's
4
4
  related-products rail. Results are the SAME canonical product objects the
@@ -1,4 +1,4 @@
1
- # Subscriptions — the customer portal
1
+ # Subscriptions
2
2
 
3
3
  The "My subscriptions" surface (subscriptions-portal card): list, detail,
4
4
  schedule control, contract edits, cancel/reactivate and payment-method