@rebuy/rebuy 3.28.0 → 3.28.1

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
@@ -3242,7 +3242,14 @@ var CABReviewsSection = import_zod36.z.object({
3242
3242
  sectionId: import_zod36.z.uuid().default(() => (0, import_uuid11.v7)()),
3243
3243
  sectionType: import_zod36.z.literal(SectionType.reviews).default(SectionType.reviews),
3244
3244
  size: import_zod36.z.enum(reviewsSizes).default(ReviewsSize.default),
3245
- strokeColor: import_zod36.z.string().default("#fadb14"),
3245
+ /**
3246
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
3247
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
3248
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
3249
+ * the whole reviews section.
3250
+ */
3251
+ strokeColor: import_zod36.z.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
3252
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
3246
3253
  strokeWidth: import_zod36.z.number().nonnegative().default(0)
3247
3254
  });
3248
3255
 
package/dist/index.mjs CHANGED
@@ -2916,7 +2916,14 @@ var CABReviewsSection = z36.object({
2916
2916
  sectionId: z36.uuid().default(() => uuidv710()),
2917
2917
  sectionType: z36.literal(SectionType.reviews).default(SectionType.reviews),
2918
2918
  size: z36.enum(reviewsSizes).default(ReviewsSize.default),
2919
- strokeColor: z36.string().default("#fadb14"),
2919
+ /**
2920
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
2921
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
2922
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
2923
+ * the whole reviews section.
2924
+ */
2925
+ strokeColor: z36.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
2926
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
2920
2927
  strokeWidth: z36.number().nonnegative().default(0)
2921
2928
  });
2922
2929
 
@@ -1572,7 +1572,14 @@ var CABReviewsSection = import_zod16.z.object({
1572
1572
  sectionId: import_zod16.z.uuid().default(() => (0, import_uuid10.v7)()),
1573
1573
  sectionType: import_zod16.z.literal(SectionType.reviews).default(SectionType.reviews),
1574
1574
  size: import_zod16.z.enum(reviewsSizes).default(ReviewsSize.default),
1575
- strokeColor: import_zod16.z.string().default("#fadb14"),
1575
+ /**
1576
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1577
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1578
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1579
+ * the whole reviews section.
1580
+ */
1581
+ strokeColor: import_zod16.z.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1582
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1576
1583
  strokeWidth: import_zod16.z.number().nonnegative().default(0)
1577
1584
  });
1578
1585
 
@@ -1362,7 +1362,14 @@ var CABReviewsSection = z16.object({
1362
1362
  sectionId: z16.uuid().default(() => uuidv710()),
1363
1363
  sectionType: z16.literal(SectionType.reviews).default(SectionType.reviews),
1364
1364
  size: z16.enum(reviewsSizes).default(ReviewsSize.default),
1365
- strokeColor: z16.string().default("#fadb14"),
1365
+ /**
1366
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1367
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1368
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1369
+ * the whole reviews section.
1370
+ */
1371
+ strokeColor: z16.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1372
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1366
1373
  strokeWidth: z16.number().nonnegative().default(0)
1367
1374
  });
1368
1375
 
@@ -1399,7 +1399,14 @@ var CABReviewsSection = import_zod19.z.object({
1399
1399
  sectionId: import_zod19.z.uuid().default(() => (0, import_uuid10.v7)()),
1400
1400
  sectionType: import_zod19.z.literal(SectionType.reviews).default(SectionType.reviews),
1401
1401
  size: import_zod19.z.enum(reviewsSizes).default(ReviewsSize.default),
1402
- strokeColor: import_zod19.z.string().default("#fadb14"),
1402
+ /**
1403
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1404
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1405
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1406
+ * the whole reviews section.
1407
+ */
1408
+ strokeColor: import_zod19.z.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1409
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1403
1410
  strokeWidth: import_zod19.z.number().nonnegative().default(0)
1404
1411
  });
1405
1412
 
