@useinsider/guido 3.16.0-beta.0faee0c → 3.16.0-beta.1fe8cea

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/components/Guido.vue.js +8 -8
  2. package/dist/components/Guido.vue2.js +78 -76
  3. package/dist/components/organisms/AutoSaveController.vue2.js +9 -8
  4. package/dist/components/organisms/header/EditorActions.vue.js +2 -2
  5. package/dist/components/organisms/header/EditorActions.vue2.js +74 -43
  6. package/dist/components/organisms/header/LeftSlot.vue.js +5 -5
  7. package/dist/components/organisms/header/LeftSlot.vue2.js +46 -23
  8. package/dist/components/organisms/validation/ValidationErrorDrawer.vue.js +20 -0
  9. package/dist/components/organisms/validation/ValidationErrorDrawer.vue2.js +48 -0
  10. package/dist/components/wrappers/WpDrawer.vue.js +9 -7
  11. package/dist/components/wrappers/WpDrawer.vue2.js +1 -1
  12. package/dist/composables/useHtmlValidator.js +1 -0
  13. package/dist/composables/useSave.js +84 -41
  14. package/dist/composables/useSaveValidation.js +276 -0
  15. package/dist/composables/useSaveValidationRollout.js +9 -0
  16. package/dist/composables/useStripo.js +29 -30
  17. package/dist/composables/validators/useCouponBlockValidator.js +24 -21
  18. package/dist/composables/validators/useUnsubscribeBlockValidator.js +34 -27
  19. package/dist/enums/save-validation.js +4 -0
  20. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +31 -24
  21. package/dist/guido.css +1 -1
  22. package/dist/src/@types/save-validation.d.ts +19 -0
  23. package/dist/src/components/Guido.vue.d.ts +17 -1
  24. package/dist/src/components/organisms/header/EditorActions.vue.d.ts +17 -1
  25. package/dist/src/components/organisms/header/HeaderWrapper.vue.d.ts +17 -1
  26. package/dist/src/components/organisms/header/RightSlot.vue.d.ts +17 -1
  27. package/dist/src/components/organisms/validation/ValidationErrorDrawer.vue.d.ts +2 -0
  28. package/dist/src/components/wrappers/WpDrawer.vue.d.ts +4 -3
  29. package/dist/src/composables/useHtmlValidator.d.ts +14 -0
  30. package/dist/src/composables/useSave.d.ts +6 -1
  31. package/dist/src/composables/useSaveValidation.d.ts +6 -0
  32. package/dist/src/composables/useSaveValidationRollout.d.ts +3 -0
  33. package/dist/src/composables/validators/useCouponBlockValidator.d.ts +3 -0
  34. package/dist/src/composables/validators/useUnsubscribeBlockValidator.d.ts +10 -0
  35. package/dist/src/enums/save-validation.d.ts +27 -0
  36. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +5 -0
  37. package/dist/src/stores/config.d.ts +5 -0
  38. package/dist/src/stores/save-validation.d.ts +22 -0
  39. package/dist/src/utils/templatePreparation.d.ts +30 -0
  40. package/dist/stores/config.js +13 -6
  41. package/dist/stores/save-validation.js +30 -0
  42. package/dist/utils/templatePreparation.js +28 -25
  43. package/package.json +1 -1
