@rebuy/rebuy 3.12.1 → 3.14.0

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.
package/dist/index.cjs CHANGED
@@ -1162,6 +1162,20 @@ var CabShopConfig = import_zod2.z.object({
1162
1162
  apiKey: import_zod2.z.string(),
1163
1163
  /** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
1164
1164
  billingVersion: import_zod2.z.string().nullable().catch(null).default(null),
1165
+ /**
1166
+ * The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
1167
+ * and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
1168
+ * wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
1169
+ * sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
1170
+ * Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
1171
+ * immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
1172
+ * string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
1173
+ * coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
1174
+ * as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
1175
+ * positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
1176
+ * to un-busted (origin-served — slower, never stale), never a parse fail.
1177
+ */
1178
+ cacheKey: import_zod2.z.union([import_zod2.z.number(), import_zod2.z.string()]).transform((value) => Number(value)).pipe(import_zod2.z.number().int().positive()).optional().catch(void 0),
1165
1179
  /**
1166
1180
  * The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
1167
1181
  * (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
@@ -3994,7 +4008,7 @@ var normalizeTokens = (text, knownTokens = DEFAULT_KNOWN_TOKENS) => {
3994
4008
 
3995
4009
  // src/transforms/contentBlockV1/convertContentBlockTextToV2.ts
3996
4010
  var literalText = (_field, value) => normalizeTokens(value);
3997
- var convertContentBlockTextToV2 = (language, text = literalText) => {
4011
+ var convertContentBlockTextToV2 = (language, text = literalText, textAlign = "center") => {
3998
4012
  const description = language?.description;
3999
4013
  const superTitle = language?.superTitle;
4000
4014
  const title = language?.title;
@@ -4006,20 +4020,20 @@ var convertContentBlockTextToV2 = (language, text = literalText) => {
4006
4020
  fontSize: "extraLarge",
4007
4021
  marks: [{ type: "bold" }],
4008
4022
  text: text("superTitle", superTitle),
4009
- textAlign: "center"
4023
+ textAlign
4010
4024
  }) : null,
4011
4025
  title ? convertTextToTiptapParagraph({
4012
4026
  color: "base",
4013
4027
  fontSize: "medium",
4014
4028
  marks: [{ type: "bold" }],
4015
4029
  text: text("title", title),
4016
- textAlign: "center"
4030
+ textAlign
4017
4031
  }) : null,
4018
4032
  description ? convertTextToTiptapParagraph({
4019
4033
  color: "base",
4020
4034
  fontSize: "base",
4021
4035
  text: text("description", description),
4022
- textAlign: "center"
4036
+ textAlign
4023
4037
  }) : null
4024
4038
  ].filter((p) => p !== null),
4025
4039
  type: "doc"
@@ -4736,6 +4750,19 @@ var headerVariables = (header) => HEADER_FIELDS.flatMap((field) => {
4736
4750
  const defaultValue = header[field];
4737
4751
  return defaultValue === void 0 ? [] : [{ defaultValue, name: field, type: VariableType.text }];
4738
4752
  });
4753
+ var headerNode = (header, textAlign) => ({
4754
+ content: { en: convertContentBlockTextToV2(header, headerText, textAlign) },
4755
+ sectionType: "text"
4756
+ });
4757
+ var buildHeaderNavRow = (header) => ({
4758
+ alignment: { horizontal: "start", vertical: "bottom" },
4759
+ direction: "grid",
4760
+ grid: { columns: ["fill", "auto"] },
4761
+ name: "header",
4762
+ sections: [headerNode(header, "start"), buildCarouselNav()],
4763
+ sectionType: "layout",
4764
+ spacing: "base"
4765
+ });
4739
4766
  var HAS_PRODUCTS = "hasProducts";
4740
4767
  var hasProductsMutators = () => [
4741
4768
  {
@@ -4828,6 +4855,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
4828
4855
  const pageSize = Math.max(columns, 1);
4829
4856
  const limit = settings.limit ?? void 0;
4830
4857
  const showsNav = !!layout?.carousel && !paginationOff(layout.showPaginations) && (limit === void 0 || limit > pageSize);
4858
+ const headerInChrome = showsNav && hasHeaderText(header);
4831
4859
  const fitsOneRow = limit !== void 0 && limit <= pageSize;
4832
4860
  const sideBySide = direction === "grid" || direction === "columns";
4833
4861
  const offersDirection = sideBySide && (showsNav || fitsOneRow) ? "horizontal" : "vertical";
@@ -4885,7 +4913,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
4885
4913
  itemsAtOnce: Math.max(showsNav ? columns : limit ?? ENGINE_LIMIT_CEILING, 1),
4886
4914
  ...listLayout,
4887
4915
  padding: gridChrome ? paddingFor(containerBorder.style) : "none",
4888
- sections: showsNav ? [buildCarouselNav(), offers] : [offers],
4916
+ sections: showsNav ? [headerInChrome ? buildHeaderNavRow(header) : buildCarouselNav(), offers] : [offers],
4889
4917
  sectionType: "carousel"
4890
4918
  };
4891
4919
  return CABRootSection.parse({
@@ -4895,10 +4923,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
4895
4923
  // The widget name is required by the admin save schema; carry the legacy widget's name through.
4896
4924
  ...name && { name },
4897
4925
  previewMode: settings.previewMode,
4898
- sections: [
4899
- ...hasHeaderText(header) ? [{ content: { en: convertContentBlockTextToV2(header, headerText) }, sectionType: "text" }] : [],
4900
- display
4901
- ],
4926
+ sections: [...hasHeaderText(header) && !headerInChrome ? [headerNode(header, "center")] : [], display],
4902
4927
  // CABTracking's schema fallbacks fill missing or null fields and the missing object; malformed values throw.
4903
4928
  tracking: settings.tracking,
4904
4929
  type: SHOPIFY_CHECKOUT_EXTENSION,
package/dist/index.mjs CHANGED
@@ -876,6 +876,20 @@ var CabShopConfig = z2.object({
876
876
  apiKey: z2.string(),
877
877
  /** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
878
878
  billingVersion: z2.string().nullable().catch(null).default(null),
879
+ /**
880
+ * The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
881
+ * and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
882
+ * wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
883
+ * sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
884
+ * Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
885
+ * immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
886
+ * string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
887
+ * coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
888
+ * as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
889
+ * positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
890
+ * to un-busted (origin-served — slower, never stale), never a parse fail.
891
+ */
892
+ cacheKey: z2.union([z2.number(), z2.string()]).transform((value) => Number(value)).pipe(z2.number().int().positive()).optional().catch(void 0),
879
893
  /**
880
894
  * The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
881
895
  * (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
@@ -3708,7 +3722,7 @@ var normalizeTokens = (text, knownTokens = DEFAULT_KNOWN_TOKENS) => {
3708
3722
 
3709
3723
  // src/transforms/contentBlockV1/convertContentBlockTextToV2.ts
3710
3724
  var literalText = (_field, value) => normalizeTokens(value);
3711
- var convertContentBlockTextToV2 = (language, text = literalText) => {
3725
+ var convertContentBlockTextToV2 = (language, text = literalText, textAlign = "center") => {
3712
3726
  const description = language?.description;
3713
3727
  const superTitle = language?.superTitle;
3714
3728
  const title = language?.title;
@@ -3720,20 +3734,20 @@ var convertContentBlockTextToV2 = (language, text = literalText) => {
3720
3734
  fontSize: "extraLarge",
3721
3735
  marks: [{ type: "bold" }],
3722
3736
  text: text("superTitle", superTitle),
3723
- textAlign: "center"
3737
+ textAlign
3724
3738
  }) : null,
3725
3739
  title ? convertTextToTiptapParagraph({
3726
3740
  color: "base",
3727
3741
  fontSize: "medium",
3728
3742
  marks: [{ type: "bold" }],
3729
3743
  text: text("title", title),
3730
- textAlign: "center"
3744
+ textAlign
3731
3745
  }) : null,
3732
3746
  description ? convertTextToTiptapParagraph({
3733
3747
  color: "base",
3734
3748
  fontSize: "base",
3735
3749
  text: text("description", description),
3736
- textAlign: "center"
3750
+ textAlign
3737
3751
  }) : null
3738
3752
  ].filter((p) => p !== null),
3739
3753
  type: "doc"
@@ -4450,6 +4464,19 @@ var headerVariables = (header) => HEADER_FIELDS.flatMap((field) => {
4450
4464
  const defaultValue = header[field];
4451
4465
  return defaultValue === void 0 ? [] : [{ defaultValue, name: field, type: VariableType.text }];
4452
4466
  });
4467
+ var headerNode = (header, textAlign) => ({
4468
+ content: { en: convertContentBlockTextToV2(header, headerText, textAlign) },
4469
+ sectionType: "text"
4470
+ });
4471
+ var buildHeaderNavRow = (header) => ({
4472
+ alignment: { horizontal: "start", vertical: "bottom" },
4473
+ direction: "grid",
4474
+ grid: { columns: ["fill", "auto"] },
4475
+ name: "header",
4476
+ sections: [headerNode(header, "start"), buildCarouselNav()],
4477
+ sectionType: "layout",
4478
+ spacing: "base"
4479
+ });
4453
4480
  var HAS_PRODUCTS = "hasProducts";
4454
4481
  var hasProductsMutators = () => [
4455
4482
  {
@@ -4542,6 +4569,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
4542
4569
  const pageSize = Math.max(columns, 1);
4543
4570
  const limit = settings.limit ?? void 0;
4544
4571
  const showsNav = !!layout?.carousel && !paginationOff(layout.showPaginations) && (limit === void 0 || limit > pageSize);
4572
+ const headerInChrome = showsNav && hasHeaderText(header);
4545
4573
  const fitsOneRow = limit !== void 0 && limit <= pageSize;
4546
4574
  const sideBySide = direction === "grid" || direction === "columns";
4547
4575
  const offersDirection = sideBySide && (showsNav || fitsOneRow) ? "horizontal" : "vertical";
@@ -4599,7 +4627,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
4599
4627
  itemsAtOnce: Math.max(showsNav ? columns : limit ?? ENGINE_LIMIT_CEILING, 1),
4600
4628
  ...listLayout,
4601
4629
  padding: gridChrome ? paddingFor(containerBorder.style) : "none",
4602
- sections: showsNav ? [buildCarouselNav(), offers] : [offers],
4630
+ sections: showsNav ? [headerInChrome ? buildHeaderNavRow(header) : buildCarouselNav(), offers] : [offers],
4603
4631
  sectionType: "carousel"
4604
4632
  };
4605
4633
  return CABRootSection.parse({
@@ -4609,10 +4637,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
4609
4637
  // The widget name is required by the admin save schema; carry the legacy widget's name through.
4610
4638
  ...name && { name },
4611
4639
  previewMode: settings.previewMode,
4612
- sections: [
4613
- ...hasHeaderText(header) ? [{ content: { en: convertContentBlockTextToV2(header, headerText) }, sectionType: "text" }] : [],
4614
- display
4615
- ],
4640
+ sections: [...hasHeaderText(header) && !headerInChrome ? [headerNode(header, "center")] : [], display],
4616
4641
  // CABTracking's schema fallbacks fill missing or null fields and the missing object; malformed values throw.
4617
4642
  tracking: settings.tracking,
4618
4643
  type: SHOPIFY_CHECKOUT_EXTENSION,
@@ -39,6 +39,7 @@ export declare const CabShopConfig: z.ZodObject<{
39
39
  }, z.core.$strip>>>>>;
40
40
  apiKey: z.ZodString;
41
41
  billingVersion: z.ZodDefault<z.ZodCatch<z.ZodNullable<z.ZodString>>>;
42
+ cacheKey: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>, z.ZodNumber>>>;
42
43
  currency: z.ZodCatch<z.ZodOptional<z.ZodString>>;
43
44
  markets: z.ZodPipe<z.ZodDefault<z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
44
45
  enabled: z.ZodBoolean;
@@ -76,6 +77,7 @@ export declare const CabUserConfig: z.ZodObject<{
76
77
  }, z.core.$strip>>>>>;
77
78
  apiKey: z.ZodString;
78
79
  billingVersion: z.ZodDefault<z.ZodCatch<z.ZodNullable<z.ZodString>>>;
80
+ cacheKey: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>, z.ZodNumber>>>;
79
81
  currency: z.ZodCatch<z.ZodOptional<z.ZodString>>;
80
82
  markets: z.ZodPipe<z.ZodDefault<z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
81
83
  enabled: z.ZodBoolean;
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { type CabUserConfig } from '../schema/cabShopConfig';
2
3
  import { type MonetizeInput, type ServerContext } from '../server/shared';
3
4
  /**
4
5
  * A single ads-engine offer. The `*_url` fields are tracking endpoints the engine returns fully
@@ -34,5 +35,7 @@ export declare const buildAdsParams: (input: MonetizeInput, publisherKey: string
34
35
  * Fetch live ad offers from the Rebuy ads engine. Resolves the shop's publisher key server-side (and
35
36
  * returns `[]` when the shop isn't enrolled — no ads, not an error). A non-prod upstream sends the
36
37
  * staging supply key; a non-prod/editor/QA-email request is flagged `testing` (excluded from billing).
38
+ * `prefetched` is a caller's already-resolved shop config (inline hydration threads it) — consulted only
39
+ * AFTER the gates below, so a gated-out request still costs zero upstream calls either way.
37
40
  */
38
- export declare const fetchAdsEngineOffers: (input: MonetizeInput, ctx: ServerContext) => Promise<AdsEngineOffer[]>;
41
+ export declare const fetchAdsEngineOffers: (input: MonetizeInput, ctx: ServerContext, prefetched?: CabUserConfig) => Promise<AdsEngineOffer[]>;
@@ -582,6 +582,20 @@ var CabShopConfig = import_zod6.z.object({
582
582
  apiKey: import_zod6.z.string(),
583
583
  /** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
584
584
  billingVersion: import_zod6.z.string().nullable().catch(null).default(null),
585
+ /**
586
+ * The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
587
+ * and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
588
+ * wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
589
+ * sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
590
+ * Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
591
+ * immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
592
+ * string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
593
+ * coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
594
+ * as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
595
+ * positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
596
+ * to un-busted (origin-served — slower, never stale), never a parse fail.
597
+ */
598
+ cacheKey: import_zod6.z.union([import_zod6.z.number(), import_zod6.z.string()]).transform((value) => Number(value)).pipe(import_zod6.z.number().int().positive()).optional().catch(void 0),
585
599
  /**
586
600
  * The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
587
601
  * (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
@@ -2134,7 +2148,7 @@ var normalizeTokens = (text2, knownTokens = DEFAULT_KNOWN_TOKENS) => {
2134
2148
 
2135
2149
  // src/transforms/contentBlockV1/convertContentBlockTextToV2.ts
2136
2150
  var literalText = (_field, value) => normalizeTokens(value);
2137
- var convertContentBlockTextToV2 = (language, text2 = literalText) => {
2151
+ var convertContentBlockTextToV2 = (language, text2 = literalText, textAlign = "center") => {
2138
2152
  const description = language?.description;
2139
2153
  const superTitle = language?.superTitle;
2140
2154
  const title = language?.title;
@@ -2146,20 +2160,20 @@ var convertContentBlockTextToV2 = (language, text2 = literalText) => {
2146
2160
  fontSize: "extraLarge",
2147
2161
  marks: [{ type: "bold" }],
2148
2162
  text: text2("superTitle", superTitle),
2149
- textAlign: "center"
2163
+ textAlign
2150
2164
  }) : null,
2151
2165
  title ? convertTextToTiptapParagraph({
2152
2166
  color: "base",
2153
2167
  fontSize: "medium",
2154
2168
  marks: [{ type: "bold" }],
2155
2169
  text: text2("title", title),
2156
- textAlign: "center"
2170
+ textAlign
2157
2171
  }) : null,
2158
2172
  description ? convertTextToTiptapParagraph({
2159
2173
  color: "base",
2160
2174
  fontSize: "base",
2161
2175
  text: text2("description", description),
2162
- textAlign: "center"
2176
+ textAlign
2163
2177
  }) : null
2164
2178
  ].filter((p) => p !== null),
2165
2179
  type: "doc"
@@ -3273,6 +3287,19 @@ var headerVariables = (header) => HEADER_FIELDS.flatMap((field) => {
3273
3287
  const defaultValue = header[field];
3274
3288
  return defaultValue === void 0 ? [] : [{ defaultValue, name: field, type: VariableType.text }];
3275
3289
  });
3290
+ var headerNode = (header, textAlign) => ({
3291
+ content: { en: convertContentBlockTextToV2(header, headerText, textAlign) },
3292
+ sectionType: "text"
3293
+ });
3294
+ var buildHeaderNavRow = (header) => ({
3295
+ alignment: { horizontal: "start", vertical: "bottom" },
3296
+ direction: "grid",
3297
+ grid: { columns: ["fill", "auto"] },
3298
+ name: "header",
3299
+ sections: [headerNode(header, "start"), buildCarouselNav()],
3300
+ sectionType: "layout",
3301
+ spacing: "base"
3302
+ });
3276
3303
  var HAS_PRODUCTS = "hasProducts";
3277
3304
  var hasProductsMutators = () => [
3278
3305
  {
@@ -3365,6 +3392,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3365
3392
  const pageSize = Math.max(columns, 1);
3366
3393
  const limit = settings.limit ?? void 0;
3367
3394
  const showsNav = !!layout?.carousel && !paginationOff(layout.showPaginations) && (limit === void 0 || limit > pageSize);
3395
+ const headerInChrome = showsNav && hasHeaderText(header);
3368
3396
  const fitsOneRow = limit !== void 0 && limit <= pageSize;
3369
3397
  const sideBySide = direction === "grid" || direction === "columns";
3370
3398
  const offersDirection = sideBySide && (showsNav || fitsOneRow) ? "horizontal" : "vertical";
@@ -3422,7 +3450,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3422
3450
  itemsAtOnce: Math.max(showsNav ? columns : limit ?? ENGINE_LIMIT_CEILING, 1),
3423
3451
  ...listLayout,
3424
3452
  padding: gridChrome ? paddingFor(containerBorder.style) : "none",
3425
- sections: showsNav ? [buildCarouselNav(), offers] : [offers],
3453
+ sections: showsNav ? [headerInChrome ? buildHeaderNavRow(header) : buildCarouselNav(), offers] : [offers],
3426
3454
  sectionType: "carousel"
3427
3455
  };
3428
3456
  return CABRootSection.parse({
@@ -3432,10 +3460,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3432
3460
  // The widget name is required by the admin save schema; carry the legacy widget's name through.
3433
3461
  ...name && { name },
3434
3462
  previewMode: settings.previewMode,
3435
- sections: [
3436
- ...hasHeaderText(header) ? [{ content: { en: convertContentBlockTextToV2(header, headerText) }, sectionType: "text" }] : [],
3437
- display
3438
- ],
3463
+ sections: [...hasHeaderText(header) && !headerInChrome ? [headerNode(header, "center")] : [], display],
3439
3464
  // CABTracking's schema fallbacks fill missing or null fields and the missing object; malformed values throw.
3440
3465
  tracking: settings.tracking,
3441
3466
  type: SHOPIFY_CHECKOUT_EXTENSION,
@@ -4008,10 +4033,10 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
4008
4033
  ...userAgent && { user_agent: userAgent }
4009
4034
  };
4010
4035
  };
4011
- var fetchAdsEngineOffers = async (input, ctx) => {
4036
+ var fetchAdsEngineOffers = async (input, ctx, prefetched) => {
4012
4037
  if (!input.country || !SUPPORTED_COUNTRIES.has(input.country.toUpperCase())) return [];
4013
4038
  if (!(input.sessionId || input.visitorId)) return [];
4014
- const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
4039
+ const { shop } = prefetched ?? await fetchUserConfig({ shop: input.shop }, ctx);
4015
4040
  const publisherKey = shop.monetize?.publisherKey;
4016
4041
  if (!publisherKey) return [];
4017
4042
  const isProduction = ctx.host === REBUY_ENGINE_HOST;
@@ -4169,8 +4194,10 @@ var composeOfferCard = (offer, input) => {
4169
4194
  };
4170
4195
 
4171
4196
  // src/server/monetizeOffers.ts
4172
- var fetchMonetizeOffers = async (input, ctx) => {
4173
- const composed = (await fetchAdsEngineOffers(input, ctx)).map((offer) => composeOfferCard(offer, input));
4197
+ var fetchMonetizeOffers = async (input, ctx, prefetched) => {
4198
+ const composed = (await fetchAdsEngineOffers(input, ctx, prefetched)).map(
4199
+ (offer) => composeOfferCard(offer, input)
4200
+ );
4174
4201
  const offers = composed.filter((card) => card !== void 0);
4175
4202
  if (offers.length < composed.length)
4176
4203
  ctx.reportDrift?.({
@@ -4225,8 +4252,8 @@ var dispatchWidgetTransform = (id, normalized, report) => {
4225
4252
  if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
4226
4253
  return normalized;
4227
4254
  };
4228
- var fetchProgressBarContext = async (shop, ctx) => {
4229
- const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
4255
+ var fetchProgressBarContext = async (shop, ctx, prefetched) => {
4256
+ const config = prefetched === void 0 ? await fetchUserConfig({ shop }, ctx).catch(() => void 0) : prefetched;
4230
4257
  if (!config) return {};
4231
4258
  if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
4232
4259
  ctx.reportDrift?.({
@@ -4265,7 +4292,7 @@ var findMonetizeMarker = (section) => {
4265
4292
  }
4266
4293
  return void 0;
4267
4294
  };
4268
- var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4295
+ var hydrateMonetizeMarkers = async (converted, input, ctx, config) => {
4269
4296
  if (!input.monetize || !isCABTree(converted)) return;
4270
4297
  const first = findMonetizeMarker(converted);
4271
4298
  if (!first) return;
@@ -4277,7 +4304,8 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4277
4304
  shop: input.shop,
4278
4305
  visitorId: input.monetize.visitorId || input.visitorId
4279
4306
  },
4280
- ctx
4307
+ ctx,
4308
+ config ?? void 0
4281
4309
  );
4282
4310
  first.offers = offers;
4283
4311
  } catch (err) {
@@ -4287,7 +4315,7 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4287
4315
  });
4288
4316
  }
4289
4317
  };
4290
- var fetchAndConvert = async (id, input, ctx) => {
4318
+ var fetchAndConvert = async (id, input, ctx, config) => {
4291
4319
  const params = { id: String(id), shop: input.shop };
4292
4320
  if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
4293
4321
  if (input.key) params.key = input.key;
@@ -4304,34 +4332,32 @@ var fetchAndConvert = async (id, input, ctx) => {
4304
4332
  if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
4305
4333
  const normalized = convertNumericObjects(deepCamelCase(data));
4306
4334
  if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
4307
- const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
4335
+ const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
4308
4336
  return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
4309
4337
  }
4310
4338
  if (isConvertibleProgressBarV1(normalized)) {
4311
- const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
4339
+ const { progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
4312
4340
  return convertProgressBarToV2({ id, progressColor, settings: normalized });
4313
4341
  }
4314
4342
  const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
4315
4343
  const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
4316
4344
  if (colorless.length > 0) {
4317
- const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
4345
+ const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx, config);
4318
4346
  if (color) for (const bar of colorless) bar.progressColor = color;
4319
4347
  }
4320
4348
  return converted;
4321
4349
  };
4322
- var resolveWidgetExperiment = async (input, ctx) => {
4323
- if (!input.visitorId) return void 0;
4324
- try {
4325
- const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
4326
- return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
4327
- } catch {
4328
- return void 0;
4329
- }
4330
- };
4350
+ var resolveWidgetExperiment = (input, config) => input.visitorId && config ? resolveExperiment(config.shop.activeExperiments, input.id, input.visitorId) : void 0;
4331
4351
  var convertWidgetSettings = async (input, ctx) => {
4332
- const experiment = await resolveWidgetExperiment(input, ctx);
4333
- const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
4334
- await hydrateMonetizeMarkers(result, input, ctx);
4352
+ const config = input.cacheKey != null && !input.visitorId ? void 0 : await fetchUserConfig({ shop: input.shop }, ctx).catch(() => null);
4353
+ const experiment = resolveWidgetExperiment(input, config);
4354
+ const result = await fetchAndConvert(
4355
+ experiment?.elementId ?? input.id,
4356
+ { ...input, cacheKey: input.cacheKey ?? config?.shop.cacheKey },
4357
+ ctx,
4358
+ config
4359
+ );
4360
+ await hydrateMonetizeMarkers(result, input, ctx, config);
4335
4361
  return experiment && isCABTree(result) ? { ...result, experiment } : result;
4336
4362
  };
4337
4363
 
@@ -4675,7 +4701,14 @@ var MonetizeInputSchema = import_zod29.z.object({
4675
4701
  zipCode: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
4676
4702
  });
4677
4703
  var WidgetSettingsInputSchema = import_zod29.z.object({
4678
- cacheKey: import_zod29.z.number().optional(),
4704
+ /**
4705
+ * Same domain as `CabShopConfig.cacheKey`, for the same reason: this side WINS the orchestrator's
4706
+ * `input.cacheKey ?? config.shop.cacheKey` (and an explicit value skips the config read entirely), so
4707
+ * an unconstrained `0`/negative/float here would go upstream as a constant never-rotating `cache_key`
4708
+ * and pin the edge copy permanently stale. `.catch(undefined)` because a bogus buster should degrade
4709
+ * the read to un-busted, never 400 a widget render.
4710
+ */
4711
+ cacheKey: import_zod29.z.number().int().positive().optional().catch(void 0),
4679
4712
  id: import_zod29.z.number(),
4680
4713
  key: import_zod29.z.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4681
4714
  /**
@@ -534,6 +534,20 @@ var CabShopConfig = z6.object({
534
534
  apiKey: z6.string(),
535
535
  /** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
536
536
  billingVersion: z6.string().nullable().catch(null).default(null),
537
+ /**
538
+ * The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
539
+ * and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
540
+ * wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
541
+ * sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
542
+ * Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
543
+ * immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
544
+ * string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
545
+ * coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
546
+ * as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
547
+ * positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
548
+ * to un-busted (origin-served — slower, never stale), never a parse fail.
549
+ */
550
+ cacheKey: z6.union([z6.number(), z6.string()]).transform((value) => Number(value)).pipe(z6.number().int().positive()).optional().catch(void 0),
537
551
  /**
538
552
  * The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
539
553
  * (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
@@ -2086,7 +2100,7 @@ var normalizeTokens = (text2, knownTokens = DEFAULT_KNOWN_TOKENS) => {
2086
2100
 
2087
2101
  // src/transforms/contentBlockV1/convertContentBlockTextToV2.ts
2088
2102
  var literalText = (_field, value) => normalizeTokens(value);
2089
- var convertContentBlockTextToV2 = (language, text2 = literalText) => {
2103
+ var convertContentBlockTextToV2 = (language, text2 = literalText, textAlign = "center") => {
2090
2104
  const description = language?.description;
2091
2105
  const superTitle = language?.superTitle;
2092
2106
  const title = language?.title;
@@ -2098,20 +2112,20 @@ var convertContentBlockTextToV2 = (language, text2 = literalText) => {
2098
2112
  fontSize: "extraLarge",
2099
2113
  marks: [{ type: "bold" }],
2100
2114
  text: text2("superTitle", superTitle),
2101
- textAlign: "center"
2115
+ textAlign
2102
2116
  }) : null,
2103
2117
  title ? convertTextToTiptapParagraph({
2104
2118
  color: "base",
2105
2119
  fontSize: "medium",
2106
2120
  marks: [{ type: "bold" }],
2107
2121
  text: text2("title", title),
2108
- textAlign: "center"
2122
+ textAlign
2109
2123
  }) : null,
2110
2124
  description ? convertTextToTiptapParagraph({
2111
2125
  color: "base",
2112
2126
  fontSize: "base",
2113
2127
  text: text2("description", description),
2114
- textAlign: "center"
2128
+ textAlign
2115
2129
  }) : null
2116
2130
  ].filter((p) => p !== null),
2117
2131
  type: "doc"
@@ -3225,6 +3239,19 @@ var headerVariables = (header) => HEADER_FIELDS.flatMap((field) => {
3225
3239
  const defaultValue = header[field];
3226
3240
  return defaultValue === void 0 ? [] : [{ defaultValue, name: field, type: VariableType.text }];
3227
3241
  });
3242
+ var headerNode = (header, textAlign) => ({
3243
+ content: { en: convertContentBlockTextToV2(header, headerText, textAlign) },
3244
+ sectionType: "text"
3245
+ });
3246
+ var buildHeaderNavRow = (header) => ({
3247
+ alignment: { horizontal: "start", vertical: "bottom" },
3248
+ direction: "grid",
3249
+ grid: { columns: ["fill", "auto"] },
3250
+ name: "header",
3251
+ sections: [headerNode(header, "start"), buildCarouselNav()],
3252
+ sectionType: "layout",
3253
+ spacing: "base"
3254
+ });
3228
3255
  var HAS_PRODUCTS = "hasProducts";
3229
3256
  var hasProductsMutators = () => [
3230
3257
  {
@@ -3317,6 +3344,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3317
3344
  const pageSize = Math.max(columns, 1);
3318
3345
  const limit = settings.limit ?? void 0;
3319
3346
  const showsNav = !!layout?.carousel && !paginationOff(layout.showPaginations) && (limit === void 0 || limit > pageSize);
3347
+ const headerInChrome = showsNav && hasHeaderText(header);
3320
3348
  const fitsOneRow = limit !== void 0 && limit <= pageSize;
3321
3349
  const sideBySide = direction === "grid" || direction === "columns";
3322
3350
  const offersDirection = sideBySide && (showsNav || fitsOneRow) ? "horizontal" : "vertical";
@@ -3374,7 +3402,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3374
3402
  itemsAtOnce: Math.max(showsNav ? columns : limit ?? ENGINE_LIMIT_CEILING, 1),
3375
3403
  ...listLayout,
3376
3404
  padding: gridChrome ? paddingFor(containerBorder.style) : "none",
3377
- sections: showsNav ? [buildCarouselNav(), offers] : [offers],
3405
+ sections: showsNav ? [headerInChrome ? buildHeaderNavRow(header) : buildCarouselNav(), offers] : [offers],
3378
3406
  sectionType: "carousel"
3379
3407
  };
3380
3408
  return CABRootSection.parse({
@@ -3384,10 +3412,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3384
3412
  // The widget name is required by the admin save schema; carry the legacy widget's name through.
3385
3413
  ...name && { name },
3386
3414
  previewMode: settings.previewMode,
3387
- sections: [
3388
- ...hasHeaderText(header) ? [{ content: { en: convertContentBlockTextToV2(header, headerText) }, sectionType: "text" }] : [],
3389
- display
3390
- ],
3415
+ sections: [...hasHeaderText(header) && !headerInChrome ? [headerNode(header, "center")] : [], display],
3391
3416
  // CABTracking's schema fallbacks fill missing or null fields and the missing object; malformed values throw.
3392
3417
  tracking: settings.tracking,
3393
3418
  type: SHOPIFY_CHECKOUT_EXTENSION,
@@ -3960,10 +3985,10 @@ var buildAdsParams = (input, publisherKey, supplyKey, testing, userAgent, ipAddr
3960
3985
  ...userAgent && { user_agent: userAgent }
3961
3986
  };
3962
3987
  };
3963
- var fetchAdsEngineOffers = async (input, ctx) => {
3988
+ var fetchAdsEngineOffers = async (input, ctx, prefetched) => {
3964
3989
  if (!input.country || !SUPPORTED_COUNTRIES.has(input.country.toUpperCase())) return [];
3965
3990
  if (!(input.sessionId || input.visitorId)) return [];
3966
- const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
3991
+ const { shop } = prefetched ?? await fetchUserConfig({ shop: input.shop }, ctx);
3967
3992
  const publisherKey = shop.monetize?.publisherKey;
3968
3993
  if (!publisherKey) return [];
3969
3994
  const isProduction = ctx.host === REBUY_ENGINE_HOST;
@@ -4121,8 +4146,10 @@ var composeOfferCard = (offer, input) => {
4121
4146
  };
4122
4147
 
4123
4148
  // src/server/monetizeOffers.ts
4124
- var fetchMonetizeOffers = async (input, ctx) => {
4125
- const composed = (await fetchAdsEngineOffers(input, ctx)).map((offer) => composeOfferCard(offer, input));
4149
+ var fetchMonetizeOffers = async (input, ctx, prefetched) => {
4150
+ const composed = (await fetchAdsEngineOffers(input, ctx, prefetched)).map(
4151
+ (offer) => composeOfferCard(offer, input)
4152
+ );
4126
4153
  const offers = composed.filter((card) => card !== void 0);
4127
4154
  if (offers.length < composed.length)
4128
4155
  ctx.reportDrift?.({
@@ -4177,8 +4204,8 @@ var dispatchWidgetTransform = (id, normalized, report) => {
4177
4204
  if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
4178
4205
  return normalized;
4179
4206
  };
4180
- var fetchProgressBarContext = async (shop, ctx) => {
4181
- const config = await fetchUserConfig({ shop }, ctx).catch(() => void 0);
4207
+ var fetchProgressBarContext = async (shop, ctx, prefetched) => {
4208
+ const config = prefetched === void 0 ? await fetchUserConfig({ shop }, ctx).catch(() => void 0) : prefetched;
4182
4209
  if (!config) return {};
4183
4210
  if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
4184
4211
  ctx.reportDrift?.({
@@ -4217,7 +4244,7 @@ var findMonetizeMarker = (section) => {
4217
4244
  }
4218
4245
  return void 0;
4219
4246
  };
4220
- var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4247
+ var hydrateMonetizeMarkers = async (converted, input, ctx, config) => {
4221
4248
  if (!input.monetize || !isCABTree(converted)) return;
4222
4249
  const first = findMonetizeMarker(converted);
4223
4250
  if (!first) return;
@@ -4229,7 +4256,8 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4229
4256
  shop: input.shop,
4230
4257
  visitorId: input.monetize.visitorId || input.visitorId
4231
4258
  },
4232
- ctx
4259
+ ctx,
4260
+ config ?? void 0
4233
4261
  );
4234
4262
  first.offers = offers;
4235
4263
  } catch (err) {
@@ -4239,7 +4267,7 @@ var hydrateMonetizeMarkers = async (converted, input, ctx) => {
4239
4267
  });
4240
4268
  }
4241
4269
  };
4242
- var fetchAndConvert = async (id, input, ctx) => {
4270
+ var fetchAndConvert = async (id, input, ctx, config) => {
4243
4271
  const params = { id: String(id), shop: input.shop };
4244
4272
  if (input.cacheKey != null) params.cache_key = String(input.cacheKey);
4245
4273
  if (input.key) params.key = input.key;
@@ -4256,34 +4284,32 @@ var fetchAndConvert = async (id, input, ctx) => {
4256
4284
  if (isMissingUpstreamPayload(data)) throw new NotFoundError(`Widget not found: ${id}`);
4257
4285
  const normalized = convertNumericObjects(deepCamelCase(data));
4258
4286
  if (isProgressBarV1(normalized) && normalized.barSettings.barType === "smart-cart") {
4259
- const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx);
4287
+ const { bars, progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
4260
4288
  return convertSmartCartProgressBarToV2({ bars: bars ?? [], id, progressColor, settings: normalized });
4261
4289
  }
4262
4290
  if (isConvertibleProgressBarV1(normalized)) {
4263
- const { progressColor } = await fetchProgressBarContext(input.shop, ctx);
4291
+ const { progressColor } = await fetchProgressBarContext(input.shop, ctx, config);
4264
4292
  return convertProgressBarToV2({ id, progressColor, settings: normalized });
4265
4293
  }
4266
4294
  const converted = dispatchWidgetTransform(id, normalized, ctx.reportDrift);
4267
4295
  const colorless = isCABRootSection(normalized) ? colorlessProgressBars(converted) : [];
4268
4296
  if (colorless.length > 0) {
4269
- const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx);
4297
+ const { progressColor: color } = await fetchProgressBarContext(input.shop, ctx, config);
4270
4298
  if (color) for (const bar of colorless) bar.progressColor = color;
4271
4299
  }
4272
4300
  return converted;
4273
4301
  };
4274
- var resolveWidgetExperiment = async (input, ctx) => {
4275
- if (!input.visitorId) return void 0;
4276
- try {
4277
- const { shop } = await fetchUserConfig({ shop: input.shop }, ctx);
4278
- return resolveExperiment(shop.activeExperiments, input.id, input.visitorId);
4279
- } catch {
4280
- return void 0;
4281
- }
4282
- };
4302
+ var resolveWidgetExperiment = (input, config) => input.visitorId && config ? resolveExperiment(config.shop.activeExperiments, input.id, input.visitorId) : void 0;
4283
4303
  var convertWidgetSettings = async (input, ctx) => {
4284
- const experiment = await resolveWidgetExperiment(input, ctx);
4285
- const result = await fetchAndConvert(experiment?.elementId ?? input.id, input, ctx);
4286
- await hydrateMonetizeMarkers(result, input, ctx);
4304
+ const config = input.cacheKey != null && !input.visitorId ? void 0 : await fetchUserConfig({ shop: input.shop }, ctx).catch(() => null);
4305
+ const experiment = resolveWidgetExperiment(input, config);
4306
+ const result = await fetchAndConvert(
4307
+ experiment?.elementId ?? input.id,
4308
+ { ...input, cacheKey: input.cacheKey ?? config?.shop.cacheKey },
4309
+ ctx,
4310
+ config
4311
+ );
4312
+ await hydrateMonetizeMarkers(result, input, ctx, config);
4287
4313
  return experiment && isCABTree(result) ? { ...result, experiment } : result;
4288
4314
  };
4289
4315
 
@@ -4627,7 +4653,14 @@ var MonetizeInputSchema = z29.object({
4627
4653
  zipCode: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional()
4628
4654
  });
4629
4655
  var WidgetSettingsInputSchema = z29.object({
4630
- cacheKey: z29.number().optional(),
4656
+ /**
4657
+ * Same domain as `CabShopConfig.cacheKey`, for the same reason: this side WINS the orchestrator's
4658
+ * `input.cacheKey ?? config.shop.cacheKey` (and an explicit value skips the config read entirely), so
4659
+ * an unconstrained `0`/negative/float here would go upstream as a constant never-rotating `cache_key`
4660
+ * and pin the edge copy permanently stale. `.catch(undefined)` because a bogus buster should degrade
4661
+ * the read to un-busted, never 400 a widget render.
4662
+ */
4663
+ cacheKey: z29.number().int().positive().optional().catch(void 0),
4631
4664
  id: z29.number(),
4632
4665
  key: z29.string().max(REQUEST_BOUNDS.STRING_ID_MAX).optional(),
4633
4666
  /**
@@ -1,3 +1,4 @@
1
+ import { type CabUserConfig } from '../schema/cabShopConfig';
1
2
  import { type CABSection } from '../schema/widgets/checkout-and-beyond';
2
3
  import { type MonetizeInput, type ServerContext } from '../server/shared';
3
4
  export type MonetizeOffers = {
@@ -8,6 +9,7 @@ export type MonetizeOffers = {
8
9
  * offer card the extension renders directly. The header chrome (experiment copy + order-confirmed
9
10
  * line) is baked inside every card — inside its border, as React draws it — so the response no longer
10
11
  * carries a separate `header` (older extension builds read it as optional and render nothing). Empty
11
- * when the shop isn't ad-enrolled.
12
+ * when the shop isn't ad-enrolled. `prefetched` is a caller's already-resolved shop config, passed
13
+ * through to the ads layer's publisher-key lookup so inline hydration doesn't refetch it.
12
14
  */
13
- export declare const fetchMonetizeOffers: (input: MonetizeInput, ctx: ServerContext) => Promise<MonetizeOffers>;
15
+ export declare const fetchMonetizeOffers: (input: MonetizeInput, ctx: ServerContext, prefetched?: CabUserConfig) => Promise<MonetizeOffers>;
@@ -233,7 +233,7 @@ export declare const MonetizeInputSchema: z.ZodObject<{
233
233
  }, z.core.$strip>;
234
234
  /** Input for `convertWidgetSettings`. */
235
235
  export declare const WidgetSettingsInputSchema: z.ZodObject<{
236
- cacheKey: z.ZodOptional<z.ZodNumber>;
236
+ cacheKey: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
237
237
  id: z.ZodNumber;
238
238
  key: z.ZodOptional<z.ZodString>;
239
239
  monetize: z.ZodOptional<z.ZodObject<{
@@ -7,5 +7,7 @@ export type HeaderTextResolver = (field: HeaderField, value: string) => string;
7
7
  * A paragraph per truthy field, styled as the legacy header stack. `text` swaps what each paragraph
8
8
  * carries — the offers converter hands over `{{token}}` references so data sources can override the
9
9
  * copy — while the truthiness gate (which fields exist at all) stays the merchant's authored copy.
10
+ * `textAlign` follows placement: legacy centers the header above a list but left-aligns it inside a
11
+ * carousel's chrome row (`HeaderBlock`'s `inlineAlignment = isCarousel ? 'start' : 'center'`).
10
12
  */
11
- export declare const convertContentBlockTextToV2: (language: V1Language | undefined, text?: HeaderTextResolver) => TiptapDocument;
13
+ export declare const convertContentBlockTextToV2: (language: V1Language | undefined, text?: HeaderTextResolver, textAlign?: "center" | "start") => TiptapDocument;
@@ -1571,7 +1571,7 @@ var normalizeTokens = (text, knownTokens = DEFAULT_KNOWN_TOKENS) => {
1571
1571
 
1572
1572
  // src/transforms/contentBlockV1/convertContentBlockTextToV2.ts
1573
1573
  var literalText = (_field, value) => normalizeTokens(value);
1574
- var convertContentBlockTextToV2 = (language, text = literalText) => {
1574
+ var convertContentBlockTextToV2 = (language, text = literalText, textAlign = "center") => {
1575
1575
  const description = language?.description;
1576
1576
  const superTitle = language?.superTitle;
1577
1577
  const title = language?.title;
@@ -1583,20 +1583,20 @@ var convertContentBlockTextToV2 = (language, text = literalText) => {
1583
1583
  fontSize: "extraLarge",
1584
1584
  marks: [{ type: "bold" }],
1585
1585
  text: text("superTitle", superTitle),
1586
- textAlign: "center"
1586
+ textAlign
1587
1587
  }) : null,
1588
1588
  title ? convertTextToTiptapParagraph({
1589
1589
  color: "base",
1590
1590
  fontSize: "medium",
1591
1591
  marks: [{ type: "bold" }],
1592
1592
  text: text("title", title),
1593
- textAlign: "center"
1593
+ textAlign
1594
1594
  }) : null,
1595
1595
  description ? convertTextToTiptapParagraph({
1596
1596
  color: "base",
1597
1597
  fontSize: "base",
1598
1598
  text: text("description", description),
1599
- textAlign: "center"
1599
+ textAlign
1600
1600
  }) : null
1601
1601
  ].filter((p) => p !== null),
1602
1602
  type: "doc"
@@ -3141,6 +3141,19 @@ var headerVariables = (header) => HEADER_FIELDS.flatMap((field) => {
3141
3141
  const defaultValue = header[field];
3142
3142
  return defaultValue === void 0 ? [] : [{ defaultValue, name: field, type: VariableType.text }];
3143
3143
  });
3144
+ var headerNode = (header, textAlign) => ({
3145
+ content: { en: convertContentBlockTextToV2(header, headerText, textAlign) },
3146
+ sectionType: "text"
3147
+ });
3148
+ var buildHeaderNavRow = (header) => ({
3149
+ alignment: { horizontal: "start", vertical: "bottom" },
3150
+ direction: "grid",
3151
+ grid: { columns: ["fill", "auto"] },
3152
+ name: "header",
3153
+ sections: [headerNode(header, "start"), buildCarouselNav()],
3154
+ sectionType: "layout",
3155
+ spacing: "base"
3156
+ });
3144
3157
  var HAS_PRODUCTS = "hasProducts";
3145
3158
  var hasProductsMutators = () => [
3146
3159
  {
@@ -3233,6 +3246,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3233
3246
  const pageSize = Math.max(columns, 1);
3234
3247
  const limit = settings.limit ?? void 0;
3235
3248
  const showsNav = !!layout?.carousel && !paginationOff(layout.showPaginations) && (limit === void 0 || limit > pageSize);
3249
+ const headerInChrome = showsNav && hasHeaderText(header);
3236
3250
  const fitsOneRow = limit !== void 0 && limit <= pageSize;
3237
3251
  const sideBySide = direction === "grid" || direction === "columns";
3238
3252
  const offersDirection = sideBySide && (showsNav || fitsOneRow) ? "horizontal" : "vertical";
@@ -3290,7 +3304,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3290
3304
  itemsAtOnce: Math.max(showsNav ? columns : limit ?? ENGINE_LIMIT_CEILING, 1),
3291
3305
  ...listLayout,
3292
3306
  padding: gridChrome ? paddingFor(containerBorder.style) : "none",
3293
- sections: showsNav ? [buildCarouselNav(), offers] : [offers],
3307
+ sections: showsNav ? [headerInChrome ? buildHeaderNavRow(header) : buildCarouselNav(), offers] : [offers],
3294
3308
  sectionType: "carousel"
3295
3309
  };
3296
3310
  return CABRootSection.parse({
@@ -3300,10 +3314,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3300
3314
  // The widget name is required by the admin save schema; carry the legacy widget's name through.
3301
3315
  ...name && { name },
3302
3316
  previewMode: settings.previewMode,
3303
- sections: [
3304
- ...hasHeaderText(header) ? [{ content: { en: convertContentBlockTextToV2(header, headerText) }, sectionType: "text" }] : [],
3305
- display
3306
- ],
3317
+ sections: [...hasHeaderText(header) && !headerInChrome ? [headerNode(header, "center")] : [], display],
3307
3318
  // CABTracking's schema fallbacks fill missing or null fields and the missing object; malformed values throw.
3308
3319
  tracking: settings.tracking,
3309
3320
  type: SHOPIFY_CHECKOUT_EXTENSION,
@@ -1514,7 +1514,7 @@ var normalizeTokens = (text, knownTokens = DEFAULT_KNOWN_TOKENS) => {
1514
1514
 
1515
1515
  // src/transforms/contentBlockV1/convertContentBlockTextToV2.ts
1516
1516
  var literalText = (_field, value) => normalizeTokens(value);
1517
- var convertContentBlockTextToV2 = (language, text = literalText) => {
1517
+ var convertContentBlockTextToV2 = (language, text = literalText, textAlign = "center") => {
1518
1518
  const description = language?.description;
1519
1519
  const superTitle = language?.superTitle;
1520
1520
  const title = language?.title;
@@ -1526,20 +1526,20 @@ var convertContentBlockTextToV2 = (language, text = literalText) => {
1526
1526
  fontSize: "extraLarge",
1527
1527
  marks: [{ type: "bold" }],
1528
1528
  text: text("superTitle", superTitle),
1529
- textAlign: "center"
1529
+ textAlign
1530
1530
  }) : null,
1531
1531
  title ? convertTextToTiptapParagraph({
1532
1532
  color: "base",
1533
1533
  fontSize: "medium",
1534
1534
  marks: [{ type: "bold" }],
1535
1535
  text: text("title", title),
1536
- textAlign: "center"
1536
+ textAlign
1537
1537
  }) : null,
1538
1538
  description ? convertTextToTiptapParagraph({
1539
1539
  color: "base",
1540
1540
  fontSize: "base",
1541
1541
  text: text("description", description),
1542
- textAlign: "center"
1542
+ textAlign
1543
1543
  }) : null
1544
1544
  ].filter((p) => p !== null),
1545
1545
  type: "doc"
@@ -3084,6 +3084,19 @@ var headerVariables = (header) => HEADER_FIELDS.flatMap((field) => {
3084
3084
  const defaultValue = header[field];
3085
3085
  return defaultValue === void 0 ? [] : [{ defaultValue, name: field, type: VariableType.text }];
3086
3086
  });
3087
+ var headerNode = (header, textAlign) => ({
3088
+ content: { en: convertContentBlockTextToV2(header, headerText, textAlign) },
3089
+ sectionType: "text"
3090
+ });
3091
+ var buildHeaderNavRow = (header) => ({
3092
+ alignment: { horizontal: "start", vertical: "bottom" },
3093
+ direction: "grid",
3094
+ grid: { columns: ["fill", "auto"] },
3095
+ name: "header",
3096
+ sections: [headerNode(header, "start"), buildCarouselNav()],
3097
+ sectionType: "layout",
3098
+ spacing: "base"
3099
+ });
3087
3100
  var HAS_PRODUCTS = "hasProducts";
3088
3101
  var hasProductsMutators = () => [
3089
3102
  {
@@ -3176,6 +3189,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3176
3189
  const pageSize = Math.max(columns, 1);
3177
3190
  const limit = settings.limit ?? void 0;
3178
3191
  const showsNav = !!layout?.carousel && !paginationOff(layout.showPaginations) && (limit === void 0 || limit > pageSize);
3192
+ const headerInChrome = showsNav && hasHeaderText(header);
3179
3193
  const fitsOneRow = limit !== void 0 && limit <= pageSize;
3180
3194
  const sideBySide = direction === "grid" || direction === "columns";
3181
3195
  const offersDirection = sideBySide && (showsNav || fitsOneRow) ? "horizontal" : "vertical";
@@ -3233,7 +3247,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3233
3247
  itemsAtOnce: Math.max(showsNav ? columns : limit ?? ENGINE_LIMIT_CEILING, 1),
3234
3248
  ...listLayout,
3235
3249
  padding: gridChrome ? paddingFor(containerBorder.style) : "none",
3236
- sections: showsNav ? [buildCarouselNav(), offers] : [offers],
3250
+ sections: showsNav ? [headerInChrome ? buildHeaderNavRow(header) : buildCarouselNav(), offers] : [offers],
3237
3251
  sectionType: "carousel"
3238
3252
  };
3239
3253
  return CABRootSection.parse({
@@ -3243,10 +3257,7 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3243
3257
  // The widget name is required by the admin save schema; carry the legacy widget's name through.
3244
3258
  ...name && { name },
3245
3259
  previewMode: settings.previewMode,
3246
- sections: [
3247
- ...hasHeaderText(header) ? [{ content: { en: convertContentBlockTextToV2(header, headerText) }, sectionType: "text" }] : [],
3248
- display
3249
- ],
3260
+ sections: [...hasHeaderText(header) && !headerInChrome ? [headerNode(header, "center")] : [], display],
3250
3261
  // CABTracking's schema fallbacks fill missing or null fields and the missing object; malformed values throw.
3251
3262
  tracking: settings.tracking,
3252
3263
  type: SHOPIFY_CHECKOUT_EXTENSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebuy/rebuy",
3
- "version": "3.12.1",
3
+ "version": "3.14.0",
4
4
  "description": "Shared zod schemas, legacy-to-CAB widget transforms, and server orchestrators for Rebuy's Shopify consumers (rebuy-api, admin-nextjs, rebuy-shopify-extensions)",
5
5
  "license": "MIT",
6
6
  "author": "Rebuy, Inc.",