@@ -1374,7 +1374,14 @@ var CABReviewsSection = z19.object({
1374
1374
  sectionId: z19.uuid().default(() => uuidv710()),
1375
1375
  sectionType: z19.literal(SectionType.reviews).default(SectionType.reviews),
1376
1376
  size: z19.enum(reviewsSizes).default(ReviewsSize.default),
1377
- strokeColor: z19.string().default("#fadb14"),
1377
+ /**
1378
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1379
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1380
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1381
+ * the whole reviews section.
1382
+ */
1383
+ strokeColor: z19.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1384
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1378
1385
  strokeWidth: z19.number().nonnegative().default(0)
1379
1386
  });
1380
1387
 
@@ -172,7 +172,6 @@ export declare const LanguageKey: {
172
172
  readonly zh: "zh";
173
173
  };
174
174
  export type LanguageKey = (typeof languageKeys)[number];
175
- /** Runtime cart/order events a variable mutator can fire on (the events phase of REB-22472). */
176
175
  /**
177
176
  * Platform chrome a `layout` can wear. A frame changes how a renderer wraps the section, never its data, so a
178
177
  * framed section stays a plain `layout` rather than becoming its own section type (a `callout` is passive
@@ -184,6 +183,7 @@ export declare const LayoutFrame: {
184
183
  readonly "offer-content": "offer-content";
185
184
  };
186
185
  export type LayoutFrame = (typeof layoutFrames)[number];
186
+ /** Runtime cart/order events a variable mutator can fire on (the events phase of REB-22472). */
187
187
  export declare const mutatorEvents: readonly ["item_added", "quantity_changed", "variant_selected"];
188
188
  export declare const MutatorEvent: {
189
189
  readonly item_added: "item_added";
@@ -2022,7 +2022,14 @@ var CABReviewsSection = import_zod23.z.object({
2022
2022
  sectionId: import_zod23.z.uuid().default(() => (0, import_uuid10.v7)()),
2023
2023
  sectionType: import_zod23.z.literal(SectionType.reviews).default(SectionType.reviews),
2024
2024
  size: import_zod23.z.enum(reviewsSizes).default(ReviewsSize.default),
2025
- strokeColor: import_zod23.z.string().default("#fadb14"),
2025
+ /**
2026
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
2027
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
2028
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
2029
+ * the whole reviews section.
2030
+ */
2031
+ strokeColor: import_zod23.z.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
2032
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
2026
2033
  strokeWidth: import_zod23.z.number().nonnegative().default(0)
2027
2034
  });
2028
2035
 
@@ -4059,7 +4066,9 @@ var convertTextBlock = (raw, report) => {
4059
4066
  result: settingsSchema.shape[field].safeParse(shell.data.settings[field])
4060
4067
  }));
4061
4068
  const horizontal = { center: "center", leading: "start", trailing: "end" }[shell.data.settings.horizontalAlignment];
4062
- const styled2 = values.every(({ result }) => result.success && typeof result.data === "string");
4069
+ const styled2 = values.every(
4070
+ ({ result }) => result.success && (result.data === null || typeof result.data === "string")
4071
+ );
4063
4072
  const omit = (field) => {
4064
4073
  report?.({ detail: `Unreadable post-purchase text field: ${field}`, kind: "section" });
4065
4074
  return [];
@@ -4072,20 +4081,18 @@ var convertTextBlock = (raw, report) => {
4072
4081
  const section = CABTextSection.safeParse({ content: { en: content } });
4073
4082
  return section.success ? [section.data] : omit(field);
4074
4083
  });
4075
- return [
4076
- CABLayoutSection.parse({
4077
- alignment: {
4078
- horizontal
4079
- },
4080
- sections
4081
- })
4082
- ];
4084
+ return sections.length ? [CABLayoutSection.parse({ alignment: { horizontal }, sections })] : [];
4083
4085
  };
4084
4086
 
4085
4087
  // src/transforms/postPurchase/convertTimerBlock.ts
4086
4088
  var Presentation = TimerBlock.pick({ styles: true }).extend({
4087
4089
  settings: TimerBlock.shape.settings.pick({ title: true })
4088
4090
  });
