@useinsider/guido 3.18.3-beta.ec88281 → 3.19.0-beta.f02eaa1

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 (63) hide show
  1. package/README.md +41 -3
  2. package/dist/@types/config/schemas.js +59 -51
  3. package/dist/components/Guido.vue.js +4 -4
  4. package/dist/components/Guido.vue2.js +55 -53
  5. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +7 -7
  6. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +53 -49
  7. package/dist/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue.js +18 -0
  8. package/dist/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue2.js +25 -0
  9. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +24 -22
  10. package/dist/components/organisms/onboarding/OnboardingWrapper.vue2.js +28 -24
  11. package/dist/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue.js +21 -0
  12. package/dist/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue2.js +46 -0
  13. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +21 -0
  14. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +46 -0
  15. package/dist/composables/useCustomInterfaceAppearance.js +46 -42
  16. package/dist/composables/useGuidoStateBridge.js +29 -24
  17. package/dist/composables/useLegacyStrategyConversion.js +61 -0
  18. package/dist/composables/useStripo.js +7 -7
  19. package/dist/enums/extensions/recommendationBlock.js +40 -31
  20. package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +4 -4
  21. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +77 -76
  22. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +72 -28
  23. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +45 -44
  24. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +6 -3
  25. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +110 -46
  26. package/dist/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.js +42 -0
  27. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +7 -7
  28. package/dist/guido.css +1 -1
  29. package/dist/library.js +15 -13
  30. package/dist/package.json.js +1 -1
  31. package/dist/public/editorState.js +4 -0
  32. package/dist/services/recommendationApi.js +46 -37
  33. package/dist/src/@types/config/schemas.d.ts +16 -0
  34. package/dist/src/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue.d.ts +2 -0
  35. package/dist/src/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue.d.ts +2 -0
  36. package/dist/src/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.d.ts +2 -0
  37. package/dist/src/composables/useConfig.d.ts +4 -0
  38. package/dist/src/composables/useGuidoStateBridge.d.ts +11 -7
  39. package/dist/src/composables/useLegacyStrategyConversion.d.ts +3 -0
  40. package/dist/src/enums/extensions/recommendationBlock.d.ts +5 -1
  41. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +2 -0
  42. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +4 -0
  43. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +70 -1
  44. package/dist/src/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.d.ts +32 -0
  45. package/dist/src/library.d.ts +2 -0
  46. package/dist/src/public/editorState.d.ts +14 -0
  47. package/dist/src/public/trackingIds.d.ts +6 -2
  48. package/dist/src/services/recommendationApi.d.ts +2 -1
  49. package/dist/src/stores/config.d.ts +322 -0
  50. package/dist/src/stores/editor.d.ts +25 -0
  51. package/dist/src/stores/guido-email-editor.d.ts +27 -27
  52. package/dist/src/stores/onboarding.d.ts +808 -10
  53. package/dist/src/utils/liquidDynamicContent.d.ts +20 -0
  54. package/dist/static/assets/icons/filled-arrow-rising.svg.js +5 -0
  55. package/dist/static/assets/icons/line-info.svg.js +5 -0
  56. package/dist/static/styles/components/recommendation-strategy.css.js +70 -5
  57. package/dist/stores/config.js +74 -60
  58. package/dist/stores/editor.js +4 -0
  59. package/dist/stores/guido-email-editor.js +0 -4
  60. package/dist/stores/onboarding.js +46 -36
  61. package/dist/utils/genericUtil.js +64 -61
  62. package/dist/utils/liquidDynamicContent.js +15 -0
  63. package/package.json +10 -6