@@ -1,24 +1,27 @@
1
- import { ToasterTypeOptions as c } from "../../enums/toaster.js";
2
- import { COUPON_PLACEHOLDER_DEFAULT as i, COUPON_PLACEHOLDER_LIQUID as l } from "../../extensions/Blocks/CouponBlock/template.js";
3
- import { useToaster as m } from "../useToaster.js";
4
- import { useTranslations as p } from "../useTranslations.js";
5
- const u = /* @__PURE__ */ new Set([
6
- i,
7
- l
8
- ]), A = () => {
9
- const { showToaster: t } = m(), r = p();
10
- return { validateCouponBlockTags: (e) => {
11
- const n = new DOMParser().parseFromString(e, "text/html");
12
- return Array.from(n.querySelectorAll(".coupon-block")).find((s) => {
13
- var o;
14
- const a = ((o = s.textContent) == null ? void 0 : o.trim()) ?? "";
15
- return !u.has(a);
16
- }) ? (t({
17
- type: c.Warning,
18
- message: r("newsletter.coupon-tag-modified")
19
- }), !1) : !0;
20
- } };
1
+ import { ToasterTypeOptions as n } from "../../enums/toaster.js";
2
+ import { COUPON_PLACEHOLDER_DEFAULT as s, COUPON_PLACEHOLDER_LIQUID as a } from "../../extensions/Blocks/CouponBlock/template.js";
3
+ import { useToaster as i } from "../useToaster.js";
4
+ import { useTranslations as l } from "../useTranslations.js";
5
+ const c = /* @__PURE__ */ new Set([
6
+ s,
7
+ a
8
+ ]), p = "newsletter.coupon-tag-modified";
9
+ function u(r) {
10
+ const e = new DOMParser().parseFromString(r, "text/html");
11
+ return Array.from(e.querySelectorAll(".coupon-block")).map((o) => {
12
+ var t;
13
+ return ((t = o.textContent) == null ? void 0 : t.trim()) ?? "";
14
+ }).filter((o) => !c.has(o));
15
+ }
16
+ const T = () => {
17
+ const { showToaster: r } = i(), e = l();
18
+ return { validateCouponBlockTags: (t) => u(t).length > 0 ? (r({
19
+ type: n.Warning,
20
+ message: e(p)
21
+ }), !1) : !0 };
21
22
  };
22
23
  export {
23
- A as useCouponBlockValidator
24
+ p as COUPON_TAG_MODIFIED_KEY,
25
+ u as findInvalidCouponBlocks,
26
+ T as useCouponBlockValidator
24
27
  };
@@ -1,8 +1,8 @@
1
- import { ToasterTypeOptions as i } from "../../enums/toaster.js";
1
+ import { ToasterTypeOptions as c } from "../../enums/toaster.js";
2
2
  import { PAGE_TYPES as u } from "../../enums/unsubscribe.js";
3
- import { UNSUBSCRIBE_BLOCK_SELECTOR as l, DATA_ATTRIBUTES as a } from "../../extensions/Blocks/Unsubscribe/utils/constants.js";
4
- import { useToaster as E } from "../useToaster.js";
5
- import { useTranslations as m } from "../useTranslations.js";
3
+ import { UNSUBSCRIBE_BLOCK_SELECTOR as i, DATA_ATTRIBUTES as a } from "../../extensions/Blocks/Unsubscribe/utils/constants.js";
4
+ import { useToaster as p } from "../useToaster.js";
5
+ import { useTranslations as l } from "../useTranslations.js";
6
6
  const T = [
7
7
  {
8
8
  pageType: u.GLOBAL_UNSUBSCRIBE,
@@ -12,30 +12,37 @@ const T = [
12
12
  pageType: u.SUBSCRIPTION_PREFERENCE_CENTER,
13
13
  messageKey: "unsubscription-preference.duplicate-pref-center"
14
14
  }
15
- ], g = "unsubscription-preference.duplicate-both", b = "unsubscription-preference.select-page-to-continue", P = () => {
16
- const { showToaster: c } = E(), p = m();
17
- return { validateUnsubscribeBlockUniqueness: (n) => {
18
- const o = new DOMParser().parseFromString(n, "text/html"), r = /* @__PURE__ */ new Map();
19
- o.querySelectorAll(l).forEach((e) => {
20
- const s = Number(e.getAttribute(a.PAGE_TYPE));
21
- r.set(s, (r.get(s) ?? 0) + 1);
22
- });
23
- const t = T.filter((e) => (r.get(e.pageType) ?? 0) > 1).map((e) => e.messageKey);
24
- return t.length ? (c({
25
- type: i.Error,
26
- message: p(t.length > 1 ? g : t[0])
15
+ ], g = "unsubscription-preference.duplicate-both", E = "unsubscription-preference.select-page-to-continue";
16
+ function m(r) {
17
+ const s = new DOMParser().parseFromString(r, "text/html"), t = /* @__PURE__ */ new Map();
18
+ return s.querySelectorAll(i).forEach((n) => {
19
+ const e = Number(n.getAttribute(a.PAGE_TYPE));
20
+ t.set(e, (t.get(e) ?? 0) + 1);
21
+ }), T.filter((n) => (t.get(n.pageType) ?? 0) > 1);
22
+ }
23
+ function b(r) {
24
+ const s = new DOMParser().parseFromString(r, "text/html");
25
+ return Array.from(s.querySelectorAll(i)).filter((t) => {
26
+ const n = t.getAttribute(a.PAGE_TYPE), e = t.getAttribute(a.PAGE_LIST);
27
+ return !n || !e;
28
+ }).length;
29
+ }
30
+ const y = () => {
31
+ const { showToaster: r } = p(), s = l();
32
+ return { validateUnsubscribeBlockUniqueness: (e) => {
33
+ const o = m(e);
34
+ return o.length ? (r({
35
+ type: c.Error,
36
+ message: s(o.length > 1 ? g : o[0].messageKey)
27
37
  }), !1) : !0;
28
- }, validateUnsubscribeBlockHasTemplate: (n) => {
29
- const o = new DOMParser().parseFromString(n, "text/html");
30
- return Array.from(o.querySelectorAll(l)).some((t) => {
31
- const e = t.getAttribute(a.PAGE_TYPE), s = t.getAttribute(a.PAGE_LIST);
32
- return !e || !s;
33
- }) ? (c({
34
- type: i.Warning,
35
- message: p(b)
36
- }), !1) : !0;
37
- } };
38
+ }, validateUnsubscribeBlockHasTemplate: (e) => b(e) === 0 ? !0 : (r({
39
+ type: c.Warning,
40
+ message: s(E)
41
+ }), !1) };
38
42
  };
39
43
  export {
40
- P as useUnsubscribeBlockValidator
44
+ E as EMPTY_BLOCK_KEY,
45
+ b as countUnsubBlocksWithoutPage,
46
+ m as findDuplicateUnsubPageTypes,
47
+ y as useUnsubscribeBlockValidator
41
48
  };
@@ -0,0 +1,4 @@
1
+ var n = /* @__PURE__ */ ((s) => (s.CouponTagModified = "coupon-tag-modified", s.DuplicateUnsubBlock = "duplicate-unsub-block", s.UnsubBlockMissingTemplate = "unsub-block-missing-template", s.LiquidSyntax = "liquid-syntax", s.InvalidLinks = "invalid-links", s.LinkCheckInProgress = "link-check-in-progress", s.CustomFieldScript = "custom-field-script", s.EmptyHtml = "empty-html", s.MismatchedBraces = "mismatched-braces", s.InvalidDynamicTag = "invalid-dynamic-tag", s.InvalidCustomField = "invalid-custom-field", s.ConditionsSyntax = "conditions-syntax", s.WrongEqualityOperator = "wrong-equality-operator", s.MissingIfEndif = "missing-if-endif", s.ConditionTagSpacing = "condition-tag-spacing", s.BracesInsideIfTag = "braces-inside-if-tag", s.RecommendationRequiredFields = "recommendation-required-fields", s.RecommendationFilters = "recommendation-filters", s.InvalidImageType = "invalid-image-type", s.CheckboxUnsubGroup = "checkbox-unsub-group", s.RadioButtonUnsubGroup = "radio-button-unsub-group", s))(n || {});
2
+ export {
3
+ n as SaveValidationRule
4
+ };
@@ -1,20 +1,20 @@
1
- import { getRecommendationFeedSourceMaps as g, getOperatorOptions as R, PriceAttributes as C } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as y } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as G } from "../../../../stores/config.js";
4
- import { defineStore as F } from "pinia";
1
+ import { getRecommendationFeedSourceMaps as g, getOperatorOptions as C, PriceAttributes as y } from "../../../../enums/extensions/recommendationBlock.js";
2
+ import { useRecommendationApi as G } from "../../../../services/recommendationApi.js";
3
+ import { useConfigStore as F } from "../../../../stores/config.js";
4
+ import { defineStore as P } from "pinia";
5
5
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as P, DEFAULT_CARDS_IN_ROW as D } from "../constants/layout.js";
6
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as D, DEFAULT_CARDS_IN_ROW as v } from "../constants/layout.js";
7
7
  import { EXCLUDED_ALGORITHM_IDS as w } from "../constants/defaultConfig.js";
8
8
  import { getDefaultProducts as S } from "../templates/utils.js";
9
9
  import { generateCompleteFilterQuery as b } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as v } from "../utils/partnerCustomizations.js";