4091
+ var withCountdown = (doc, run, textAlign) => {
4092
+ const paragraphs = doc.content.length ? doc.content : [TiptapParagraph.parse({ attrs: { textAlign } })];
4093
+ const last = paragraphs[paragraphs.length - 1];
4094
+ return { ...doc, content: [...paragraphs.slice(0, -1), { ...last, content: [...last.content, run] }] };
4095
+ };
4089
4096
  var convertTimerBlock = (raw, report) => {
4090
4097
  const omit = () => {
4091
4098
  report?.({ detail: "Unreadable post-purchase timer display", kind: "section" });
@@ -4096,17 +4103,15 @@ var convertTimerBlock = (raw, report) => {
4096
4103
  const { title } = parsed.data.settings;
4097
4104
  const styles2 = parsed.data.styles;
4098
4105
  const horizontal = { center: "center", leading: "start", trailing: "end" }[styles2.textAlignment];
4106
+ const countdown = styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize);
4099
4107
  const content = typeof title === "string" ? {
4100
4108
  content: [
4101
4109
  {
4102
4110
  attrs: { textAlign: horizontal },
4103
- content: [
4104
- styledTextRun(title, styles2.titleEmphasized, styles2.titleSize),
4105
- styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize)
4106
- ]
4111
+ content: [styledTextRun(title, styles2.titleEmphasized, styles2.titleSize), countdown]
4107
4112
  }
4108
4113
  ]
4109
- } : title;
4114
+ } : withCountdown(title, countdown, horizontal);
4110
4115
  const text2 = CABTextSection.safeParse({ content: { en: content } });
4111
4116
  if (!text2.success) return omit();
