@rebuy/rebuy 3.60.1 → 3.60.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.
package/dist/index.cjs CHANGED
@@ -1212,8 +1212,13 @@ var import_zod2 = require("zod");
1212
1212
  // src/schema/driftedMarkets.ts
1213
1213
  var DRIFTED_MARKETS = Object.freeze({ enabled: false });
1214
1214
 
1215
+ // src/schema/driftedSmartCart.ts
1216
+ var DRIFTED_SMART_CART_SETTINGS = Object.freeze({});
1217
+ var DRIFTED_SMART_CART = Object.freeze({});
1218
+
1215
1219
  // src/schema/smartCart.ts
1216
1220
  var import_zod = require("zod");
1221
+ var phpEmptyMapAsAbsent = (value) => Array.isArray(value) && value.length === 0 ? void 0 : value;
1217
1222
  var SmartCartTierProduct = import_zod.z.looseObject({
1218
1223
  handle: import_zod.z.string().optional(),
1219
1224
  id: import_zod.z.coerce.number().optional(),
@@ -1267,7 +1272,11 @@ var SmartCartTier = import_zod.z.looseObject({
1267
1272
  * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
1268
1273
  */
1269
1274
  minimum: import_zod.z.preprocess((value) => value == null ? void 0 : value, import_zod.z.coerce.number().optional()),
1270
- presentmentCurrencies: import_zod.z.looseObject({ enabled: import_zod.z.boolean().optional() }).optional(),
1275
+ /** An empty map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed tier. */
1276
+ presentmentCurrencies: import_zod.z.preprocess(
1277
+ phpEmptyMapAsAbsent,
1278
+ import_zod.z.looseObject({ enabled: import_zod.z.boolean().optional() }).optional()
1279
+ ),
1271
1280
  productAmountReachedLabel: import_zod.z.boolean().optional(),
1272
1281
  productAmountReachedLabelText: import_zod.z.string().optional(),
1273
1282
  productAmountRemainingLabel: import_zod.z.boolean().optional(),
@@ -1320,16 +1329,35 @@ var SmartCartBar = import_zod.z.looseObject({
1320
1329
  freeShippingOnSubscription: import_zod.z.boolean().optional(),
1321
1330
  geolocationEnabled: import_zod.z.boolean().optional(),
1322
1331
  id: import_zod.z.string().optional(),
1323
- language: import_zod.z.looseObject({
1324
- freeShippingReached: import_zod.z.string().optional(),
1325
- freeShippingRemaining: import_zod.z.string().optional(),
1326
- giftAreaTitle: import_zod.z.string().optional()
1327
- }).optional(),
1332
+ /** An empty `language` map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed bar. */
1333
+ language: import_zod.z.preprocess(
1334
+ phpEmptyMapAsAbsent,
1335
+ import_zod.z.looseObject({
1336
+ freeShippingReached: import_zod.z.string().optional(),
1337
+ freeShippingRemaining: import_zod.z.string().optional(),
1338
+ giftAreaTitle: import_zod.z.string().optional()
1339
+ }).optional()
1340
+ ),
1328
1341
  name: import_zod.z.string().optional(),
1329
1342
  tiers: import_zod.z.array(SmartCartTier).optional()
1330
1343
  });
1331
1344
  var SmartCartComponent = import_zod.z.looseObject({
1332
- settings: import_zod.z.looseObject({ bars: import_zod.z.array(SmartCartBar).optional() }).optional(),
1345
+ /**
1346
+ * Every component in the layout is parsed, not just the progress bar, so this field is the blast-radius
1347
+ * boundary (REB-25139): before it caught, b.box's `cart_items` component arriving as `settings: []` failed
1348
+ * the component, the `components` array and the whole config — and `CabUserConfig`'s config-level catch
1349
+ * then silently dropped every progress bar for the shop. Now:
1350
+ *
1351
+ * - PHP's empty-map `[]` reads as absent ({@link phpEmptyMapAsAbsent}) — normal engine vocabulary, no drift.
1352
+ * - Any other unreadable settings (a string, a non-empty array, a bar that fails its schema) `.catch` to the
1353
+ * {@link DRIFTED_SMART_CART_SETTINGS} identity sentinel: that component loses its settings (no bars),
1354
+ * its siblings — and the progress bar, when it is a sibling — still parse, and the server's progress-bar
1355
+ * resolution emits a `shopConfig` drift on seeing the sentinel.
1356
+ *
1357
+ * Bars are not salvaged per element: the only reader takes the first progress-bar component's bars as a
1358
+ * set (one per geo segment), and a drifted bar now costs that component's bars WITH a drift, not the cart.
1359
+ */
1360
+ settings: import_zod.z.preprocess(phpEmptyMapAsAbsent, import_zod.z.looseObject({ bars: import_zod.z.array(SmartCartBar).optional() }).optional()).catch(DRIFTED_SMART_CART_SETTINGS),
1333
1361
  type: import_zod.z.string().optional()
1334
1362
  });
1335
1363
  var SmartCartConfig = import_zod.z.looseObject({
@@ -1432,7 +1460,7 @@ var CabShopConfig = import_zod2.z.object({
1432
1460
  });
1433
1461
  var CabUserConfig = import_zod2.z.object({
1434
1462
  shop: CabShopConfig,
1435
- smartCart: SmartCartConfig.nullish().catch(void 0)
1463
+ smartCart: SmartCartConfig.nullish().catch(DRIFTED_SMART_CART)
1436
1464
  });
1437
1465
 
1438
1466
  // src/schema/metadata.ts
package/dist/index.mjs CHANGED
@@ -730,8 +730,13 @@ import { z as z2 } from "zod";
730
730
  // src/schema/driftedMarkets.ts
731
731
  var DRIFTED_MARKETS = Object.freeze({ enabled: false });
732
732
 
733
+ // src/schema/driftedSmartCart.ts
734
+ var DRIFTED_SMART_CART_SETTINGS = Object.freeze({});
735
+ var DRIFTED_SMART_CART = Object.freeze({});
736
+
733
737
  // src/schema/smartCart.ts
734
738
  import { z } from "zod";
739
+ var phpEmptyMapAsAbsent = (value) => Array.isArray(value) && value.length === 0 ? void 0 : value;
735
740
  var SmartCartTierProduct = z.looseObject({
736
741
  handle: z.string().optional(),
737
742
  id: z.coerce.number().optional(),
@@ -785,7 +790,11 @@ var SmartCartTier = z.looseObject({
785
790
  * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
786
791
  */
787
792
  minimum: z.preprocess((value) => value == null ? void 0 : value, z.coerce.number().optional()),
788
- presentmentCurrencies: z.looseObject({ enabled: z.boolean().optional() }).optional(),
793
+ /** An empty map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed tier. */
794
+ presentmentCurrencies: z.preprocess(
795
+ phpEmptyMapAsAbsent,
796
+ z.looseObject({ enabled: z.boolean().optional() }).optional()
797
+ ),
789
798
  productAmountReachedLabel: z.boolean().optional(),
790
799
  productAmountReachedLabelText: z.string().optional(),
791
800
  productAmountRemainingLabel: z.boolean().optional(),
@@ -838,16 +847,35 @@ var SmartCartBar = z.looseObject({
838
847
  freeShippingOnSubscription: z.boolean().optional(),
839
848
  geolocationEnabled: z.boolean().optional(),
840
849
  id: z.string().optional(),
841
- language: z.looseObject({
842
- freeShippingReached: z.string().optional(),
843
- freeShippingRemaining: z.string().optional(),
844
- giftAreaTitle: z.string().optional()
845
- }).optional(),
850
+ /** An empty `language` map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed bar. */
851
+ language: z.preprocess(
852
+ phpEmptyMapAsAbsent,
853
+ z.looseObject({
854
+ freeShippingReached: z.string().optional(),
855
+ freeShippingRemaining: z.string().optional(),
856
+ giftAreaTitle: z.string().optional()
857
+ }).optional()
858
+ ),
846
859
  name: z.string().optional(),
847
860
  tiers: z.array(SmartCartTier).optional()
848
861
  });
849
862
  var SmartCartComponent = z.looseObject({
850
- settings: z.looseObject({ bars: z.array(SmartCartBar).optional() }).optional(),
863
+ /**
864
+ * Every component in the layout is parsed, not just the progress bar, so this field is the blast-radius
865
+ * boundary (REB-25139): before it caught, b.box's `cart_items` component arriving as `settings: []` failed
866
+ * the component, the `components` array and the whole config — and `CabUserConfig`'s config-level catch
867
+ * then silently dropped every progress bar for the shop. Now:
868
+ *
869
+ * - PHP's empty-map `[]` reads as absent ({@link phpEmptyMapAsAbsent}) — normal engine vocabulary, no drift.
870
+ * - Any other unreadable settings (a string, a non-empty array, a bar that fails its schema) `.catch` to the
871
+ * {@link DRIFTED_SMART_CART_SETTINGS} identity sentinel: that component loses its settings (no bars),
872
+ * its siblings — and the progress bar, when it is a sibling — still parse, and the server's progress-bar
873
+ * resolution emits a `shopConfig` drift on seeing the sentinel.
874
+ *
875
+ * Bars are not salvaged per element: the only reader takes the first progress-bar component's bars as a
876
+ * set (one per geo segment), and a drifted bar now costs that component's bars WITH a drift, not the cart.
877
+ */
878
+ settings: z.preprocess(phpEmptyMapAsAbsent, z.looseObject({ bars: z.array(SmartCartBar).optional() }).optional()).catch(DRIFTED_SMART_CART_SETTINGS),
851
879
  type: z.string().optional()
852
880
  });
853
881
  var SmartCartConfig = z.looseObject({
@@ -950,7 +978,7 @@ var CabShopConfig = z2.object({
950
978
  });
951
979
  var CabUserConfig = z2.object({
952
980
  shop: CabShopConfig,
953
- smartCart: SmartCartConfig.nullish().catch(void 0)
981
+ smartCart: SmartCartConfig.nullish().catch(DRIFTED_SMART_CART)
954
982
  });
955
983
 
956
984
  // src/schema/metadata.ts
@@ -59,6 +59,11 @@ export type CabShopConfig = z.infer<typeof CabShopConfig>;
59
59
  * The lean `/api/v1/user/config` projection `fetchUserConfig` parses. `smartFlows` (schema-required on
60
60
  * the full `UserConfig` but read by no CAB path) is dropped; `smartCart` keeps its `.catch()`
61
61
  * safety bar so a smart-cart shape drift can never fail apiKey resolution.
62
+ *
63
+ * The catch lands on the {@link DRIFTED_SMART_CART} identity sentinel (no components ⇒ no bars), not
64
+ * `undefined`, so the server's progress-bar resolution can tell a drifted config from a shop with no smart
65
+ * cart (`null`/absent stay `null`/`undefined`) and emit a `shopConfig` drift (REB-25139: this catch used to
66
+ * drop every progress bar for the shop silently).
62
67
  */
63
68
  export declare const CabUserConfig: z.ZodObject<{
64
69
  shop: z.ZodObject<{
@@ -95,7 +100,7 @@ export declare const CabUserConfig: z.ZodObject<{
95
100
  }, z.core.$strip>;
96
101
  smartCart: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
97
102
  components: z.ZodOptional<z.ZodArray<z.ZodObject<{
98
- settings: z.ZodOptional<z.ZodObject<{
103
+ settings: z.ZodCatch<z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
99
104
  bars: z.ZodOptional<z.ZodArray<z.ZodObject<{
100
105
  countryCodes: z.ZodOptional<z.ZodArray<z.ZodString>>;
101
106
  excludeDiscountInSubtotal: z.ZodOptional<z.ZodBoolean>;
@@ -105,11 +110,11 @@ export declare const CabUserConfig: z.ZodObject<{
105
110
  freeShippingOnSubscription: z.ZodOptional<z.ZodBoolean>;
106
111
  geolocationEnabled: z.ZodOptional<z.ZodBoolean>;
107
112
  id: z.ZodOptional<z.ZodString>;
108
- language: z.ZodOptional<z.ZodObject<{
113
+ language: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
109
114
  freeShippingReached: z.ZodOptional<z.ZodString>;
110
115
  freeShippingRemaining: z.ZodOptional<z.ZodString>;
111
116
  giftAreaTitle: z.ZodOptional<z.ZodString>;
112
- }, z.core.$loose>>;
117
+ }, z.core.$loose>>>;
113
118
  name: z.ZodOptional<z.ZodString>;
114
119
  tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
115
120
  allProducts: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -141,9 +146,9 @@ export declare const CabUserConfig: z.ZodObject<{
141
146
  filterOosVariants: z.ZodOptional<z.ZodBoolean>;
142
147
  id: z.ZodOptional<z.ZodString>;
143
148
  minimum: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
144
- presentmentCurrencies: z.ZodOptional<z.ZodObject<{
149
+ presentmentCurrencies: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
145
150
  enabled: z.ZodOptional<z.ZodBoolean>;
146
- }, z.core.$loose>>;
151
+ }, z.core.$loose>>>;
147
152
  productAmountReachedLabel: z.ZodOptional<z.ZodBoolean>;
148
153
  productAmountReachedLabelText: z.ZodOptional<z.ZodString>;
149
154
  productAmountRemainingLabel: z.ZodOptional<z.ZodBoolean>;
@@ -161,7 +166,7 @@ export declare const CabUserConfig: z.ZodObject<{
161
166
  unlockConditionType: z.ZodOptional<z.ZodString>;
162
167
  }, z.core.$loose>>>;
163
168
  }, z.core.$loose>>>;
164
- }, z.core.$loose>>;
169
+ }, z.core.$loose>>>>;
165
170
  type: z.ZodOptional<z.ZodString>;
166
171
  }, z.core.$loose>>>;
167
172
  }, z.core.$loose>>>>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Identity sentinels for the smart-cart salvages (REB-25139), on the {@link file://./driftedMarkets.ts} pattern:
3
+ * the schema's `.catch` returns one of THESE frozen objects, and the server's progress-bar resolution detects the
4
+ * drift by reference and emits a `shopConfig` drift. Both read as "no bars" to every consumer — `settings: {}`
5
+ * carries no `bars`, a config `{}` carries no `components` — so a consumer that never checks identity degrades
6
+ * exactly as it would on a missing field.
7
+ *
8
+ * Deliberately NOT re-exported from `src/schema/index.ts` for the same reason as `DRIFTED_MARKETS`: identity only
9
+ * holds within one bundle, and the comparison lives in `./server` (`server/widgetSettings.ts`), where the
10
+ * `CabUserConfig` parse that produced the value also runs.
11
+ */
12
+ /** A smart-cart component whose `settings` failed to parse: dropped to no settings, its siblings kept. */
13
+ export declare const DRIFTED_SMART_CART_SETTINGS: Readonly<{}>;
14
+ /** A smart-cart config that failed to parse as a whole (e.g. a non-array `components`): no components. */
15
+ export declare const DRIFTED_SMART_CART: Readonly<{}>;
@@ -69,9 +69,9 @@ export declare const SmartCartTier: z.ZodObject<{
69
69
  filterOosVariants: z.ZodOptional<z.ZodBoolean>;
70
70
  id: z.ZodOptional<z.ZodString>;
71
71
  minimum: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
72
- presentmentCurrencies: z.ZodOptional<z.ZodObject<{
72
+ presentmentCurrencies: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
73
73
  enabled: z.ZodOptional<z.ZodBoolean>;
74
- }, z.core.$loose>>;
74
+ }, z.core.$loose>>>;
75
75
  productAmountReachedLabel: z.ZodOptional<z.ZodBoolean>;
76
76
  productAmountReachedLabelText: z.ZodOptional<z.ZodString>;
77
77
  productAmountRemainingLabel: z.ZodOptional<z.ZodBoolean>;
@@ -102,11 +102,11 @@ export declare const SmartCartBar: z.ZodObject<{
102
102
  freeShippingOnSubscription: z.ZodOptional<z.ZodBoolean>;
103
103
  geolocationEnabled: z.ZodOptional<z.ZodBoolean>;
104
104
  id: z.ZodOptional<z.ZodString>;
105
- language: z.ZodOptional<z.ZodObject<{
105
+ language: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
106
106
  freeShippingReached: z.ZodOptional<z.ZodString>;
107
107
  freeShippingRemaining: z.ZodOptional<z.ZodString>;
108
108
  giftAreaTitle: z.ZodOptional<z.ZodString>;
109
- }, z.core.$loose>>;
109
+ }, z.core.$loose>>>;
110
110
  name: z.ZodOptional<z.ZodString>;
111
111
  tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
112
112
  allProducts: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -138,9 +138,9 @@ export declare const SmartCartBar: z.ZodObject<{
138
138
  filterOosVariants: z.ZodOptional<z.ZodBoolean>;
139
139
  id: z.ZodOptional<z.ZodString>;
140
140
  minimum: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
141
- presentmentCurrencies: z.ZodOptional<z.ZodObject<{
141
+ presentmentCurrencies: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
142
142
  enabled: z.ZodOptional<z.ZodBoolean>;
143
- }, z.core.$loose>>;
143
+ }, z.core.$loose>>>;
144
144
  productAmountReachedLabel: z.ZodOptional<z.ZodBoolean>;
145
145
  productAmountReachedLabelText: z.ZodOptional<z.ZodString>;
146
146
  productAmountRemainingLabel: z.ZodOptional<z.ZodBoolean>;
@@ -160,7 +160,7 @@ export declare const SmartCartBar: z.ZodObject<{
160
160
  }, z.core.$loose>;
161
161
  /** A smart-cart layout component; only `type === 'tiered_progress_bar'` carries the bars we render. */
162
162
  export declare const SmartCartComponent: z.ZodObject<{
163
- settings: z.ZodOptional<z.ZodObject<{
163
+ settings: z.ZodCatch<z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
164
164
  bars: z.ZodOptional<z.ZodArray<z.ZodObject<{
165
165
  countryCodes: z.ZodOptional<z.ZodArray<z.ZodString>>;
166
166
  excludeDiscountInSubtotal: z.ZodOptional<z.ZodBoolean>;
@@ -170,11 +170,11 @@ export declare const SmartCartComponent: z.ZodObject<{
170
170
  freeShippingOnSubscription: z.ZodOptional<z.ZodBoolean>;
171
171
  geolocationEnabled: z.ZodOptional<z.ZodBoolean>;
172
172
  id: z.ZodOptional<z.ZodString>;
173
- language: z.ZodOptional<z.ZodObject<{
173
+ language: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
174
174
  freeShippingReached: z.ZodOptional<z.ZodString>;
175
175
  freeShippingRemaining: z.ZodOptional<z.ZodString>;
176
176
  giftAreaTitle: z.ZodOptional<z.ZodString>;
177
- }, z.core.$loose>>;
177
+ }, z.core.$loose>>>;
178
178
  name: z.ZodOptional<z.ZodString>;
179
179
  tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
180
180
  allProducts: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -206,9 +206,9 @@ export declare const SmartCartComponent: z.ZodObject<{
206
206
  filterOosVariants: z.ZodOptional<z.ZodBoolean>;
207
207
  id: z.ZodOptional<z.ZodString>;
208
208
  minimum: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
209
- presentmentCurrencies: z.ZodOptional<z.ZodObject<{
209
+ presentmentCurrencies: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
210
210
  enabled: z.ZodOptional<z.ZodBoolean>;
211
- }, z.core.$loose>>;
211
+ }, z.core.$loose>>>;
212
212
  productAmountReachedLabel: z.ZodOptional<z.ZodBoolean>;
213
213
  productAmountReachedLabelText: z.ZodOptional<z.ZodString>;
214
214
  productAmountRemainingLabel: z.ZodOptional<z.ZodBoolean>;
@@ -226,7 +226,7 @@ export declare const SmartCartComponent: z.ZodObject<{
226
226
  unlockConditionType: z.ZodOptional<z.ZodString>;
227
227
  }, z.core.$loose>>>;
228
228
  }, z.core.$loose>>>;
229
- }, z.core.$loose>>;
229
+ }, z.core.$loose>>>>;
230
230
  type: z.ZodOptional<z.ZodString>;
231
231
  }, z.core.$loose>;
232
232
  /**
@@ -234,12 +234,12 @@ export declare const SmartCartComponent: z.ZodObject<{
234
234
  * `tiered_progress_bar` component(s) and their bars/tiers. The rest of the onsite
235
235
  * smart-cart config (buy-more-save-more, custom templates/CSS, callbacks) is out of
236
236
  * scope — `looseObject` preserves-but-ignores it. Parsed under `UserConfig.smartCart`
237
- * with `.catch(undefined)` so a shape drift here can NEVER break apiKey resolution for
238
- * offers / monetize / analytics / gift validation.
237
+ * (`.catch(undefined)`) and `CabUserConfig.smartCart` (`.catch` to a drift sentinel) so a shape
238
+ * drift here can NEVER break apiKey resolution for offers / monetize / analytics / gift validation.
239
239
  */
240
240
  export declare const SmartCartConfig: z.ZodObject<{
241
241
  components: z.ZodOptional<z.ZodArray<z.ZodObject<{
242
- settings: z.ZodOptional<z.ZodObject<{
242
+ settings: z.ZodCatch<z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
243
243
  bars: z.ZodOptional<z.ZodArray<z.ZodObject<{
244
244
  countryCodes: z.ZodOptional<z.ZodArray<z.ZodString>>;
245
245
  excludeDiscountInSubtotal: z.ZodOptional<z.ZodBoolean>;
@@ -249,11 +249,11 @@ export declare const SmartCartConfig: z.ZodObject<{
249
249
  freeShippingOnSubscription: z.ZodOptional<z.ZodBoolean>;
250
250
  geolocationEnabled: z.ZodOptional<z.ZodBoolean>;
251
251
  id: z.ZodOptional<z.ZodString>;
252
- language: z.ZodOptional<z.ZodObject<{
252
+ language: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
253
253
  freeShippingReached: z.ZodOptional<z.ZodString>;
254
254
  freeShippingRemaining: z.ZodOptional<z.ZodString>;
255
255
  giftAreaTitle: z.ZodOptional<z.ZodString>;
256
- }, z.core.$loose>>;
256
+ }, z.core.$loose>>>;
257
257
  name: z.ZodOptional<z.ZodString>;
258
258
  tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
259
259
  allProducts: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -285,9 +285,9 @@ export declare const SmartCartConfig: z.ZodObject<{
285
285
  filterOosVariants: z.ZodOptional<z.ZodBoolean>;
286
286
  id: z.ZodOptional<z.ZodString>;
287
287
  minimum: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
288
- presentmentCurrencies: z.ZodOptional<z.ZodObject<{
288
+ presentmentCurrencies: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
289
289
  enabled: z.ZodOptional<z.ZodBoolean>;
290
- }, z.core.$loose>>;
290
+ }, z.core.$loose>>>;
291
291
  productAmountReachedLabel: z.ZodOptional<z.ZodBoolean>;
292
292
  productAmountReachedLabelText: z.ZodOptional<z.ZodString>;
293
293
  productAmountRemainingLabel: z.ZodOptional<z.ZodBoolean>;
@@ -305,7 +305,7 @@ export declare const SmartCartConfig: z.ZodObject<{
305
305
  unlockConditionType: z.ZodOptional<z.ZodString>;
306
306
  }, z.core.$loose>>>;
307
307
  }, z.core.$loose>>>;
308
- }, z.core.$loose>>;
308
+ }, z.core.$loose>>>>;
309
309
  type: z.ZodOptional<z.ZodString>;
310
310
  }, z.core.$loose>>>;
311
311
  }, z.core.$loose>;
@@ -111,6 +111,13 @@ var ShopConfig = import_zod.z.object({
111
111
 
112
112
  // src/schema/smartCart.ts
113
113
  var import_zod2 = require("zod");
114
+
115
+ // src/schema/driftedSmartCart.ts
116
+ var DRIFTED_SMART_CART_SETTINGS = Object.freeze({});
117
+ var DRIFTED_SMART_CART = Object.freeze({});
118
+
119
+ // src/schema/smartCart.ts
120
+ var phpEmptyMapAsAbsent = (value) => Array.isArray(value) && value.length === 0 ? void 0 : value;
114
121
  var SmartCartTierProduct = import_zod2.z.looseObject({
115
122
  handle: import_zod2.z.string().optional(),
116
123
  id: import_zod2.z.coerce.number().optional(),
@@ -164,7 +171,11 @@ var SmartCartTier = import_zod2.z.looseObject({
164
171
  * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
165
172
  */
166
173
  minimum: import_zod2.z.preprocess((value) => value == null ? void 0 : value, import_zod2.z.coerce.number().optional()),
167
- presentmentCurrencies: import_zod2.z.looseObject({ enabled: import_zod2.z.boolean().optional() }).optional(),
174
+ /** An empty map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed tier. */
175
+ presentmentCurrencies: import_zod2.z.preprocess(
176
+ phpEmptyMapAsAbsent,
177
+ import_zod2.z.looseObject({ enabled: import_zod2.z.boolean().optional() }).optional()
178
+ ),
168
179
  productAmountReachedLabel: import_zod2.z.boolean().optional(),
169
180
  productAmountReachedLabelText: import_zod2.z.string().optional(),
170
181
  productAmountRemainingLabel: import_zod2.z.boolean().optional(),
@@ -217,16 +228,35 @@ var SmartCartBar = import_zod2.z.looseObject({
217
228
  freeShippingOnSubscription: import_zod2.z.boolean().optional(),
218
229
  geolocationEnabled: import_zod2.z.boolean().optional(),
219
230
  id: import_zod2.z.string().optional(),
220
- language: import_zod2.z.looseObject({
221
- freeShippingReached: import_zod2.z.string().optional(),
222
- freeShippingRemaining: import_zod2.z.string().optional(),
223
- giftAreaTitle: import_zod2.z.string().optional()
224
- }).optional(),
231
+ /** An empty `language` map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed bar. */
232
+ language: import_zod2.z.preprocess(
233
+ phpEmptyMapAsAbsent,
234
+ import_zod2.z.looseObject({
235
+ freeShippingReached: import_zod2.z.string().optional(),
236
+ freeShippingRemaining: import_zod2.z.string().optional(),
237
+ giftAreaTitle: import_zod2.z.string().optional()
238
+ }).optional()
239
+ ),
225
240
  name: import_zod2.z.string().optional(),
226
241
  tiers: import_zod2.z.array(SmartCartTier).optional()
227
242
  });
228
243
  var SmartCartComponent = import_zod2.z.looseObject({
229
- settings: import_zod2.z.looseObject({ bars: import_zod2.z.array(SmartCartBar).optional() }).optional(),
244
+ /**
245
+ * Every component in the layout is parsed, not just the progress bar, so this field is the blast-radius
246
+ * boundary (REB-25139): before it caught, b.box's `cart_items` component arriving as `settings: []` failed
247
+ * the component, the `components` array and the whole config — and `CabUserConfig`'s config-level catch
248
+ * then silently dropped every progress bar for the shop. Now:
249
+ *
250
+ * - PHP's empty-map `[]` reads as absent ({@link phpEmptyMapAsAbsent}) — normal engine vocabulary, no drift.
251
+ * - Any other unreadable settings (a string, a non-empty array, a bar that fails its schema) `.catch` to the
252
+ * {@link DRIFTED_SMART_CART_SETTINGS} identity sentinel: that component loses its settings (no bars),
253
+ * its siblings — and the progress bar, when it is a sibling — still parse, and the server's progress-bar
254
+ * resolution emits a `shopConfig` drift on seeing the sentinel.
255
+ *
256
+ * Bars are not salvaged per element: the only reader takes the first progress-bar component's bars as a
257
+ * set (one per geo segment), and a drifted bar now costs that component's bars WITH a drift, not the cart.
258
+ */
259
+ settings: import_zod2.z.preprocess(phpEmptyMapAsAbsent, import_zod2.z.looseObject({ bars: import_zod2.z.array(SmartCartBar).optional() }).optional()).catch(DRIFTED_SMART_CART_SETTINGS),
230
260
  type: import_zod2.z.string().optional()
231
261
  });
232
262
  var SmartCartConfig = import_zod2.z.looseObject({
@@ -87,6 +87,13 @@ var ShopConfig = z.object({
87
87
 
88
88
  // src/schema/smartCart.ts
89
89
  import { z as z2 } from "zod";
90
+
91
+ // src/schema/driftedSmartCart.ts
92
+ var DRIFTED_SMART_CART_SETTINGS = Object.freeze({});
93
+ var DRIFTED_SMART_CART = Object.freeze({});
94
+
95
+ // src/schema/smartCart.ts
96
+ var phpEmptyMapAsAbsent = (value) => Array.isArray(value) && value.length === 0 ? void 0 : value;
90
97
  var SmartCartTierProduct = z2.looseObject({
91
98
  handle: z2.string().optional(),
92
99
  id: z2.coerce.number().optional(),
@@ -140,7 +147,11 @@ var SmartCartTier = z2.looseObject({
140
147
  * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
141
148
  */
142
149
  minimum: z2.preprocess((value) => value == null ? void 0 : value, z2.coerce.number().optional()),
143
- presentmentCurrencies: z2.looseObject({ enabled: z2.boolean().optional() }).optional(),
150
+ /** An empty map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed tier. */
151
+ presentmentCurrencies: z2.preprocess(
152
+ phpEmptyMapAsAbsent,
153
+ z2.looseObject({ enabled: z2.boolean().optional() }).optional()
154
+ ),
144
155
  productAmountReachedLabel: z2.boolean().optional(),
145
156
  productAmountReachedLabelText: z2.string().optional(),
146
157
  productAmountRemainingLabel: z2.boolean().optional(),
@@ -193,16 +204,35 @@ var SmartCartBar = z2.looseObject({
193
204
  freeShippingOnSubscription: z2.boolean().optional(),
194
205
  geolocationEnabled: z2.boolean().optional(),
195
206
  id: z2.string().optional(),
196
- language: z2.looseObject({
197
- freeShippingReached: z2.string().optional(),
198
- freeShippingRemaining: z2.string().optional(),
199
- giftAreaTitle: z2.string().optional()
200
- }).optional(),
207
+ /** An empty `language` map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed bar. */
208
+ language: z2.preprocess(
209
+ phpEmptyMapAsAbsent,
210
+ z2.looseObject({
211
+ freeShippingReached: z2.string().optional(),
212
+ freeShippingRemaining: z2.string().optional(),
213
+ giftAreaTitle: z2.string().optional()
214
+ }).optional()
215
+ ),
201
216
  name: z2.string().optional(),
202
217
  tiers: z2.array(SmartCartTier).optional()
203
218
  });
204
219
  var SmartCartComponent = z2.looseObject({
205
- settings: z2.looseObject({ bars: z2.array(SmartCartBar).optional() }).optional(),
220
+ /**
221
+ * Every component in the layout is parsed, not just the progress bar, so this field is the blast-radius
222
+ * boundary (REB-25139): before it caught, b.box's `cart_items` component arriving as `settings: []` failed
223
+ * the component, the `components` array and the whole config — and `CabUserConfig`'s config-level catch
224
+ * then silently dropped every progress bar for the shop. Now:
225
+ *
226
+ * - PHP's empty-map `[]` reads as absent ({@link phpEmptyMapAsAbsent}) — normal engine vocabulary, no drift.
227
+ * - Any other unreadable settings (a string, a non-empty array, a bar that fails its schema) `.catch` to the
228
+ * {@link DRIFTED_SMART_CART_SETTINGS} identity sentinel: that component loses its settings (no bars),
229
+ * its siblings — and the progress bar, when it is a sibling — still parse, and the server's progress-bar
230
+ * resolution emits a `shopConfig` drift on seeing the sentinel.
231
+ *
232
+ * Bars are not salvaged per element: the only reader takes the first progress-bar component's bars as a
233
+ * set (one per geo segment), and a drifted bar now costs that component's bars WITH a drift, not the cart.
234
+ */
235
+ settings: z2.preprocess(phpEmptyMapAsAbsent, z2.looseObject({ bars: z2.array(SmartCartBar).optional() }).optional()).catch(DRIFTED_SMART_CART_SETTINGS),
206
236
  type: z2.string().optional()
207
237
  });
208
238
  var SmartCartConfig = z2.looseObject({
@@ -80,7 +80,7 @@ export declare const UserConfig: z.ZodObject<{
80
80
  }, z.core.$strip>;
81
81
  smartCart: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
82
82
  components: z.ZodOptional<z.ZodArray<z.ZodObject<{
83
- settings: z.ZodOptional<z.ZodObject<{
83
+ settings: z.ZodCatch<z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
84
84
  bars: z.ZodOptional<z.ZodArray<z.ZodObject<{
85
85
  countryCodes: z.ZodOptional<z.ZodArray<z.ZodString>>;
86
86
  excludeDiscountInSubtotal: z.ZodOptional<z.ZodBoolean>;
@@ -90,11 +90,11 @@ export declare const UserConfig: z.ZodObject<{
90
90
  freeShippingOnSubscription: z.ZodOptional<z.ZodBoolean>;
91
91
  geolocationEnabled: z.ZodOptional<z.ZodBoolean>;
92
92
  id: z.ZodOptional<z.ZodString>;
93
- language: z.ZodOptional<z.ZodObject<{
93
+ language: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
94
94
  freeShippingReached: z.ZodOptional<z.ZodString>;
95
95
  freeShippingRemaining: z.ZodOptional<z.ZodString>;
96
96
  giftAreaTitle: z.ZodOptional<z.ZodString>;
97
- }, z.core.$loose>>;
97
+ }, z.core.$loose>>>;
98
98
  name: z.ZodOptional<z.ZodString>;
99
99
  tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
100
100
  allProducts: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -126,9 +126,9 @@ export declare const UserConfig: z.ZodObject<{
126
126
  filterOosVariants: z.ZodOptional<z.ZodBoolean>;
127
127
  id: z.ZodOptional<z.ZodString>;
128
128
  minimum: z.ZodPreprocess<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
129
- presentmentCurrencies: z.ZodOptional<z.ZodObject<{
129
+ presentmentCurrencies: z.ZodPreprocess<z.ZodOptional<z.ZodObject<{
130
130
  enabled: z.ZodOptional<z.ZodBoolean>;
131
- }, z.core.$loose>>;
131
+ }, z.core.$loose>>>;
132
132
  productAmountReachedLabel: z.ZodOptional<z.ZodBoolean>;
133
133
  productAmountReachedLabelText: z.ZodOptional<z.ZodString>;
134
134
  productAmountRemainingLabel: z.ZodOptional<z.ZodBoolean>;
@@ -146,7 +146,7 @@ export declare const UserConfig: z.ZodObject<{
146
146
  unlockConditionType: z.ZodOptional<z.ZodString>;
147
147
  }, z.core.$loose>>>;
148
148
  }, z.core.$loose>>>;
149
- }, z.core.$loose>>;
149
+ }, z.core.$loose>>>>;
150
150
  type: z.ZodOptional<z.ZodString>;
151
151
  }, z.core.$loose>>>;
152
152
  }, z.core.$loose>>>>;
@@ -1560,8 +1560,13 @@ var import_zod20 = require("zod");
1560
1560
  // src/schema/driftedMarkets.ts
1561
1561
  var DRIFTED_MARKETS = Object.freeze({ enabled: false });
1562
1562
 
1563
+ // src/schema/driftedSmartCart.ts
1564
+ var DRIFTED_SMART_CART_SETTINGS = Object.freeze({});
1565
+ var DRIFTED_SMART_CART = Object.freeze({});
1566
+
1563
1567
  // src/schema/smartCart.ts
1564
1568
  var import_zod19 = require("zod");
1569
+ var phpEmptyMapAsAbsent = (value) => Array.isArray(value) && value.length === 0 ? void 0 : value;
1565
1570
  var SmartCartTierProduct = import_zod19.z.looseObject({
1566
1571
  handle: import_zod19.z.string().optional(),
1567
1572
  id: import_zod19.z.coerce.number().optional(),
@@ -1615,7 +1620,11 @@ var SmartCartTier = import_zod19.z.looseObject({
1615
1620
  * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
1616
1621
  */
1617
1622
  minimum: import_zod19.z.preprocess((value) => value == null ? void 0 : value, import_zod19.z.coerce.number().optional()),
1618
- presentmentCurrencies: import_zod19.z.looseObject({ enabled: import_zod19.z.boolean().optional() }).optional(),
1623
+ /** An empty map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed tier. */
1624
+ presentmentCurrencies: import_zod19.z.preprocess(
1625
+ phpEmptyMapAsAbsent,
1626
+ import_zod19.z.looseObject({ enabled: import_zod19.z.boolean().optional() }).optional()
1627
+ ),
1619
1628
  productAmountReachedLabel: import_zod19.z.boolean().optional(),
1620
1629
  productAmountReachedLabelText: import_zod19.z.string().optional(),
1621
1630
  productAmountRemainingLabel: import_zod19.z.boolean().optional(),
@@ -1668,16 +1677,35 @@ var SmartCartBar = import_zod19.z.looseObject({
1668
1677
  freeShippingOnSubscription: import_zod19.z.boolean().optional(),
1669
1678
  geolocationEnabled: import_zod19.z.boolean().optional(),
1670
1679
  id: import_zod19.z.string().optional(),
1671
- language: import_zod19.z.looseObject({
1672
- freeShippingReached: import_zod19.z.string().optional(),
1673
- freeShippingRemaining: import_zod19.z.string().optional(),
1674
- giftAreaTitle: import_zod19.z.string().optional()
1675
- }).optional(),
1680
+ /** An empty `language` map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed bar. */
1681
+ language: import_zod19.z.preprocess(
1682
+ phpEmptyMapAsAbsent,
1683
+ import_zod19.z.looseObject({
1684
+ freeShippingReached: import_zod19.z.string().optional(),
1685
+ freeShippingRemaining: import_zod19.z.string().optional(),
1686
+ giftAreaTitle: import_zod19.z.string().optional()
1687
+ }).optional()
1688
+ ),
1676
1689
  name: import_zod19.z.string().optional(),
1677
1690
  tiers: import_zod19.z.array(SmartCartTier).optional()
1678
1691
  });
1679
1692
  var SmartCartComponent = import_zod19.z.looseObject({
1680
- settings: import_zod19.z.looseObject({ bars: import_zod19.z.array(SmartCartBar).optional() }).optional(),
1693
+ /**
1694
+ * Every component in the layout is parsed, not just the progress bar, so this field is the blast-radius
1695
+ * boundary (REB-25139): before it caught, b.box's `cart_items` component arriving as `settings: []` failed
1696
+ * the component, the `components` array and the whole config — and `CabUserConfig`'s config-level catch
1697
+ * then silently dropped every progress bar for the shop. Now:
1698
+ *
1699
+ * - PHP's empty-map `[]` reads as absent ({@link phpEmptyMapAsAbsent}) — normal engine vocabulary, no drift.
1700
+ * - Any other unreadable settings (a string, a non-empty array, a bar that fails its schema) `.catch` to the
1701
+ * {@link DRIFTED_SMART_CART_SETTINGS} identity sentinel: that component loses its settings (no bars),
1702
+ * its siblings — and the progress bar, when it is a sibling — still parse, and the server's progress-bar
1703
+ * resolution emits a `shopConfig` drift on seeing the sentinel.
1704
+ *
1705
+ * Bars are not salvaged per element: the only reader takes the first progress-bar component's bars as a
1706
+ * set (one per geo segment), and a drifted bar now costs that component's bars WITH a drift, not the cart.
1707
+ */
1708
+ settings: import_zod19.z.preprocess(phpEmptyMapAsAbsent, import_zod19.z.looseObject({ bars: import_zod19.z.array(SmartCartBar).optional() }).optional()).catch(DRIFTED_SMART_CART_SETTINGS),
1681
1709
  type: import_zod19.z.string().optional()
1682
1710
  });
1683
1711
  var SmartCartConfig = import_zod19.z.looseObject({
@@ -1780,7 +1808,7 @@ var CabShopConfig = import_zod20.z.object({
1780
1808
  });
1781
1809
  var CabUserConfig = import_zod20.z.object({
1782
1810
  shop: CabShopConfig,
1783
- smartCart: SmartCartConfig.nullish().catch(void 0)
1811
+ smartCart: SmartCartConfig.nullish().catch(DRIFTED_SMART_CART)
1784
1812
  });
1785
1813
 
1786
1814
  // src/schema/widgets/checkout-and-beyond/image.ts
@@ -6092,9 +6120,26 @@ var dispatchWidgetTransform = (id, normalized, report) => {
6092
6120
  if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
6093
6121
  return normalized;
6094
6122
  };
6123
+ var reportSmartCartDrift = (shop, smartCart, ctx) => {
6124
+ if (smartCart === DRIFTED_SMART_CART) {
6125
+ ctx.reportDrift?.({
6126
+ detail: `smart cart config unreadable (drifted shape) \u2014 every progress bar dropped for ${shop}`,
6127
+ kind: "shopConfig"
6128
+ });
6129
+ return;
6130
+ }
6131
+ const drifted = (smartCart?.components ?? []).filter((entry) => entry.settings === DRIFTED_SMART_CART_SETTINGS);
6132
+ if (drifted.length) {
6133
+ ctx.reportDrift?.({
6134
+ detail: `smart cart component settings unreadable (drifted shape) \u2014 dropped for ${drifted.map((entry) => entry.type ?? "untyped").join(", ")} on ${shop}`,
6135
+ kind: "shopConfig"
6136
+ });
6137
+ }
6138
+ };
6095
6139
  var fetchProgressBarContext = async (shop, ctx, prefetched) => {
6096
6140
  const config = prefetched === void 0 ? await fetchUserConfig({ shop }, ctx).catch(() => void 0) : prefetched;
6097
6141
  if (!config) return {};
6142
+ reportSmartCartDrift(shop, config.smartCart, ctx);
6098
6143
  if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
6099
6144
  ctx.reportDrift?.({
6100
6145
  detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
@@ -1487,8 +1487,13 @@ import { z as z20 } from "zod";
1487
1487
  // src/schema/driftedMarkets.ts
1488
1488
  var DRIFTED_MARKETS = Object.freeze({ enabled: false });
1489
1489
 
1490
+ // src/schema/driftedSmartCart.ts
1491
+ var DRIFTED_SMART_CART_SETTINGS = Object.freeze({});
1492
+ var DRIFTED_SMART_CART = Object.freeze({});
1493
+
1490
1494
  // src/schema/smartCart.ts
1491
1495
  import { z as z19 } from "zod";
1496
+ var phpEmptyMapAsAbsent = (value) => Array.isArray(value) && value.length === 0 ? void 0 : value;
1492
1497
  var SmartCartTierProduct = z19.looseObject({
1493
1498
  handle: z19.string().optional(),
1494
1499
  id: z19.coerce.number().optional(),
@@ -1542,7 +1547,11 @@ var SmartCartTier = z19.looseObject({
1542
1547
  * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
1543
1548
  */
1544
1549
  minimum: z19.preprocess((value) => value == null ? void 0 : value, z19.coerce.number().optional()),
1545
- presentmentCurrencies: z19.looseObject({ enabled: z19.boolean().optional() }).optional(),
1550
+ /** An empty map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed tier. */
1551
+ presentmentCurrencies: z19.preprocess(
1552
+ phpEmptyMapAsAbsent,
1553
+ z19.looseObject({ enabled: z19.boolean().optional() }).optional()
1554
+ ),
1546
1555
  productAmountReachedLabel: z19.boolean().optional(),
1547
1556
  productAmountReachedLabelText: z19.string().optional(),
1548
1557
  productAmountRemainingLabel: z19.boolean().optional(),
@@ -1595,16 +1604,35 @@ var SmartCartBar = z19.looseObject({
1595
1604
  freeShippingOnSubscription: z19.boolean().optional(),
1596
1605
  geolocationEnabled: z19.boolean().optional(),
1597
1606
  id: z19.string().optional(),
1598
- language: z19.looseObject({
1599
- freeShippingReached: z19.string().optional(),
1600
- freeShippingRemaining: z19.string().optional(),
1601
- giftAreaTitle: z19.string().optional()
1602
- }).optional(),
1607
+ /** An empty `language` map arrives as PHP's `[]` (see {@link phpEmptyMapAsAbsent}) — absent, not a failed bar. */
1608
+ language: z19.preprocess(
1609
+ phpEmptyMapAsAbsent,
1610
+ z19.looseObject({
1611
+ freeShippingReached: z19.string().optional(),
1612
+ freeShippingRemaining: z19.string().optional(),
1613
+ giftAreaTitle: z19.string().optional()
1614
+ }).optional()
1615
+ ),
1603
1616
  name: z19.string().optional(),
1604
1617
  tiers: z19.array(SmartCartTier).optional()
1605
1618
  });
1606
1619
  var SmartCartComponent = z19.looseObject({
1607
- settings: z19.looseObject({ bars: z19.array(SmartCartBar).optional() }).optional(),
1620
+ /**
1621
+ * Every component in the layout is parsed, not just the progress bar, so this field is the blast-radius
1622
+ * boundary (REB-25139): before it caught, b.box's `cart_items` component arriving as `settings: []` failed
1623
+ * the component, the `components` array and the whole config — and `CabUserConfig`'s config-level catch
1624
+ * then silently dropped every progress bar for the shop. Now:
1625
+ *
1626
+ * - PHP's empty-map `[]` reads as absent ({@link phpEmptyMapAsAbsent}) — normal engine vocabulary, no drift.
1627
+ * - Any other unreadable settings (a string, a non-empty array, a bar that fails its schema) `.catch` to the
1628
+ * {@link DRIFTED_SMART_CART_SETTINGS} identity sentinel: that component loses its settings (no bars),
1629
+ * its siblings — and the progress bar, when it is a sibling — still parse, and the server's progress-bar
1630
+ * resolution emits a `shopConfig` drift on seeing the sentinel.
1631
+ *
1632
+ * Bars are not salvaged per element: the only reader takes the first progress-bar component's bars as a
1633
+ * set (one per geo segment), and a drifted bar now costs that component's bars WITH a drift, not the cart.
1634
+ */
1635
+ settings: z19.preprocess(phpEmptyMapAsAbsent, z19.looseObject({ bars: z19.array(SmartCartBar).optional() }).optional()).catch(DRIFTED_SMART_CART_SETTINGS),
1608
1636
  type: z19.string().optional()
1609
1637
  });
1610
1638
  var SmartCartConfig = z19.looseObject({
@@ -1707,7 +1735,7 @@ var CabShopConfig = z20.object({
1707
1735
  });
1708
1736
  var CabUserConfig = z20.object({
1709
1737
  shop: CabShopConfig,
1710
- smartCart: SmartCartConfig.nullish().catch(void 0)
1738
+ smartCart: SmartCartConfig.nullish().catch(DRIFTED_SMART_CART)
1711
1739
  });
1712
1740
 
1713
1741
  // src/schema/widgets/checkout-and-beyond/image.ts
@@ -6019,9 +6047,26 @@ var dispatchWidgetTransform = (id, normalized, report) => {
6019
6047
  if (isOfferV1(normalized)) return convertOfferToV2({ id, name: normalized.name, settings: normalized });
6020
6048
  return normalized;
6021
6049
  };
6050
+ var reportSmartCartDrift = (shop, smartCart, ctx) => {
6051
+ if (smartCart === DRIFTED_SMART_CART) {
6052
+ ctx.reportDrift?.({
6053
+ detail: `smart cart config unreadable (drifted shape) \u2014 every progress bar dropped for ${shop}`,
6054
+ kind: "shopConfig"
6055
+ });
6056
+ return;
6057
+ }
6058
+ const drifted = (smartCart?.components ?? []).filter((entry) => entry.settings === DRIFTED_SMART_CART_SETTINGS);
6059
+ if (drifted.length) {
6060
+ ctx.reportDrift?.({
6061
+ detail: `smart cart component settings unreadable (drifted shape) \u2014 dropped for ${drifted.map((entry) => entry.type ?? "untyped").join(", ")} on ${shop}`,
6062
+ kind: "shopConfig"
6063
+ });
6064
+ }
6065
+ };
6022
6066
  var fetchProgressBarContext = async (shop, ctx, prefetched) => {
6023
6067
  const config = prefetched === void 0 ? await fetchUserConfig({ shop }, ctx).catch(() => void 0) : prefetched;
6024
6068
  if (!config) return {};
6069
+ reportSmartCartDrift(shop, config.smartCart, ctx);
6025
6070
  if (config.shop.billingVersion === "v6" && config.shop.activePackages.some((pkg) => pkg.shortName === "")) {
6026
6071
  ctx.reportDrift?.({
6027
6072
  detail: "shop config active package entry inerted (drifted shape) \u2014 smart-cart entitlement may misgate",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebuy/rebuy",
3
- "version": "3.60.1",
3
+ "version": "3.60.2",
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.",