11
- import { isFilterValid as N } from "../validation/filterSchema.js";
12
- import { isConfigValid as x } from "../validation/requiredFields.js";
13
- const h = y();
10
+ import { getPartnerRecommendationParams as N } from "../utils/partnerCustomizations.js";
11
+ import { isFilterValid as x } from "../validation/filterSchema.js";
12
+ import { isConfigValid as I } from "../validation/requiredFields.js";
13
+ const h = G();
14
14
  let m = null, u = null, d = null;
15
- function k() {
15
+ function R() {
16
16
  return {
17
- cardsInRow: D,
17
+ cardsInRow: v,
18
18
  currencySettings: {
19
19
  name: "USD",
20
20
  value: "USD",
@@ -28,7 +28,7 @@ function k() {
28
28
  productIds: [],
29
29
  id: 1,
30
30
  language: "en_US",
31
- mobileCardsInRow: P,
31
+ mobileCardsInRow: D,
32
32
  mobileLayoutEnabled: !1,
33
33
  orientation: "grid",
34
34
  recommendedProducts: [],
@@ -43,9 +43,9 @@ function k() {
43
43
  priceHideIfSameAsDiscounted: !1
44
44
  };
45
45
  }
46
- function I() {
46
+ function k() {
47
47
  return {
48
- recommendationConfigs: k(),
48
+ recommendationConfigs: R(),
49
49
  recommendationProducts: [],
50
50
  filterStatus: !1,
51
51
  filterSelectionDrawerStatus: !1,
@@ -63,7 +63,7 @@ const L = () => ({
63
63
  blockStates: {},
64
64
  currentRecommendationId: null,
65
65
  configVersion: 0
66
- }), $ = F("guidoRecommendationExtension", {
66
+ }), $ = P("guidoRecommendationExtension", {
67
67
  state: () => L(),
68
68
  getters: {
69
69
  // ====================================================================
@@ -74,7 +74,7 @@ const L = () => ({
74
74
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
75
75
  */
76
76
  recommendationConfigs(t) {
77
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : k();
77
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : R();
78
78
  },
79
79
  /**
80
80
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -138,7 +138,7 @@ const L = () => ({
138
138
  getFilterList() {
139
139
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
140
140
  let e;
141
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : C.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
141
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : y.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
142
142
  text: t.displayName,
143
143
  value: e,
144
144
  type: t.attributeType
@@ -160,7 +160,7 @@ const L = () => ({
160
160
  setCurrentBlock(t) {
161
161
  this.blockStates[t] || (this.blockStates = {
162
162
  ...this.blockStates,
163
- [t]: I()
163
+ [t]: k()
164
164
  }), this.currentRecommendationId = t;
165
165
  },
166
166
  /**
@@ -228,7 +228,7 @@ const L = () => ({
228
228
  decimalCount: String(e.currencyDecimalCount),
229
229
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
230
230
  thousandSeparator: r(e.currencyThousandSeparator, ".")
231
- }, c = !this.blockStates[t], i = c ? I() : this.blockStates[t];
231
+ }, c = !this.blockStates[t], i = c ? k() : this.blockStates[t];
232
232
  i.recommendationConfigs = {
233
233
  ...i.recommendationConfigs,
234
234
  strategy: e.strategy,
@@ -266,7 +266,7 @@ const L = () => ({
266
266
  * Creates a filter with the first available attribute and operator pre-selected.
267
267
  */
268
268
  createDefaultFilter(t, e) {
269
- const [r] = this.getFilterList, [n] = R(r == null ? void 0 : r.type);
269
+ const [r] = this.getFilterList, [n] = C(r == null ? void 0 : r.type);
270
270
  return {
271
271
  type: "standardFilter",
272
272
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -383,7 +383,7 @@ const L = () => ({
383
383
  const n = [...e.recommendationConfigs.filters];
384
384
  n[r] = {
385
385
  ...t,
386
- isValid: N(t)
386
+ isValid: x(t)
387
387
  }, e.recommendationConfigs.filters = n;
388
388
  }
389
389
  },
@@ -429,7 +429,14 @@ const L = () => ({
429
429
  * every block's recommendationConfigs across user edits.
430
430
  */
431
431
  hasInvalidBlock() {
432
- return Object.values(this.blockStates).some((t) => !x(t.recommendationConfigs, this));
432
+ return Object.values(this.blockStates).some((t) => !I(t.recommendationConfigs, this));
433
+ },
434
+ /**
435
+ * Per-instance counterpart to `hasInvalidBlock`: the ids of every block
436
+ * that fails required-field validation, one drawer row each (SD-147430).
437
+ */
438
+ invalidBlockIds() {
439
+ return Object.entries(this.blockStates).filter(([, t]) => !I(t.recommendationConfigs, this)).map(([t]) => t);
433
440
  },
434
441
  // ====================================================================
435
442
  // Per-Block Product Fetching
@@ -450,7 +457,7 @@ const L = () => ({
450
457
  },
451
458
  async _doFetchProducts() {
452
459
  var p;
453
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = r.filters.filter((l) => l.isValid), c = b(n), i = ((p = g().find((l) => l.key === r.strategy)) == null ? void 0 : p.path) || "", o = G(), s = parseInt(r.size) || 6, a = {
460
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = r.filters.filter((l) => l.isValid), c = b(n), i = ((p = g().find((l) => l.key === r.strategy)) == null ? void 0 : p.path) || "", o = F(), s = parseInt(r.size) || 6, a = {
454
461
  locale: r.language,
455
462
  currency: r.currencySettings.value,
456
463
  partnerName: o.partnerName,
@@ -460,7 +467,7 @@ const L = () => ({
460
467
  };
461
468
  r.strategy === "manualMerchandising" ? a.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), c && (a.filter = c), r.shuffleProducts && (a.shuffle = !0), Object.assign(
462
469
  a,
463
- v(o.partnerName, r.strategy)
470
+ N(o.partnerName, r.strategy)
464
471
  );
465
472
  let f;
466
473
  try {
package/dist/guido.css CHANGED
@@ -1 +1 @@
1
- .gap-16[data-v-5553d071],.gap-16[data-v-0e1b0c54]{gap:16px}[data-v-73199fa4] .in-button-v2__wrapper{line-height:0}[data-v-22226124] .in-segments-wrapper__button_selected,[data-v-22226124] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb;color:#0010ac;border-color:#0010ac}[data-v-2cb418af] .in-progress-wrapper__progress p span:last-child{display:none!important}[data-v-2cb418af] .in-progress-description-status{display:none!important}.view-options-wrapper[data-v-87033101]{position:relative;display:inline-block}.new-tag[data-v-87033101]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-87033101] .guido__view-option-selection-desktop svg,[data-v-87033101] .guido__view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-87033101] .in-segments-wrapper__button_selected,[data-v-87033101] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-87033101] .in-tooltip-wrapper-v2__icon{cursor:pointer}.editor-toolbar[data-v-173c3a40]{gap:4px}.version-history-item[data-v-ee4b9c3f]{flex-basis:200px}.version-history[data-v-fad98586]{gap:8px}.version-history__toolbar[data-v-fad98586]{gap:4px}.view-options-wrapper[data-v-6ebabfe4]{position:relative;display:inline-block}.new-tag[data-v-6ebabfe4]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-6ebabfe4] .guido__verion-history-view-option-selection-desktop svg,[data-v-6ebabfe4] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-6ebabfe4] .in-segments-wrapper__button_selected,[data-v-6ebabfe4] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-6ebabfe4] .in-tooltip-wrapper-v2__icon{cursor:pointer}.auto-save-toggle[data-v-2c964af4]{position:relative}.auto-save-toggle__info-box[data-v-2c964af4]{position:absolute;top:100%;left:0;z-index:10;width:280px}.editor-actions[data-v-6e711fd7]{gap:4px}.header-wrapper[data-v-d11dd577]{min-width:1000px}.guido-loading__wrapper[data-v-07c4b2d8]{height:100%;top:75px!important;bottom:0!important}.guido-editor__wrapper[data-v-b0d3b445]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-b0d3b445]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-b0d3b445]{height:calc(100vh - 75px - var(--ribbon-offset))}[data-v-293f1c47] .in-breadcrumb-wrapper__links{cursor:pointer}.templates-wrapper[data-v-91a26acd]{gap:16px;grid-template-columns:repeat(3,1fr)}.templates-wrapper .template-wrapper[data-v-91a26acd]{cursor:pointer}.templates-wrapper .template-wrapper .template-container[data-v-91a26acd]{height:274px;padding:2px;transition:none}.templates-wrapper .template-wrapper .template-container.selected[data-v-91a26acd]{padding:1px}.templates-wrapper .template-wrapper .template-container .thumbnail[data-v-91a26acd]{object-fit:cover;transform:scale(1)}[data-v-b9a93c6e] .guido__verion-history-view-option-selection-desktop svg,[data-v-b9a93c6e] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-b9a93c6e] .in-segments-wrapper__button_selected,[data-v-b9a93c6e] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}.error-list[data-v-c3fd5d4b]{gap:16px}.desktop-browser-header[data-v-d86c5af5]{height:79px;min-height:79px}.desktop-browser-header__left[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:378px}.desktop-browser-header__center[data-v-d86c5af5]{height:79px;background-repeat:repeat-x;background-size:auto 100%;background-position:left top}.desktop-browser-header__right[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:112px}.desktop-preview[data-v-cf946232]{min-width:602px;height:70vh;min-height:583px;border-radius:10px}.desktop-preview iframe[data-v-cf946232]{min-height:504px}.iframe-wrapper[data-v-f255b2bb]{width:258px}.iframe-scaled[data-v-f255b2bb]{width:320px;height:124.0310077519%;transform:scale(.80625);transform-origin:top left}.cropped-text[data-v-eb3d05d7]{width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mobile-preview-wrapper__phone[data-v-3f472f96]{width:282px}.mobile-preview-wrapper__phone img[data-v-3f472f96]{object-fit:cover;border-radius:44px}.mobile-preview-wrapper__content[data-v-3f472f96]{width:258px;height:450px;left:12px}[data-v-6bcaca8a] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-f16f20f8] .vueperslides__bullets{pointer-events:none!important}[data-v-f16f20f8] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-bb3cca55] .vueperslides__bullets{pointer-events:none!important}[data-v-bb3cca55] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-b09e80c4] .vueperslides__bullets{pointer-events:none!important}[data-v-b09e80c4] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-9bdef600] .vueperslides__parallax-wrapper{padding-bottom:110px!important}
1
+ .gap-16[data-v-5553d071],.gap-16[data-v-0e1b0c54]{gap:16px}[data-v-74d455fc] .in-button-v2__wrapper{line-height:0}[data-v-22226124] .in-segments-wrapper__button_selected,[data-v-22226124] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb;color:#0010ac;border-color:#0010ac}[data-v-2cb418af] .in-progress-wrapper__progress p span:last-child{display:none!important}[data-v-2cb418af] .in-progress-description-status{display:none!important}.view-options-wrapper[data-v-87033101]{position:relative;display:inline-block}.new-tag[data-v-87033101]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-87033101] .guido__view-option-selection-desktop svg,[data-v-87033101] .guido__view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-87033101] .in-segments-wrapper__button_selected,[data-v-87033101] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-87033101] .in-tooltip-wrapper-v2__icon{cursor:pointer}.editor-toolbar[data-v-173c3a40]{gap:4px}.version-history-item[data-v-ee4b9c3f]{flex-basis:200px}.version-history[data-v-fad98586]{gap:8px}.version-history__toolbar[data-v-fad98586]{gap:4px}.view-options-wrapper[data-v-6ebabfe4]{position:relative;display:inline-block}.new-tag[data-v-6ebabfe4]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-6ebabfe4] .guido__verion-history-view-option-selection-desktop svg,[data-v-6ebabfe4] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-6ebabfe4] .in-segments-wrapper__button_selected,[data-v-6ebabfe4] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-6ebabfe4] .in-tooltip-wrapper-v2__icon{cursor:pointer}.auto-save-toggle[data-v-2c964af4]{position:relative}.auto-save-toggle__info-box[data-v-2c964af4]{position:absolute;top:100%;left:0;z-index:10;width:280px}.editor-actions[data-v-44d8e7c8]{gap:4px}.header-wrapper[data-v-d11dd577]{min-width:1000px}.guido-loading__wrapper[data-v-07c4b2d8]{height:100%;top:75px!important;bottom:0!important}.validation-error-list[data-v-cdf0f8c8]{gap:16px}.validation-error-row[data-v-cdf0f8c8]{gap:8px;padding:24px;border:1px solid #E1E1E1;border-radius:8px}.validation-error-row__icon[data-v-cdf0f8c8]{flex-shrink:0;font-size:24px;line-height:0}.validation-error-row__text[data-v-cdf0f8c8]{font-size:13px;line-height:16px;color:#292929;word-break:break-word}.guido-editor__wrapper[data-v-f02fdde0]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-f02fdde0]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-f02fdde0]{height:calc(100vh - 75px - var(--ribbon-offset))}[data-v-293f1c47] .in-breadcrumb-wrapper__links{cursor:pointer}.templates-wrapper[data-v-91a26acd]{gap:16px;grid-template-columns:repeat(3,1fr)}.templates-wrapper .template-wrapper[data-v-91a26acd]{cursor:pointer}.templates-wrapper .template-wrapper .template-container[data-v-91a26acd]{height:274px;padding:2px;transition:none}.templates-wrapper .template-wrapper .template-container.selected[data-v-91a26acd]{padding:1px}.templates-wrapper .template-wrapper .template-container .thumbnail[data-v-91a26acd]{object-fit:cover;transform:scale(1)}[data-v-b9a93c6e] .guido__verion-history-view-option-selection-desktop svg,[data-v-b9a93c6e] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-b9a93c6e] .in-segments-wrapper__button_selected,[data-v-b9a93c6e] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}.error-list[data-v-c3fd5d4b]{gap:16px}.desktop-browser-header[data-v-d86c5af5]{height:79px;min-height:79px}.desktop-browser-header__left[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:378px}.desktop-browser-header__center[data-v-d86c5af5]{height:79px;background-repeat:repeat-x;background-size:auto 100%;background-position:left top}.desktop-browser-header__right[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:112px}.desktop-preview[data-v-cf946232]{min-width:602px;height:70vh;min-height:583px;border-radius:10px}.desktop-preview iframe[data-v-cf946232]{min-height:504px}.iframe-wrapper[data-v-f255b2bb]{width:258px}.iframe-scaled[data-v-f255b2bb]{width:320px;height:124.0310077519%;transform:scale(.80625);transform-origin:top left}.cropped-text[data-v-eb3d05d7]{width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mobile-preview-wrapper__phone[data-v-3f472f96]{width:282px}.mobile-preview-wrapper__phone img[data-v-3f472f96]{object-fit:cover;border-radius:44px}.mobile-preview-wrapper__content[data-v-3f472f96]{width:258px;height:450px;left:12px}[data-v-6bcaca8a] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-f16f20f8] .vueperslides__bullets{pointer-events:none!important}[data-v-f16f20f8] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-bb3cca55] .vueperslides__bullets{pointer-events:none!important}[data-v-bb3cca55] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-b09e80c4] .vueperslides__bullets{pointer-events:none!important}[data-v-b09e80c4] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-9bdef600] .vueperslides__parallax-wrapper{padding-bottom:110px!important}
@@ -0,0 +1,19 @@
1
+ import type { SaveValidationRule } from '@@/Enums/save-validation';
2
+ import type { ToasterTypeOptions } from '@@/Enums/toaster';
3
+ /** Which action triggered the validation run. Drives telemetry only in v1. */
4
+ export type SaveValidationIntent = 'save' | 'back' | 'test';
5
+ /**
6
+ * A single validation failure instance, one per drawer row. Names avoid the
7
+ * public `ValidationError` (config validation) collision — NOT exported from
8
+ * library.ts.
9
+ */
10
+ export interface SaveValidationError {
11
+ /** Stable discriminator for tests and de-duplication. */
12
+ rule: SaveValidationRule;
13
+ /** Final translated string — trans() is applied at collect time. */
14
+ message: string;
15
+ /** Raw user content (field name / tag / src) — not i18n. */
16
+ detail?: string;
17
+ /** Mirrors the legacy toaster severity. Kept for the discriminator/tests; the drawer renders a uniform icon and never reads it. */
18
+ type: ToasterTypeOptions;
19
+ }
@@ -15,7 +15,23 @@ declare const _default: import("vue").DefineComponent<__VLS_WithDefaults<__VLS_T
15
15
  close: () => void;
16
16
  };
17
17
  hasChanges: import("vue").ComputedRef<boolean>;
18
- saveSilent: () => Promise<Omit<SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
18
+ saveSilent: () => Promise<{
19
+ dynamicContentList: import("@@/Types/generic").DynamicContent[];
20
+ compiledHtml: string;
21
+ rawHtml: string;
22
+ css: string;
23
+ ampHtml: string;
24
+ ampErrors: string[];
25
+ modules: number[];
26
+ recommendation: {
27
+ campaignUrls: Record<string, string>;
28
+ configs: Record<string, string>;
29
+ };
30
+ unsubscribe: {
31
+ status: boolean;
32
+ config: number[];
33
+ };
34
+ } | undefined> | undefined;
19
35
  setLoading: (value: boolean) => void;
20
36
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {
21
37
  "dynamic-content:open": (detail: {
@@ -1,4 +1,20 @@
1
1
  declare const _default: import("vue").DefineComponent<{}, {
2
- handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
2
+ handleSave: (isSilent: boolean) => Promise<{
3
+ dynamicContentList: import("../../../@types/generic.js").DynamicContent[];
4
+ compiledHtml: string;
5
+ rawHtml: string;
6
+ css: string;
7
+ ampHtml: string;
8
+ ampErrors: string[];
9
+ modules: number[];
10
+ recommendation: {
11
+ campaignUrls: Record<string, string>;
12
+ configs: Record<string, string>;
13
+ };
14
+ unsubscribe: {
15
+ status: boolean;
16
+ config: number[];
17
+ };
18
+ } | undefined> | undefined;
3
19
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
4
20
  export default _default;
@@ -1,4 +1,20 @@
1
1
  declare const _default: import("vue").DefineComponent<{}, {
2
- handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
2
+ handleSave: (isSilent: boolean) => Promise<{
3
+ dynamicContentList: import("../../../@types/generic.js").DynamicContent[];
4
+ compiledHtml: string;
5
+ rawHtml: string;
6
+ css: string;
7
+ ampHtml: string;
8
+ ampErrors: string[];
9
+ modules: number[];
10
+ recommendation: {
11
+ campaignUrls: Record<string, string>;
12
+ configs: Record<string, string>;
13
+ };
14
+ unsubscribe: {
15
+ status: boolean;
16
+ config: number[];
17
+ };
18
+ } | undefined> | undefined;
3
19
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
4
20
  export default _default;
@@ -1,4 +1,20 @@
1
1
  declare const _default: import("vue").DefineComponent<{}, {
2
- handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
2
+ handleSave: (isSilent: boolean) => Promise<{
3
+ dynamicContentList: import("../../../@types/generic.js").DynamicContent[];
4
+ compiledHtml: string;
5
+ rawHtml: string;
6
+ css: string;
7
+ ampHtml: string;
8
+ ampErrors: string[];
9
+ modules: number[];
10
+ recommendation: {
11
+ campaignUrls: Record<string, string>;
12
+ configs: Record<string, string>;
13
+ };
14
+ unsubscribe: {
15
+ status: boolean;
16
+ config: number[];
17
+ };
18
+ } | undefined> | undefined;
3
19
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
4
20
  export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
2
+ export default _default;
@@ -10,11 +10,11 @@ interface Props {
10
10
  status: boolean;
11
11
  titleText: string;
12
12
  }
13
- declare var __VLS_12: {}, __VLS_14: {};
13
+ declare var __VLS_13: {}, __VLS_15: {};
14
14
  type __VLS_Slots = {} & {
15
- headerBottomSlot?: (props: typeof __VLS_12) => any;
15
+ headerBottomSlot?: (props: typeof __VLS_13) => any;
16
16
  } & {
17
- default?: (props: typeof __VLS_14) => any;
17
+ default?: (props: typeof __VLS_15) => any;
18
18
  };
19
19
  declare const __VLS_component: import("vue").DefineComponent<__VLS_WithDefaults<__VLS_TypePropsToOption<Props>, {
20
20
  className: string;
@@ -26,6 +26,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_WithDefaults<
26
26
  cancelOrBackButtonEvent: () => void;
27
27
  onCloseEvent: () => void;
28
28
  primaryButtonEvent: () => void;
29
+ secondaryButtonEvent: () => void;
29
30
  }, string, Readonly<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<Props>, {
30
31
  className: string;
31
32
  size: string;
@@ -27,6 +27,20 @@ export declare function buildPartnerAttributeNameSet(filterList: FiltersResponse
27
27
  * before, so flagging it would false-positive on merge tags like `{{user.name}}`.
28
28
  */
29
29
  export declare function findInvalidCustomFields(html: string, validCustomFields: string[], recommendationBlockIds: string[], ignoredIds: Set<string>, ignoredAttributeNames: Set<string> | null): string[];
30
+ /**
31
+ * Returns the invalid `{{ ... }}` dynamic-content tags found in the HTML.
32
+ *
33
+ * A tag's variable name is the part before the first `|` (everything after it is the fallback).
34
+ * Surrounding whitespace is always allowed (trimmed). A tag is valid only when the variable name
35
+ * is an allowed system token, or it is a clean identifier (`[A-Za-z0-9_.]`) and — when a `|` is
36
+ * present — the fallback after it is not empty. Otherwise the tag is invalid (e.g.
37
+ * `{{Api Response: OA1 Main Item Url}}` — spaces/`:` — or `{{bounce|}}` — empty fallback).
38
+ *
39
+ * `data-*` block-config attribute values are excluded from the scan: blocks such as Items/cart
40
+ * store product merge tags (`data-product_button_link="{{Abandoned Cart Item (1) Url}}"`) that the
41
+ * backend resolves through its own pipeline, so they must not be treated as user-authored tags.
42
+ */
43
+ export declare function getInvalidDynamicContentTags(html: string): string[];
30
44
  export declare const useHtmlValidator: () => {
31
45
  validateHtml: (html: string, customFields: DynamicContent[], isOnSaveValidation?: boolean) => Promise<boolean>;
32
46
  };
@@ -1,4 +1,9 @@
1
1
  import type { SavedTemplateDetails } from '@@/Types/stripo';
2
+ type SaveDetails = Omit<SavedTemplateDetails, 'metadata' | 'silent'>;
3
+ type SaveResult = SaveDetails | undefined;
2
4
  export declare const useSave: () => {
3
- save: (suppressEmit?: boolean, silent?: boolean) => Promise<Omit<SavedTemplateDetails, "metadata" | "silent"> | undefined>;
5
+ save: (suppressEmit?: boolean, silent?: boolean) => Promise<SaveResult>;
6
+ saveWithErrors: () => Promise<SaveResult>;
7
+ saveRawOnly: () => Promise<SaveResult>;
4
8
  };
9
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { DynamicContent } from '@@/Types/generic';
2
+ import type { SaveValidationError } from '@@/Types/save-validation';
3
+ export declare const useSaveValidation: () => {
4
+ collectSaveValidationErrors: (compiledHtml: string, dynamicContentList: DynamicContent[]) => Promise<SaveValidationError[]>;
5
+ validateCurrentTemplate: () => Promise<SaveValidationError[]>;
6
+ };
@@ -0,0 +1,3 @@
1
+ export declare const useSaveValidationRollout: () => {
2
+ isEnabled: import("vue").ComputedRef<boolean>;
3
+ };
@@ -1,3 +1,6 @@
1
+ export declare const COUPON_TAG_MODIFIED_KEY = "newsletter.coupon-tag-modified";
2
+ /** Pure core: the tag text of every coupon block whose code was modified. */
3
+ export declare function findInvalidCouponBlocks(html: string): string[];
1
4
  export declare const useCouponBlockValidator: () => {
2
5
  validateCouponBlockTags: (html: string) => boolean;
3
6
  };
@@ -1,4 +1,14 @@
1
+ declare const SINGLETON_BLOCK_RULES: {
2
+ pageType: number;
3
+ messageKey: string;
4
+ }[];
5
+ export declare const EMPTY_BLOCK_KEY = "unsubscription-preference.select-page-to-continue";
6
+ /** Pure core: the singleton rules whose page type appears more than once. */
7
+ export declare function findDuplicateUnsubPageTypes(html: string): typeof SINGLETON_BLOCK_RULES;
8
+ /** Pure core: count of unsubscribe blocks with no page type or page list chosen. */
9
+ export declare function countUnsubBlocksWithoutPage(html: string): number;
1
10
  export declare const useUnsubscribeBlockValidator: () => {
2
11
  validateUnsubscribeBlockUniqueness: (html: string) => boolean;
3
12
  validateUnsubscribeBlockHasTemplate: (html: string) => boolean;
4
13
  };
14
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stable discriminators for each save-validation rule site — one per rule that
3
+ * can produce a drawer row, kept in sync with the collector + legacy validator chains.
4
+ */
5
+ export declare enum SaveValidationRule {
6
+ CouponTagModified = "coupon-tag-modified",
7
+ DuplicateUnsubBlock = "duplicate-unsub-block",
8
+ UnsubBlockMissingTemplate = "unsub-block-missing-template",
9
+ LiquidSyntax = "liquid-syntax",
10
+ InvalidLinks = "invalid-links",
11
+ LinkCheckInProgress = "link-check-in-progress",
12
+ CustomFieldScript = "custom-field-script",
13
+ EmptyHtml = "empty-html",
14
+ MismatchedBraces = "mismatched-braces",
15
+ InvalidDynamicTag = "invalid-dynamic-tag",
16
+ InvalidCustomField = "invalid-custom-field",
17
+ ConditionsSyntax = "conditions-syntax",
18
+ WrongEqualityOperator = "wrong-equality-operator",
19
+ MissingIfEndif = "missing-if-endif",
20
+ ConditionTagSpacing = "condition-tag-spacing",
21
+ BracesInsideIfTag = "braces-inside-if-tag",
22
+ RecommendationRequiredFields = "recommendation-required-fields",
23
+ RecommendationFilters = "recommendation-filters",
24
+ InvalidImageType = "invalid-image-type",
25
+ CheckboxUnsubGroup = "checkbox-unsub-group",
26
+ RadioButtonUnsubGroup = "radio-button-unsub-group"
27
+ }
@@ -286,6 +286,11 @@ export declare const useRecommendationExtensionStore: import("pinia").StoreDefin
286
286
  * every block's recommendationConfigs across user edits.
287
287
  */
288
288
  hasInvalidBlock(): boolean;
289
+ /**
290
+ * Per-instance counterpart to `hasInvalidBlock`: the ids of every block
291
+ * that fails required-field validation, one drawer row each (SD-147430).
292
+ */
293
+ invalidBlockIds(): string[];
289
294
  fetchRecommendationProducts(): Promise<void>;
290
295
  _doFetchProducts(): Promise<void>;
291
296
  }>;
@@ -2106,6 +2106,11 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2106
2106
  };
2107
2107
  } | null;
2108
2108
  } & import("pinia").PiniaCustomStateProperties<ConfigStoreState>) => number;
2109
+ /**
2110
+ * Transactional campaigns keep the hard save block. Reactive getter, so
2111
+ * consumers stay safe in setup where a raw config read snapshots null (SD-147945).
2112
+ */
2113
+ isTransactional(): boolean;
2109
2114
  /**
2110
2115
  * Get the username
2111
2116
  */
@@ -0,0 +1,22 @@
1
+ import type { SaveValidationError, SaveValidationIntent } from '@@/Types/save-validation';
2
+ interface SaveValidationState {
3
+ errors: SaveValidationError[];
4
+ isDrawerOpen: boolean;
5
+ intent: SaveValidationIntent | null;
6
+ }
7
+ export declare const useSaveValidationStore: import("pinia").StoreDefinition<"guidoSaveValidation", SaveValidationState, {
8
+ errorCount: (state: {
9
+ errors: {
10
+ rule: import("../enums/save-validation").SaveValidationRule;
11
+ message: string;
12
+ detail?: string | undefined;
13
+ type: import("../enums/toaster").ToasterTypeOptions;
14
+ }[];
15
+ isDrawerOpen: boolean;
16
+ intent: SaveValidationIntent | null;
17
+ } & import("pinia").PiniaCustomStateProperties<SaveValidationState>) => number;
18
+ }, {
19
+ open(errors: SaveValidationError[], intent: SaveValidationIntent): void;
20
+ close(): void;
21
+ }>;
22
+ export {};
@@ -15,6 +15,36 @@ import type { SavedTemplateDetails } from '@@/Types/stripo';
15
15
  * same source), so it is reliably present regardless of how the block was added.
16
16
  */
17
17
  export declare function getHtmlReferencedUnsubscribePages(html: string): number[];
18
+ /**
19
+ * Hydrates the recommendation extension store from the persisted `esd-ext-config`
20
+ * JSON blob on each `.recommendation-block-v2` element in the raw editor HTML.
21
+ *
22
+ * Why this runs before `compileHtml`:
23
+ * The HTML compiler's `prepareRecommendationBlocks` rule asks
24
+ * `useRecommendation.buildCampaignUrl(id)` to assemble each block's API URL
25
+ * from `blockStates[id].recommendationConfigs`. That Pinia entry is normally
26
+ * populated by `RecommendationBlockControl._syncNodeConfigToStore`, which
27
+ * only fires when the user *selects* the block. Saving without ever
28
+ * selecting a migrated block would otherwise build the URL from empty/
29
+ * default state and the campaign URL would not be saved.
30
+ *
31
+ * Why we read raw HTML, not compiled HTML:
32
+ * Stripo's `compileEmail()` strips editor-only attributes (`esd-*`) from the
33
+ * compiled output, so `esd-ext-config` is gone by the time the compiler
34
+ * pipeline sees the HTML. The raw HTML from `getTemplateData()` preserves
35
+ * the attribute as the migrator wrote it.
36
+ *
37
+ * Reference data pre-load:
38
+ * When the template has recommendation blocks, this also pre-loads the
39
+ * shared reference data (languages / currency list) so the save-time
40
+ * required-fields validator can check each block's locale and currency
41
+ * against the partner's real options. Best-effort — on a fetch failure the
42
+ * validator degrades to its empty-list guard (`requiredFields.ts`) instead
43
+ * of blocking the save or stranding the Save button.
44
+ */
45
+ declare function hydrateRecommendationStoreFromRawHtml(rawHtml: string): Promise<void>;
18
46
  export declare const useTemplatePreparation: () => {
19
47
  prepareTemplateDetails: () => Promise<Omit<SavedTemplateDetails, "metadata">>;
48
+ hydrateRecommendationStore: typeof hydrateRecommendationStoreFromRawHtml;
20
49
  };
50
+ export {};