4112
4117
  return [
@@ -4122,7 +4127,7 @@ var convertTimerBlock = (raw, report) => {
4122
4127
  // src/transforms/postPurchase/convertMessagePreview.ts
4123
4128
  var MessageWidget = import_zod33.z.object({
4124
4129
  blocks: import_zod33.z.array(import_zod33.z.unknown()).min(1),
4125
- type: import_zod33.z.literal("shopify_post_purchase_extension")
4130
+ type: import_zod33.z.literal(WidgetType.shopify_post_purchase_extension)
4126
4131
  });
4127
4132
  var BlockType2 = import_zod33.z.object({ type: import_zod33.z.string() });
4128
4133
  var converters = {
@@ -4141,7 +4146,7 @@ var convertMessagePreview = (id, settings, report) => {
4141
4146
  sections: parsed.data.blocks.flatMap((block) => {
4142
4147
  const type = BlockType2.safeParse(block);
4143
4148
  const kind = type.success ? type.data.type : void 0;
4144
- const convert2 = kind ? converters[kind] : void 0;
4149
+ const convert2 = kind && Object.hasOwn(converters, kind) ? converters[kind] : void 0;
4145
4150
  if (convert2) return convert2(block, report);
4146
4151
  report?.({
4147
4152
  detail: kind ? `Unsupported post-purchase block type: ${kind}` : "Unreadable post-purchase block: missing type",
@@ -1971,7 +1971,14 @@ var CABReviewsSection = z23.object({
1971
1971
  sectionId: z23.uuid().default(() => uuidv710()),
1972
1972
  sectionType: z23.literal(SectionType.reviews).default(SectionType.reviews),
1973
1973
  size: z23.enum(reviewsSizes).default(ReviewsSize.default),
1974
- strokeColor: z23.string().default("#fadb14"),
1974
+ /**
1975
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1976
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1977
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1978
+ * the whole reviews section.
1979
+ */
1980
+ strokeColor: z23.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1981
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1975
1982
  strokeWidth: z23.number().nonnegative().default(0)
1976
1983
  });
1977
1984
 
@@ -4008,7 +4015,9 @@ var convertTextBlock = (raw, report) => {
4008
4015
  result: settingsSchema.shape[field].safeParse(shell.data.settings[field])
4009
4016
  }));
4010
4017
  const horizontal = { center: "center", leading: "start", trailing: "end" }[shell.data.settings.horizontalAlignment];
4011
- const styled2 = values.every(({ result }) => result.success && typeof result.data === "string");
4018
+ const styled2 = values.every(
4019
+ ({ result }) => result.success && (result.data === null || typeof result.data === "string")
4020
+ );
4012
4021
  const omit = (field) => {
4013
4022
  report?.({ detail: `Unreadable post-purchase text field: ${field}`, kind: "section" });
4014
4023
  return [];
@@ -4021,20 +4030,18 @@ var convertTextBlock = (raw, report) => {
4021
4030
  const section = CABTextSection.safeParse({ content: { en: content } });
4022
4031
  return section.success ? [section.data] : omit(field);
4023
4032
  });
4024
- return [
4025
- CABLayoutSection.parse({
4026
- alignment: {
4027
- horizontal
4028
- },
4029
- sections
4030
- })
4031
- ];
4033
+ return sections.length ? [CABLayoutSection.parse({ alignment: { horizontal }, sections })] : [];
4032
4034
  };
4033
4035
 
4034
4036
  // src/transforms/postPurchase/convertTimerBlock.ts
4035
4037
  var Presentation = TimerBlock.pick({ styles: true }).extend({
4036
4038
  settings: TimerBlock.shape.settings.pick({ title: true })
4037
4039
  });
4040
+ var withCountdown = (doc, run, textAlign) => {
4041
+ const paragraphs = doc.content.length ? doc.content : [TiptapParagraph.parse({ attrs: { textAlign } })];
4042
+ const last = paragraphs[paragraphs.length - 1];
4043
+ return { ...doc, content: [...paragraphs.slice(0, -1), { ...last, content: [...last.content, run] }] };
4044
+ };
4038
4045
  var convertTimerBlock = (raw, report) => {
4039
4046
  const omit = () => {
4040
4047
  report?.({ detail: "Unreadable post-purchase timer display", kind: "section" });
@@ -4045,17 +4052,15 @@ var convertTimerBlock = (raw, report) => {
4045
4052
  const { title } = parsed.data.settings;
4046
4053
  const styles2 = parsed.data.styles;
4047
4054
  const horizontal = { center: "center", leading: "start", trailing: "end" }[styles2.textAlignment];
4055
+ const countdown = styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize);
4048
4056
  const content = typeof title === "string" ? {
4049
4057
  content: [
4050
4058
  {
4051
4059
  attrs: { textAlign: horizontal },
4052
- content: [
4053
- styledTextRun(title, styles2.titleEmphasized, styles2.titleSize),
4054
- styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize)
4055
- ]
4060
+ content: [styledTextRun(title, styles2.titleEmphasized, styles2.titleSize), countdown]
4056
4061
  }
4057
4062
  ]
4058
- } : title;
4063
+ } : withCountdown(title, countdown, horizontal);
4059
4064
  const text2 = CABTextSection.safeParse({ content: { en: content } });
4060
4065
  if (!text2.success) return omit();
4061
4066
  return [
@@ -4071,7 +4076,7 @@ var convertTimerBlock = (raw, report) => {
4071
4076
  // src/transforms/postPurchase/convertMessagePreview.ts
4072
4077
  var MessageWidget = z33.object({
4073
4078
  blocks: z33.array(z33.unknown()).min(1),
4074
- type: z33.literal("shopify_post_purchase_extension")
4079
+ type: z33.literal(WidgetType.shopify_post_purchase_extension)
4075
4080
  });
4076
4081
  var BlockType2 = z33.object({ type: z33.string() });
4077
4082
  var converters = {
@@ -4090,7 +4095,7 @@ var convertMessagePreview = (id, settings, report) => {
4090
4095
  sections: parsed.data.blocks.flatMap((block) => {
4091
4096
  const type = BlockType2.safeParse(block);
4092
4097
  const kind = type.success ? type.data.type : void 0;
4093
- const convert2 = kind ? converters[kind] : void 0;
4098
+ const convert2 = kind && Object.hasOwn(converters, kind) ? converters[kind] : void 0;
4094
4099
  if (convert2) return convert2(block, report);
4095
4100
  report?.({
4096
4101
  detail: kind ? `Unsupported post-purchase block type: ${kind}` : "Unreadable post-purchase block: missing type",
@@ -1408,7 +1408,14 @@ var CABReviewsSection = import_zod17.z.object({
1408
1408
  sectionId: import_zod17.z.uuid().default(() => (0, import_uuid10.v7)()),
1409
1409
  sectionType: import_zod17.z.literal(SectionType.reviews).default(SectionType.reviews),
1410
1410
  size: import_zod17.z.enum(reviewsSizes).default(ReviewsSize.default),
1411
- strokeColor: import_zod17.z.string().default("#fadb14"),
1411
+ /**
1412
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1413
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1414
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1415
+ * the whole reviews section.
1416
+ */
1417
+ strokeColor: import_zod17.z.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1418
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1412
1419
  strokeWidth: import_zod17.z.number().nonnegative().default(0)
1413
1420
  });
1414
1421
 
@@ -3884,7 +3891,9 @@ var convertTextBlock = (raw, report) => {
3884
3891
  result: settingsSchema.shape[field].safeParse(shell.data.settings[field])
3885
3892
  }));
3886
3893
  const horizontal = { center: "center", leading: "start", trailing: "end" }[shell.data.settings.horizontalAlignment];
3887
- const styled = values.every(({ result }) => result.success && typeof result.data === "string");
3894
+ const styled = values.every(
3895
+ ({ result }) => result.success && (result.data === null || typeof result.data === "string")
3896
+ );
3888
3897
  const omit = (field) => {
3889
3898
  report?.({ detail: `Unreadable post-purchase text field: ${field}`, kind: "section" });
3890
3899
  return [];
@@ -3897,20 +3906,18 @@ var convertTextBlock = (raw, report) => {
3897
3906
  const section = CABTextSection.safeParse({ content: { en: content } });
3898
3907
  return section.success ? [section.data] : omit(field);
3899
3908
  });
3900
- return [
3901
- CABLayoutSection.parse({
3902
- alignment: {
3903
- horizontal
3904
- },
3905
- sections
3906
- })
3907
- ];
3909
+ return sections.length ? [CABLayoutSection.parse({ alignment: { horizontal }, sections })] : [];
3908
3910
  };
3909
3911
 
3910
3912
  // src/transforms/postPurchase/convertTimerBlock.ts
3911
3913
  var Presentation = TimerBlock.pick({ styles: true }).extend({
3912
3914
  settings: TimerBlock.shape.settings.pick({ title: true })
3913
3915
  });
3916
+ var withCountdown = (doc, run, textAlign) => {
3917
+ const paragraphs = doc.content.length ? doc.content : [TiptapParagraph.parse({ attrs: { textAlign } })];
3918
+ const last = paragraphs[paragraphs.length - 1];
3919
+ return { ...doc, content: [...paragraphs.slice(0, -1), { ...last, content: [...last.content, run] }] };
3920
+ };
3914
3921
  var convertTimerBlock = (raw, report) => {
3915
3922
  const omit = () => {
3916
3923
  report?.({ detail: "Unreadable post-purchase timer display", kind: "section" });
@@ -3921,17 +3928,15 @@ var convertTimerBlock = (raw, report) => {
3921
3928
  const { title } = parsed.data.settings;
3922
3929
  const styles2 = parsed.data.styles;
3923
3930
  const horizontal = { center: "center", leading: "start", trailing: "end" }[styles2.textAlignment];
3931
+ const countdown = styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize);
3924
3932
  const content = typeof title === "string" ? {
3925
3933
  content: [
3926
3934
  {
3927
3935
  attrs: { textAlign: horizontal },
3928
- content: [
3929
- styledTextRun(title, styles2.titleEmphasized, styles2.titleSize),
3930
- styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize)
3931
- ]
3936
+ content: [styledTextRun(title, styles2.titleEmphasized, styles2.titleSize), countdown]
3932
3937
  }
3933
3938
  ]
3934
- } : title;
3939
+ } : withCountdown(title, countdown, horizontal);
3935
3940
  const text = CABTextSection.safeParse({ content: { en: content } });
3936
3941
  if (!text.success) return omit();
3937
3942
  return [
@@ -3947,7 +3952,7 @@ var convertTimerBlock = (raw, report) => {
3947
3952
  // src/transforms/postPurchase/convertMessagePreview.ts
3948
3953
  var MessageWidget = import_zod27.z.object({
3949
3954
  blocks: import_zod27.z.array(import_zod27.z.unknown()).min(1),
3950
- type: import_zod27.z.literal("shopify_post_purchase_extension")
3955
+ type: import_zod27.z.literal(WidgetType.shopify_post_purchase_extension)
3951
3956
  });
3952
3957
  var BlockType2 = import_zod27.z.object({ type: import_zod27.z.string() });
3953
3958
  var converters = {
@@ -3966,7 +3971,7 @@ var convertMessagePreview = (id, settings, report) => {
3966
3971
  sections: parsed.data.blocks.flatMap((block) => {
3967
3972
  const type = BlockType2.safeParse(block);
3968
3973
  const kind = type.success ? type.data.type : void 0;
3969
- const convert2 = kind ? converters[kind] : void 0;
3974
+ const convert2 = kind && Object.hasOwn(converters, kind) ? converters[kind] : void 0;
3970
3975
  if (convert2) return convert2(block, report);
3971
3976
  report?.({
3972
3977
  detail: kind ? `Unsupported post-purchase block type: ${kind}` : "Unreadable post-purchase block: missing type",
@@ -1347,7 +1347,14 @@ var CABReviewsSection = z17.object({
1347
1347
  sectionId: z17.uuid().default(() => uuidv710()),
1348
1348
  sectionType: z17.literal(SectionType.reviews).default(SectionType.reviews),
1349
1349
  size: z17.enum(reviewsSizes).default(ReviewsSize.default),
1350
- strokeColor: z17.string().default("#fadb14"),
1350
+ /**
1351
+ * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
1352
+ * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
1353
+ * string: its converter must validate and fall back to this default rather than let one odd value drop
1354
+ * the whole reviews section.
1355
+ */
1356
+ strokeColor: z17.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
1357
+ /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
1351
1358
  strokeWidth: z17.number().nonnegative().default(0)
1352
1359
  });
1353
1360
 
@@ -3823,7 +3830,9 @@ var convertTextBlock = (raw, report) => {
3823
3830
  result: settingsSchema.shape[field].safeParse(shell.data.settings[field])
3824
3831
  }));
3825
3832
  const horizontal = { center: "center", leading: "start", trailing: "end" }[shell.data.settings.horizontalAlignment];
3826
- const styled = values.every(({ result }) => result.success && typeof result.data === "string");
3833
+ const styled = values.every(
3834
+ ({ result }) => result.success && (result.data === null || typeof result.data === "string")
3835
+ );
3827
3836
  const omit = (field) => {
3828
3837
  report?.({ detail: `Unreadable post-purchase text field: ${field}`, kind: "section" });
3829
3838
  return [];
@@ -3836,20 +3845,18 @@ var convertTextBlock = (raw, report) => {
3836
3845
  const section = CABTextSection.safeParse({ content: { en: content } });
3837
3846
  return section.success ? [section.data] : omit(field);
3838
3847
  });
3839
- return [
3840
- CABLayoutSection.parse({
3841
- alignment: {
3842
- horizontal
3843
- },
3844
- sections
3845
- })
3846
- ];
3848
+ return sections.length ? [CABLayoutSection.parse({ alignment: { horizontal }, sections })] : [];
3847
3849
  };
3848
3850
 
3849
3851
  // src/transforms/postPurchase/convertTimerBlock.ts
3850
3852
  var Presentation = TimerBlock.pick({ styles: true }).extend({
3851
3853
  settings: TimerBlock.shape.settings.pick({ title: true })
3852
3854
  });
3855
+ var withCountdown = (doc, run, textAlign) => {
3856
+ const paragraphs = doc.content.length ? doc.content : [TiptapParagraph.parse({ attrs: { textAlign } })];
3857
+ const last = paragraphs[paragraphs.length - 1];
3858
+ return { ...doc, content: [...paragraphs.slice(0, -1), { ...last, content: [...last.content, run] }] };
3859
+ };
3853
3860
  var convertTimerBlock = (raw, report) => {
3854
3861
  const omit = () => {
3855
3862
  report?.({ detail: "Unreadable post-purchase timer display", kind: "section" });
@@ -3860,17 +3867,15 @@ var convertTimerBlock = (raw, report) => {
3860
3867
  const { title } = parsed.data.settings;
3861
3868
  const styles2 = parsed.data.styles;
3862
3869
  const horizontal = { center: "center", leading: "start", trailing: "end" }[styles2.textAlignment];
3870
+ const countdown = styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize);
3863
3871
  const content = typeof title === "string" ? {
3864
3872
  content: [
3865
3873
  {
3866
3874
  attrs: { textAlign: horizontal },
3867
- content: [
3868
- styledTextRun(title, styles2.titleEmphasized, styles2.titleSize),
3869
- styledTextRun(" {{timeRemaining}}", styles2.timerEmphasized, styles2.timerSize)
3870
- ]
3875
+ content: [styledTextRun(title, styles2.titleEmphasized, styles2.titleSize), countdown]
3871
3876
  }
3872
3877
  ]
3873
- } : title;
3878
+ } : withCountdown(title, countdown, horizontal);
3874
3879
  const text = CABTextSection.safeParse({ content: { en: content } });
3875
3880
  if (!text.success) return omit();
3876
3881
  return [
@@ -3886,7 +3891,7 @@ var convertTimerBlock = (raw, report) => {
3886
3891
  // src/transforms/postPurchase/convertMessagePreview.ts
3887
3892
  var MessageWidget = z27.object({
3888
3893
  blocks: z27.array(z27.unknown()).min(1),
3889
- type: z27.literal("shopify_post_purchase_extension")
3894
+ type: z27.literal(WidgetType.shopify_post_purchase_extension)
3890
3895
  });
3891
3896
  var BlockType2 = z27.object({ type: z27.string() });
3892
3897
  var converters = {
@@ -3905,7 +3910,7 @@ var convertMessagePreview = (id, settings, report) => {
3905
3910
  sections: parsed.data.blocks.flatMap((block) => {
3906
3911
  const type = BlockType2.safeParse(block);
3907
3912
  const kind = type.success ? type.data.type : void 0;
3908
- const convert2 = kind ? converters[kind] : void 0;
3913
+ const convert2 = kind && Object.hasOwn(converters, kind) ? converters[kind] : void 0;
3909
3914
  if (convert2) return convert2(block, report);
3910
3915
  report?.({
3911
3916
  detail: kind ? `Unsupported post-purchase block type: ${kind}` : "Unreadable post-purchase block: missing type",
@@ -1,5 +1,11 @@
1
1
  import { WidgetType } from '../../schema/widgets/checkout-and-beyond/constants';
2
- /** A saved post-purchase message-block widget: `type` plus its ordered `blocks`, no `version` discriminator. */
2
+ /**
3
+ * A saved post-purchase widget as `/cab/sections` sees it: `type` plus its ordered `blocks`, no `version`
4
+ * discriminator. This is the shape of EVERY `PostPurchaseSettings` (its `blocks` even default to three
5
+ * blocks), so the guard below claims every saved post-purchase widget by design — the section tree is the
6
+ * widget's presentation, and the non-block settings (`discount`, `products`, `integrations`, …) are read by
7
+ * the post-purchase session/Decision contracts, not converted into sections.
8
+ */
3
9
  export type V1PostPurchaseSettings = {
4
10
  blocks: unknown[];
5
11
  type: typeof WidgetType.shopify_post_purchase_extension;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebuy/rebuy",
3
- "version": "3.28.0",
3
+ "version": "3.28.1",
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.",