@@ -94,9 +94,9 @@ function j(e, t = {}) {
94
94
  const u = s ?? P(n), l = y.getConfig(n);
95
95
  if (u === "list" || !l.mobileLayoutEnabled)
96
96
  return te({ currentNode: n, documentModifier: o });
97
- const c = A(), a = $(r, c.recommendationProducts), f = i ?? U(n, c.filterList), p = `<td><table class="ins-recommendation-product-container ins-recommendation-mobile-container" width="100%" cellpadding="0" cellspacing="0" border="0">${k(a, u, {
97
+ const c = A(), f = $(r, c.recommendationProducts), a = i ?? U(n, c.filterList), p = `<td><table class="ins-recommendation-product-container ins-recommendation-mobile-container" width="100%" cellpadding="0" cellspacing="0" border="0">${k(f, u, {
98
98
  productsPerRow: l.mobileCardsInRow,
99
- composition: f,
99
+ composition: a,
100
100
  filterList: c.filterList,
101
101
  priceInline: !l.priceMovedToNextLine,
102
102
  // Match the initial render's gutter so a regeneration never changes block height (SD-143023, #386).
@@ -122,9 +122,9 @@ function ie(e, t = {}) {
122
122
  const u = n.querySelector(L) ?? n.querySelector(W);
123
123
  if (!u)
124
124
  return !1;
125
- const l = A(), c = $(r, l.recommendationProducts), { cardsInRow: a } = y.getConfig(n), f = i ?? U(n, l.filterList), d = s ?? P(n), p = y.getConfig(n), E = k(c, d, {
126
- productsPerRow: a,
127
- composition: f,
125
+ const l = A(), c = $(r, l.recommendationProducts), { cardsInRow: f } = y.getConfig(n), a = i ?? U(n, l.filterList), d = s ?? P(n), p = y.getConfig(n), E = k(c, d, {
126
+ productsPerRow: f,
127
+ composition: a,
128
128
  filterList: l.filterList,
129
129
  priceInline: !p.priceMovedToNextLine,
130
130
  // Match the initial render's gutter so a regeneration never changes block height (SD-143023, #386).
@@ -153,10 +153,10 @@ function ce(e, t, n) {
153
153
  return !1;
154
154
  const s = A(), i = r.split(",").filter(Boolean), u = D(i, o, s.filterList), l = [], c = u.join(",") !== i.join(",");
155
155
  c && l.push([R, u.join(",")]);
156
- const a = le(
156
+ const f = le(
157
157
  o.getAttribute(q)
158
- ), f = a && z(a, o, s.filterList);
159
- if (a && f && f.join(",") !== a.join(",") && l.push([q, JSON.stringify(f)]), l.length === 0)
158
+ ), a = f && z(f, o, s.filterList);
159
+ if (f && a && a.join(",") !== f.join(",") && l.push([q, JSON.stringify(a)]), l.length === 0)
160
160
  return !1;
161
161
  if (l.forEach(([d, p]) => {
162
162
  t.modifyHtml(o).setAttribute(d, p);
@@ -177,19 +177,20 @@ function Te(e) {
177
177
  products: r,
178
178
  layout: s,
179
179
  composition: i,
180
- skipStylePreservation: u
180
+ skipStylePreservation: u,
181
+ forceApply: l = !1
181
182
  } = e;
182
183
  if (!t)
183
184
  return;
184
- const l = u ? {} : x(t), c = {
185
+ const c = u ? {} : x(t), f = {
185
186
  currentNode: t,
186
187
  documentModifier: n,
187
188
  products: r,
188
189
  layout: s,
189
190
  composition: i
190
191
  };
191
- let a = ie(c, l);
192
- a = j(c, l) || a, a && (ce(t, n, i), n.apply(new S("Regenerate products with styles"))), o == null || o();
192
+ let a = ie(f, c);
193
+ a = j(f, c) || a, a && ce(t, n, i), (a || l) && n.apply(new S("Regenerate products with styles")), o == null || o();
193
194
  }
194
195
  function Re(e, t) {
195
196
  return e.length === t ? e : e.length > t ? e.slice(0, t) : [...e, ...H(t - e.length)];
@@ -250,16 +251,16 @@ function se(e) {
250
251
  omnibusDiscountEl: l,
251
252
  buttonEl: c
252
253
  } = e;
253
- let a = !1;
254
+ let f = !1;
254
255
  if (o && "querySelector" in o) {
255
- const f = o.querySelector("img");
256
- f && (t.modifyHtml(f).setAttribute("src", X(n.image_url)).setAttribute("alt", n.name), a = !0);
256
+ const a = o.querySelector("img");
257
+ a && (t.modifyHtml(a).setAttribute("src", X(n.image_url)).setAttribute("alt", n.name), f = !0);
257
258
  const d = o.querySelector("a");
258
- d && (t.modifyHtml(d).setAttribute("href", n.url), a = !0);
259
+ d && (t.modifyHtml(d).setAttribute("href", n.url), f = !0);
259
260
  }
260
261
  if (r && "querySelector" in r) {
261
- const f = r.querySelector("strong") ?? r.querySelector("p");
262
- b(t, f ?? null, n.name) && (a = !0);
262
+ const a = r.querySelector("strong") ?? r.querySelector("p");
263
+ b(t, a ?? null, n.name) && (f = !0);
263
264
  }
264
265
  if (K({
265
266
  documentModifier: t,
@@ -268,11 +269,11 @@ function se(e) {
268
269
  oldPriceEl: i,
269
270
  omnibusPriceEl: u,
270
271
  omnibusDiscountEl: l
271
- }) && (a = !0), c && "querySelector" in c) {
272
- const f = c.querySelector("a.es-button") || c.querySelector("a");
273
- f && (t.modifyHtml(f).setAttribute("href", n.url), a = !0);
272
+ }) && (f = !0), c && "querySelector" in c) {
273
+ const a = c.querySelector("a.es-button") || c.querySelector("a");
274
+ a && (t.modifyHtml(a).setAttribute("href", n.url), f = !0);
274
275
  }
275
- return a;
276
+ return f;
276
277
  }
277
278
  function V(e) {
278
279
  return "querySelector" in e ? e.querySelector(L) ?? e : e;
@@ -308,14 +309,14 @@ function fe(e, t, n) {
308
309
  let s = !1;
309
310
  return r.forEach((i, u) => {
310
311
  const l = Math.min(i.length, n.length);
311
- i.slice(0, l).forEach((c, a) => {
312
+ i.slice(0, l).forEach((c, f) => {
312
313
  if (!("querySelector" in c))
313
314
  return;
314
- const f = ae(u, n[a]);
315
- if (f === null)
315
+ const a = ae(u, n[f]);
316
+ if (a === null)
316
317
  return;
317
318
  const d = c.querySelector("p") ?? null;
318
- b(t, d, f) && (s = !0);
319
+ b(t, d, a) && (s = !0);
319
320
  });
320
321
  }), s;
321
322
  }
@@ -336,9 +337,9 @@ function O(e, t, n) {
336
337
  `[esd-extension-block-id="${m.OMNIBUS_DISCOUNT}"]`
337
338
  ), c = e.querySelectorAll(
338
339
  `[esd-extension-block-id="${m.BUTTON}"]`
339
- ), a = Math.min(o.length, n.length);
340
- let f = !1;
341
- for (let d = 0; d < a; d++) {
340
+ ), f = Math.min(o.length, n.length);
341
+ let a = !1;
342
+ for (let d = 0; d < f; d++) {
342
343
  const p = se({
343
344
  documentModifier: t,
344
345
  product: n[d],
@@ -350,33 +351,33 @@ function O(e, t, n) {
350
351
  omnibusDiscountEl: l[d] ?? null,
351
352
  buttonEl: c[d] ?? null
352
353
  });
353
- f = f || p;
354
+ a = a || p;
354
355
  }
355
- return fe(e, t, n) && (f = !0), f;
356
+ return fe(e, t, n) && (a = !0), a;
356
357
  }
357
358
  function qe(e) {
358
- const { currentNode: t, documentModifier: n, products: o } = e;
359
+ const { currentNode: t, documentModifier: n, products: o, forceApply: r = !1 } = e;
359
360
  if (!t || !("querySelectorAll" in t))
360
361
  return !1;
361
- const r = V(t);
362
- if (!("querySelectorAll" in r) || r.querySelectorAll(
362
+ const s = V(t);
363
+ if (!("querySelectorAll" in s) || s.querySelectorAll(
363
364
  `[esd-extension-block-id="${m.IMAGE}"]`
364
365
  ).length !== o.length)
365
366
  return !1;
366
- let i = O(r, n, o);
367
- const u = P(t), l = y.getConfig(t);
368
- if (u !== "list" && l.mobileLayoutEnabled && "querySelector" in t) {
369
- const c = t.querySelector(N);
370
- if (c) {
367
+ let u = O(s, n, o);
368
+ const l = P(t), c = y.getConfig(t);
369
+ if (l !== "list" && c.mobileLayoutEnabled && "querySelector" in t) {
370
+ const f = t.querySelector(N);
371
+ if (f) {
371
372
  const a = O(
372
- c,
373
+ f,
373
374
  n,
374
375
  o
375
376
  );
376
- i = i || a;
377
+ u = u || a;
377
378
  }
378
379
  }
379
- return i && n.apply(new S("Updated product content in-place")), !0;
380
+ return (u || r) && n.apply(new S("Updated product content in-place")), !0;
380
381
  }
381
382
  function _(e, t, n) {
382
383
  if (!("querySelectorAll" in e))
@@ -417,8 +418,8 @@ function Me(e) {
417
418
  if (u !== "list" && l.mobileLayoutEnabled && "querySelector" in t) {
418
419
  const c = t.querySelector(N);
419
420
  if (c) {
420
- const a = _(c, n, r);
421
- i = i || a;
421
+ const f = _(c, n, r);
422
+ i = i || f;
422
423
  }
423
424
  }
424
425
  return i && n.apply(new S("Updated price formatting in-place")), i;
@@ -134,7 +134,10 @@ ue-orderable.orderable-disabled .droppable-icon {
134
134
  border-top: 2px solid var(--guido-color-primary-500, #0A2ECC);
135
135
  }
136
136
 
137
- .drag-handle {
137
+ /* Namespaced: Stripo ships a global \`.drag-handle { position: absolute }\` for its
138
+ floating popups, which knocked this handle out of flow (SD-151734). */
139
+ .guido-drag-handle {
140
+ cursor: move;
138
141
  user-select: none;
139
142
  display: flex;
140
143
  align-items: center;
@@ -144,13 +147,13 @@ ue-orderable.orderable-disabled .droppable-icon {
144
147
  flex-shrink: 0;
145
148
  }
146
149
 
147
- .drag-handle .drag-handle-btn .button {
150
+ .guido-drag-handle .guido-drag-handle-btn .button {
148
151
  padding: 0;
149
152
  min-width: auto;
150
153
  cursor: move;
151
154
  }
152
155
 
153
- .drag-handle .drag-handle-btn .button .icon-button {
156
+ .guido-drag-handle .guido-drag-handle-btn .button .icon-button {
154
157
  color: var(--guido-color-gray-600);
155
158
  }
156
159
 
@@ -1,22 +1,23 @@
1
- import { getRecommendationFeedSourceMaps as b, getOperatorOptions as G, PriceAttributes as v } from "../../../../enums/extensions/recommendationBlock.js";
1
+ import { getRecommendationFeedSourceMaps as R, getOperatorOptions as F, PriceAttributes as P } from "../../../../enums/extensions/recommendationBlock.js";
2
2
  import { useRecommendationApi as D } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as F } from "../../../../stores/config.js";
4
- import { defineStore as P } from "pinia";
3
+ import { useConfigStore as k } from "../../../../stores/config.js";
4
+ import { defineStore as L } from "pinia";
5
5
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as _, DEFAULT_CARDS_IN_ROW as U } from "../constants/layout.js";
7
- import { DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as L } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as R } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as k } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as x } from "../utils/partnerCustomizations.js";
11
- import { sanitizeProductIds as A } from "../utils/productIds.js";
12
- import { isFilterValid as B } from "../validation/filterSchema.js";
13
- import { isConfigValid as M } from "../validation/requiredFields.js";
14
- const u = D();
6
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as U, DEFAULT_CARDS_IN_ROW as _ } from "../constants/layout.js";
7
+ import { CURRENT_CONFIG_VERSION as y, DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as M } from "../constants/defaultConfig.js";
8
+ import { getDefaultProducts as C } from "../templates/utils.js";
9
+ import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
10
+ import { isLegacyStrategyConfig as x } from "../utils/legacyStrategyMigration.js";
11
+ import { getPartnerRecommendationParams as B } from "../utils/partnerCustomizations.js";
12
+ import { sanitizeProductIds as z } from "../utils/productIds.js";
13
+ import { isFilterValid as A } from "../validation/filterSchema.js";
14
+ import { isConfigValid as O } from "../validation/requiredFields.js";
15
+ const m = D();
15
16
  let f = null, g = null, h = null, S = null;
16
17
  const p = /* @__PURE__ */ new Map();
17
- function w() {
18
+ function G() {
18
19
  return {
19
- cardsInRow: U,
20
+ cardsInRow: _,
20
21
  currencySettings: {
21
22
  name: "USD",
22
23
  value: "USD",
@@ -30,7 +31,7 @@ function w() {
30
31
  productIds: [],
31
32
  id: 1,
32
33
  language: "en_US",
33
- mobileCardsInRow: _,
34
+ mobileCardsInRow: U,
34
35
  mobileLayoutEnabled: !1,
35
36
  orientation: "grid",
36
37
  recommendedProducts: [],
@@ -38,6 +39,7 @@ function w() {
38
39
  shuffleProducts: !1,
39
40
  strategy: "mostPopular",
40
41
  strategyId: "",
42
+ nodeConfigVersion: y,
41
43
  textTrimming: !0,
42
44
  unresponsive: !1,
43
45
  size: "6",
@@ -46,21 +48,23 @@ function w() {
46
48
  priceHideIfSameAsDiscounted: !1
47
49
  };
48
50
  }
49
- function C() {
51
+ function w() {
50
52
  return {
51
- recommendationConfigs: w(),
53
+ recommendationConfigs: G(),
52
54
  recommendationProducts: [],
53
55
  filterStatus: !1,
54
56
  filterSelectionDrawerStatus: !1,
55
57
  assignStrategyDrawerStatus: !1,
56
58
  removeStrategyModalStatus: !1,
59
+ legacyStrategyUpdatedModalStatus: !1,
57
60
  filterGroup: 1,
58
61
  isInitialized: !1,
59
62
  filterSnapshot: null,
60
- syncedConfigFingerprint: null
63
+ syncedConfigFingerprint: null,
64
+ legacyConversionLoading: !1
61
65
  };
62
66
  }
63
- const z = () => ({
67
+ const E = () => ({
64
68
  recommendationCampaignUrls: {},
65
69
  activePredictiveAlgorithms: [],
66
70
  languages: {},
@@ -72,8 +76,8 @@ const z = () => ({
72
76
  blockStates: {},
73
77
  currentRecommendationId: null,
74
78
  configVersion: 0
75
- }), Y = P("guidoRecommendationExtension", {
76
- state: () => z(),
79
+ }), et = L("guidoRecommendationExtension", {
80
+ state: () => E(),
77
81
  getters: {
78
82
  // ====================================================================
79
83
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -83,7 +87,7 @@ const z = () => ({
83
87
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
84
88
  */
85
89
  recommendationConfigs(t) {
86
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : w();
90
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : G();
87
91
  },
88
92
  /**
89
93
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -103,6 +107,15 @@ const z = () => ({
103
107
  removeStrategyModalStatus(t) {
104
108
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].removeStrategyModalStatus : !1;
105
109
  },
110
+ isLegacyStrategyBlock() {
111
+ return k().isFeatureEnabled("reusableRecommendationStrategy") && x(this.recommendationConfigs);
112
+ },
113
+ legacyConversionLoading(t) {
114
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyConversionLoading : !1;
115
+ },
116
+ legacyStrategyUpdatedModalStatus(t) {
117
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyStrategyUpdatedModalStatus : !1;
118
+ },
106
119
  assignedStrategy(t) {
107
120
  const { strategyId: e } = this.recommendationConfigs;
108
121
  return e ? t.strategies.find((r) => String(r.strategy_id) === e) ?? null : null;
@@ -138,8 +151,8 @@ const z = () => ({
138
151
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
139
152
  },
140
153
  getActivePredictiveAlgorithms: (t) => {
141
- const e = b(), r = [];
142
- return t.activePredictiveAlgorithms.filter((n) => !L.includes(n)).forEach((n) => {
154
+ const e = R(), r = [];
155
+ return t.activePredictiveAlgorithms.filter((n) => !M.includes(n)).forEach((n) => {
143
156
  r.push(...e.filter((s) => s.id === n));
144
157
  }), r.map((n) => ({
145
158
  text: n.name,
@@ -157,7 +170,7 @@ const z = () => ({
157
170
  getFilterList() {
158
171
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
159
172
  let e;
160
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : v.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
173
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : P.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
161
174
  text: t.displayName,
162
175
  value: e,
163
176
  type: t.attributeType
@@ -179,7 +192,7 @@ const z = () => ({
179
192
  setCurrentBlock(t) {
180
193
  this.blockStates[t] || (this.blockStates = {
181
194
  ...this.blockStates,
182
- [t]: C()
195
+ [t]: w()
183
196
  }), this.currentRecommendationId = t;
184
197
  },
185
198
  /**
@@ -247,7 +260,7 @@ const z = () => ({
247
260
  decimalCount: String(e.currencyDecimalCount),
248
261
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
249
262
  thousandSeparator: r(e.currencyThousandSeparator, ".")
250
- }, s = !this.blockStates[t], i = s ? C() : this.blockStates[t];
263
+ }, s = !this.blockStates[t], i = s ? w() : this.blockStates[t];
251
264
  i.recommendationConfigs = {
252
265
  ...i.recommendationConfigs,
253
266
  strategy: e.strategy,
@@ -286,7 +299,7 @@ const z = () => ({
286
299
  * Creates a filter with the first available attribute and operator pre-selected.
287
300
  */
288
301
  createDefaultFilter(t, e) {
289
- const [r] = this.getFilterList, [n] = G(r == null ? void 0 : r.type);
302
+ const [r] = this.getFilterList, [n] = F(r == null ? void 0 : r.type);
290
303
  return {
291
304
  type: "standardFilter",
292
305
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -339,6 +352,12 @@ const z = () => ({
339
352
  closeAssignStrategyDrawer() {
340
353
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].assignStrategyDrawerStatus = !1);
341
354
  },
355
+ openLegacyStrategyUpdatedModal() {
356
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].legacyStrategyUpdatedModalStatus = !0);
357
+ },
358
+ closeLegacyStrategyUpdatedModal() {
359
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].legacyStrategyUpdatedModalStatus = !1);
360
+ },
342
361
  openRemoveStrategyModal() {
343
362
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !0);
344
363
  },
@@ -353,7 +372,7 @@ const z = () => ({
353
372
  S = (async () => {
354
373
  this.strategiesLoading = !0;
355
374
  try {
356
- this.strategies = await u.fetchRecommendationStrategies();
375
+ this.strategies = await m.fetchRecommendationStrategies();
357
376
  } finally {
358
377
  this.strategiesLoading = !1;
359
378
  }
@@ -368,7 +387,7 @@ const z = () => ({
368
387
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
369
388
  if (s)
370
389
  return s;
371
- const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
390
+ const { full_path: i = "" } = await m.generateStrategyEndpoint(t, e) ?? {};
372
391
  return n && i && (this.strategyUrlTemplates = {
373
392
  ...this.strategyUrlTemplates,
374
393
  [n]: i
@@ -380,8 +399,44 @@ const z = () => ({
380
399
  ...t.size !== void 0 ? { size: String(t.size) } : {}
381
400
  });
382
401
  },
402
+ async convertLegacyStrategy(t, e = { scope: "campaign" }) {
403
+ const r = this.currentRecommendationId;
404
+ if (r === null || !this.blockStates[r])
405
+ throw new Error("[Guido] convertLegacyStrategy called without an active block");
406
+ this.blockStates[r].legacyConversionLoading = !0;
407
+ try {
408
+ const n = await m.migrateLegacyStrategy(t, e);
409
+ try {
410
+ await this.fetchStrategies();
411
+ } catch (o) {
412
+ console.error("[Guido] strategy listing refresh after conversion failed", o);
413
+ }
414
+ const s = this.strategies.find((o) => String(o.strategy_id) === n), i = this.blockStates[r];
415
+ return i && (i.recommendationConfigs = {
416
+ ...i.recommendationConfigs,
417
+ strategyId: n,
418
+ nodeConfigVersion: y,
419
+ ...(s == null ? void 0 : s.size) !== void 0 ? { size: String(s.size) } : {}
420
+ }), this.currentRecommendationId === r && this.configVersion++, n;
421
+ } finally {
422
+ this.blockStates[r] && (this.blockStates[r].legacyConversionLoading = !1);
423
+ }
424
+ },
425
+ /**
426
+ * Undoes the config half of a conversion whose document write never landed, so the
427
+ * panel and the drawer fall back to "Legacy Strategy" instead of showing an assignment
428
+ * the document never received.
429
+ */
430
+ revertLegacyConversion(t, e) {
431
+ const r = this.blockStates[t];
432
+ r && (r.recommendationConfigs = { ...r.recommendationConfigs, ...e }, this.currentRecommendationId === t && this.configVersion++);
433
+ },
383
434
  removeStrategy() {
384
- this.patchCurrentBlockConfig({ strategyId: "", size: N.size });
435
+ this.patchCurrentBlockConfig({
436
+ strategyId: "",
437
+ size: N.size,
438
+ nodeConfigVersion: y
439
+ });
385
440
  },
386
441
  // ====================================================================
387
442
  // Shared Data Fetching (fetched once, used by all blocks)
@@ -397,7 +452,7 @@ const z = () => ({
397
452
  activePredictiveAlgorithms: t,
398
453
  languages: e,
399
454
  currencies: r
400
- } = await u.fetchRecommendationCreateData();
455
+ } = await m.fetchRecommendationCreateData();
401
456
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
402
457
  const n = this.blockStates[this.currentRecommendationId];
403
458
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -418,7 +473,7 @@ const z = () => ({
418
473
  return;
419
474
  }
420
475
  g = (async () => {
421
- const t = await u.fetchRecommendationFilters();
476
+ const t = await m.fetchRecommendationFilters();
422
477
  this.filterList = t;
423
478
  })();
424
479
  try {
@@ -453,7 +508,7 @@ const z = () => ({
453
508
  const n = [...e.recommendationConfigs.filters];
454
509
  n[r] = {
455
510
  ...t,
456
- isValid: B(t)
511
+ isValid: A(t)
457
512
  }, e.recommendationConfigs.filters = n;
458
513
  }
459
514
  },
@@ -491,7 +546,7 @@ const z = () => ({
491
546
  }), e.recommendationConfigs.filters = r;
492
547
  },
493
548
  generateFilterQuery() {
494
- return k(this.recommendationConfigs.filters);
549
+ return v(this.recommendationConfigs.filters);
495
550
  },
496
551
  /**
497
552
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -499,7 +554,7 @@ const z = () => ({
499
554
  * every block's recommendationConfigs across user edits.
500
555
  */
501
556
  hasInvalidBlock() {
502
- return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
557
+ return Object.values(this.blockStates).some((t) => !O(t.recommendationConfigs, this));
503
558
  },
504
559
  // ====================================================================
505
560
  // Per-Block Product Fetching
@@ -520,12 +575,12 @@ const z = () => ({
520
575
  },
521
576
  async _doFetchProducts() {
522
577
  var d;
523
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = F();
578
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = k();
524
579
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
525
580
  await this._doFetchStrategyProducts(t);
526
581
  return;
527
582
  }
528
- const s = r.filters.filter((m) => m.isValid), i = k(s), o = ((d = b().find((m) => m.key === r.strategy)) == null ? void 0 : d.path) || "", c = parseInt(r.size) || 6, a = {
583
+ const s = r.filters.filter((u) => u.isValid), i = v(s), o = ((d = R().find((u) => u.key === r.strategy)) == null ? void 0 : d.path) || "", c = parseInt(r.size) || 6, a = {
529
584
  locale: r.language,
530
585
  currency: r.currencySettings.value,
531
586
  partnerName: n.partnerName,
@@ -533,18 +588,27 @@ const z = () => ({
533
588
  details: !0,
534
589
  campaignId: n.variationId
535
590
  };
536
- r.strategy === "manualMerchandising" ? a.productId = A(r.productIds) : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), i && (a.filter = i), r.shuffleProducts && (a.shuffle = !0), Object.assign(
591
+ r.strategy === "manualMerchandising" ? a.productId = z(r.productIds) : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), i && (a.filter = i), r.shuffleProducts && (a.shuffle = !0), Object.assign(
537
592
  a,
538
- x(n.partnerName, r.strategy)
593
+ B(n.partnerName, r.strategy)
539
594
  );
540
595
  let l;
541
596
  try {
542
- l = await u.fetchRecommendationProducts(o, a);
597
+ l = await m.fetchRecommendationProducts(o, a);
543
598
  } catch {
544
599
  l = [];
545
600
  }
546
601
  this._setBlockProducts(t, l, c);
547
602
  },
603
+ /**
604
+ * Block-scoped counterpart of `fetchRecommendationProducts`, which only ever serves the
605
+ * selected block. The legacy conversion must refresh the products of the block that
606
+ * started it even after the user selected another one.
607
+ */
608
+ async fetchStrategyProductsForBlock(t) {
609
+ var e;
610
+ (e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId && await this._doFetchStrategyProducts(t);
611
+ },
548
612
  async _doFetchStrategyProducts(t) {
549
613
  const e = (p.get(t) ?? 0) + 1;
550
614
  p.set(t, e);
@@ -557,8 +621,8 @@ const z = () => ({
557
621
  );
558
622
  if (i) {
559
623
  s = parseInt(String(i.size ?? "")) || s;
560
- const o = r.currencySettings.value, a = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), l = a.indexOf("?"), d = l === -1 ? a : a.slice(0, l), m = (l === -1 ? "" : a.slice(l + 1)).split("&").filter((I) => I && !I.startsWith("user_id=")), y = m.length ? `${d}?${m.join("&")}` : d;
561
- y && (n = await u.fetchRecommendationProductsByUrl(y));
624
+ const o = r.currencySettings.value, a = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), l = a.indexOf("?"), d = l === -1 ? a : a.slice(0, l), u = (l === -1 ? "" : a.slice(l + 1)).split("&").filter((I) => I && !I.startsWith("user_id=")), b = u.length ? `${d}?${u.join("&")}` : d;
625
+ b && (n = await m.fetchRecommendationProductsByUrl(b));
562
626
  }
563
627
  } catch {
564
628
  n = [];
@@ -568,14 +632,14 @@ const z = () => ({
568
632
  _setBlockProducts(t, e, r) {
569
633
  if (!this.blockStates[t])
570
634
  return;
571
- const n = e.length > 0 ? e : R(r);
635
+ const n = e.length > 0 ? e : C(r);
572
636
  this.blockStates[t].recommendationProducts = [
573
637
  ...n.slice(0, r),
574
- ...R(Math.max(0, r - n.length))
638
+ ...C(Math.max(0, r - n.length))
575
639
  ];
576
640
  }
577
641
  }
578
642
  });
579
643
  export {
580
- Y as useRecommendationExtensionStore
644
+ et as useRecommendationExtensionStore
581
645
  };
@@ -0,0 +1,42 @@
1
+ import "../constants/selectors.js";
2
+ import { CURRENT_CONFIG_VERSION as m } from "../constants/defaultConfig.js";
3
+ import { sanitizeProductIds as p } from "./productIds.js";
4
+ const d = "manualMerchandising";
5
+ function N({ strategyId: e, nodeConfigVersion: t }) {
6
+ return !e && t < m;
7
+ }
8
+ const o = (e) => {
9
+ const t = String(e ?? "").trim();
10
+ return /^[1-9]\d*$/.test(t) ? t : null;
11
+ };
12
+ function f({ campaignId: e, templateLibraryId: t }) {
13
+ if (o(e))
14
+ return { scope: "campaign" };
15
+ const n = o(t);
16
+ return n ? { scope: "template", templateId: n } : { scope: "campaign" };
17
+ }
18
+ function y({
19
+ blockId: e,
20
+ url: t,
21
+ campaignId: n,
22
+ variationId: a,
23
+ strategy: c,
24
+ productIds: s,
25
+ target: r
26
+ }) {
27
+ const i = c === d ? p(s).split(",").filter(Boolean) : [];
28
+ return {
29
+ ...r.scope === "template" ? { templateId: Number.parseInt(r.templateId) } : {
30
+ campaignId: Number.parseInt(n) || 0,
31
+ variationId: Number.parseInt(a) || 0
32
+ },
33
+ blockId: String(e),
34
+ url: t,
35
+ ...i.length ? { productIds: i } : {}
36
+ };
37
+ }
38
+ export {
39
+ y as buildLegacyMigrationPayload,
40
+ N as isLegacyStrategyConfig,
41
+ f as resolveMigrationTarget
42
+ };
@@ -1,12 +1,12 @@
1
1
  import { ModificationDescription as f } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
2
  import { RECOMMENDATION_ID_ATTR as m } from "../constants/selectors.js";
3
- import { isPartnerManagedBlock as a, updateProductContentInPlace as d, regenerateProductRowsWithStyles as l } from "../controls/main/utils.js";
4
- import { RecommendationConfigService as s } from "../services/configService.js";
3
+ import { isPartnerManagedBlock as l, updateProductContentInPlace as a, regenerateProductRowsWithStyles as d } from "../controls/main/utils.js";
4
+ import { RecommendationConfigService as p } from "../services/configService.js";
5
5
  let e = null;
6
- function N(r) {
6
+ function M(r) {
7
7
  e = r;
8
8
  }
9
- function D(r, c, i, u) {
9
+ function N(r, c, i, u) {
10
10
  const n = e == null ? void 0 : e.getDocumentRootHtmlNode();
11
11
  if (!e || !n || !("querySelector" in n))
12
12
  return !1;
@@ -14,9 +14,9 @@ function D(r, c, i, u) {
14
14
  if (!t)
15
15
  return !1;
16
16
  const o = e.getDocumentModifier();
17
- return s.stageConfig(o, t, c), a(t) ? (o.apply(new f(u)), !0) : (d({ currentNode: t, documentModifier: o, products: i }) || l({ currentNode: t, documentModifier: o, products: i }), !0);
17
+ return p.stageConfig(o, t, c), l(t) ? (o.apply(new f(u)), !0) : (a({ currentNode: t, documentModifier: o, products: i, forceApply: !0 }) || d({ currentNode: t, documentModifier: o, products: i, forceApply: !0 }), !0);
18
18
  }
19
19
  export {
20
- D as commitBlockChangeOutsidePanel,
21
- N as registerNodeConfigWriter
20
+ N as commitBlockChangeOutsidePanel,
21
+ M as registerNodeConfigWriter
22
22
  };