@useinsider/guido 3.18.2 → 3.19.0-beta.5355c3f

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 (33) hide show
  1. package/README.md +20 -0
  2. package/dist/components/Guido.vue.js +3 -3
  3. package/dist/components/Guido.vue2.js +67 -61
  4. package/dist/components/organisms/save-as-template/SaveAsTemplateDrawer.vue2.js +34 -34
  5. package/dist/composables/useActionsApi.js +24 -20
  6. package/dist/composables/useCortexBlueprintBridge.js +115 -54
  7. package/dist/composables/useEmailTemplateApplier.js +93 -50
  8. package/dist/composables/useGuidoStateBridge.js +29 -24
  9. package/dist/composables/useHttp.js +24 -22
  10. package/dist/composables/useStripo.js +7 -7
  11. package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +4 -4
  12. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +6 -3
  13. package/dist/guido.css +1 -1
  14. package/dist/library.js +15 -13
  15. package/dist/package.json.js +1 -1
  16. package/dist/public/editorState.js +4 -0
  17. package/dist/services/templateLibraryApi.js +17 -14
  18. package/dist/src/composables/useCortexBlueprintBridge.d.ts +4 -3
  19. package/dist/src/composables/useEmailTemplateApplier.d.ts +22 -2
  20. package/dist/src/composables/useGuidoStateBridge.d.ts +11 -7
  21. package/dist/src/library.d.ts +2 -0
  22. package/dist/src/public/editorState.d.ts +14 -0
  23. package/dist/src/public/trackingIds.d.ts +6 -2
  24. package/dist/src/stores/editor.d.ts +25 -0
  25. package/dist/src/stores/guido-email-editor.d.ts +49 -27
  26. package/dist/src/utils/genericUtil.d.ts +6 -0
  27. package/dist/src/utils/liquidDynamicContent.d.ts +20 -0
  28. package/dist/stores/editor.js +4 -0
  29. package/dist/stores/guido-email-editor.js +3 -5
  30. package/dist/utils/genericUtil.js +63 -54
  31. package/dist/utils/liquidDynamicContent.js +15 -0
  32. package/dist/utils/templatePreparation.js +37 -36
  33. package/package.json +10 -6
package/dist/guido.css CHANGED
@@ -1 +1 @@
1
- @charset "UTF-8";.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-1ca6baf0]{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-050dfa52]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-050dfa52]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-050dfa52]{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}.assign-strategy-drawer-content .assign-strategy-select[data-v-2580a7d9]{width:100%;max-width:none}.assign-strategy-drawer-content .assign-strategy-select[data-v-2580a7d9] .in-dropdown-box__container{width:413px}.assign-strategy-drawer .spaced-dot-sep{background-image:linear-gradient(90deg,#91949f 20%,#fff0 0);background-position:center bottom;background-repeat:repeat-x;background-size:5px 1px}.assign-strategy-drawer .spaced-dot-sep--top{background-position:center top}.assign-strategy-drawer .spaced-dot-sep--fc-n:first-child{background:none}
1
+ @charset "UTF-8";.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-1ca6baf0]{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-7674b645]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-7674b645]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-7674b645]{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}.assign-strategy-drawer-content .assign-strategy-select[data-v-2580a7d9]{width:100%;max-width:none}.assign-strategy-drawer-content .assign-strategy-select[data-v-2580a7d9] .in-dropdown-box__container{width:413px}.assign-strategy-drawer .spaced-dot-sep{background-image:linear-gradient(90deg,#91949f 20%,#fff0 0);background-position:center bottom;background-repeat:repeat-x;background-size:5px 1px}.assign-strategy-drawer .spaced-dot-sep--top{background-position:center top}.assign-strategy-drawer .spaced-dot-sep--fc-n:first-child{background:none}
package/dist/library.js CHANGED
@@ -1,18 +1,20 @@
1
- import { default as e } from "./components/Guido.vue.js";
2
- import { MessageType as f, ProductType as s } from "./@types/config/schemas.js";
1
+ import { default as a } from "./components/Guido.vue.js";
2
+ import { MessageType as t, ProductType as d } from "./@types/config/schemas.js";
3
3
  import "./@types/config/defaults.js";
4
- import { getValidationErrors as d, isValidConfig as p, parseConfig as g, parseConfigSafe as n, validateConfig as l } from "./@types/config/validator.js";
5
- import { assignTrackingIds as T, readTrackFloor as c, readTrackSeq as x } from "./utils/linkTrackingIds.js";
4
+ import { getValidationErrors as s, isValidConfig as p, parseConfig as g, parseConfigSafe as m, validateConfig as n } from "./@types/config/validator.js";
5
+ import { assignTrackingIds as u, readTrackFloor as x, readTrackSeq as T } from "./utils/linkTrackingIds.js";
6
+ import { useGuidoEmailEditorStore as C } from "./stores/guido-email-editor.js";
6
7
  export {
7
- e as Guido,
8
- f as MessageType,
9
- s as ProductType,
10
- T as assignTrackingIds,
11
- d as getValidationErrors,
8
+ a as Guido,
9
+ t as MessageType,
10
+ d as ProductType,
11
+ u as assignTrackingIds,
12
+ s as getValidationErrors,
12
13
  p as isValidConfig,
13
14
  g as parseConfig,
14
- n as parseConfigSafe,
15
- c as readTrackFloor,
16
- x as readTrackSeq,
17
- l as validateConfig
15
+ m as parseConfigSafe,
16
+ x as readTrackFloor,
17
+ T as readTrackSeq,
18
+ C as useGuidoEmailEditorStore,
19
+ n as validateConfig
18
20
  };
@@ -1,4 +1,4 @@
1
- const o = { stripo: { version: "2.76.0" } }, s = {
1
+ const o = { stripo: { version: "2.77.0" } }, s = {
2
2
  guido: o
3
3
  };
4
4
  export {
@@ -0,0 +1,4 @@
1
+ import { useGuidoEmailEditorStore as e } from "../stores/guido-email-editor.js";
2
+ export {
3
+ e as useGuidoEmailEditorStore
4
+ };
@@ -4,13 +4,14 @@ import { useToaster as C } from "../composables/useToaster.js";
4
4
  import { useTranslations as h } from "../composables/useTranslations.js";
5
5
  import { useLiquidValidator as S } from "../composables/validators/useLiquidValidator.js";
6
6
  import { EditorType as v } from "../enums/defaults.js";
7
- import { ToasterTypeOptions as N } from "../enums/toaster.js";
8
- import { useRecommendationExtensionStore as x } from "../extensions/Blocks/Recommendation/store/recommendation.js";
9
- import { useSaveAsTemplateStore as E } from "../stores/save-as-template.js";
7
+ import { ToasterTypeOptions as x } from "../enums/toaster.js";
8
+ import { useRecommendationExtensionStore as E } from "../extensions/Blocks/Recommendation/store/recommendation.js";
9
+ import { useSaveAsTemplateStore as N } from "../stores/save-as-template.js";
10
10
  import { base64EncodeWithSpecialChars as r } from "../utils/base64.js";
11
+ import { isPlainObject as P } from "../utils/genericUtil.js";
11
12
  import { useTemplatePreparation as A } from "../utils/templatePreparation.js";
12
- const j = () => {
13
- const { get: n, post: m } = b(), { handleError: o, showToaster: l } = C(), { config: c, isFeatureEnabled: p } = w(), { validateLiquidSyntax: g } = S(), u = h();
13
+ const B = () => {
14
+ const { get: n, post: m } = b(), { handleError: o, showToaster: l } = C(), { config: c, isFeatureEnabled: p } = w(), { validateLiquidSyntax: f } = S(), u = h();
14
15
  return {
15
16
  getCategories: async () => {
16
17
  try {
@@ -40,10 +41,10 @@ const j = () => {
40
41
  createTemplate: async () => {
41
42
  var e, t, d, y;
42
43
  try {
43
- const { prepareTemplateDetails: i } = A(), s = E(), a = await i();
44
- if (p("liquidSyntax") && !await g(a.compiledHtml))
44
+ const { prepareTemplateDetails: i } = A(), s = N(), a = await i();
45
+ if (p("liquidSyntax") && !await f(a.compiledHtml))
45
46
  return !1;
46
- const f = {
47
+ const T = {
47
48
  name: s.getTemplateName,
48
49
  categories: s.getSelectedCategoryIds,
49
50
  productId: ((t = (e = c.value) == null ? void 0 : e.partner) == null ? void 0 : t.productType) || 0,
@@ -54,7 +55,7 @@ const j = () => {
54
55
  unsubscriptionPreferencePageStatus: !1,
55
56
  unsubscriptionPreferencePages: [],
56
57
  recommendationCampaignUrls: r(
57
- x().recommendationCampaignUrls
58
+ E().recommendationCampaignUrls
58
59
  ),
59
60
  recommendationConfigs: r({}),
60
61
  isGuido: !0,
@@ -66,16 +67,18 @@ const j = () => {
66
67
  css: a.css,
67
68
  dynamicContentList: a.dynamicContentList
68
69
  })
69
- }, { data: { type: T } } = await m(
70
+ }, { data: g } = await m(
70
71
  "/newsletter/template-library/create-template",
71
- f
72
+ T
72
73
  );
73
- return T === "alert" ? (l({
74
+ if (!P(g))
75
+ throw new Error("Unexpected create-template response");
76
+ return g.type === "alert" ? (l({
74
77
  message: u(
75
78
  "newsletter.template-library-save-alert",
76
79
  { templateName: s.getTemplateName }
77
80
  ),
78
- type: N.Error
81
+ type: x.Error
79
82
  }), !1) : (l({
80
83
  message: u(
81
84
  "newsletter.template-library-save-success",
@@ -90,5 +93,5 @@ const j = () => {
90
93
  };
91
94
  };
92
95
  export {
93
- j as useTemplateLibraryApi
96
+ B as useTemplateLibraryApi
94
97
  };
@@ -17,12 +17,13 @@
17
17
  * - `error` segments (and message-level `isError` / `isCancelled`) trigger
18
18
  * a localized toaster.
19
19
  *
20
+ * Every apply settles into a terminal `applyAcks` entry on the public store, so
21
+ * cortex can move its Apply button on the truth of the editor write rather than
22
+ * on the click.
23
+ *
20
24
  * In dev (`import.meta.env.DEV`) every observed segment + message-level flag
21
25
  * change is also `console.debug`-logged with the prefix `[guido:cortex]` so a
22
26
  * developer can watch the agent stream in real time. In production this is
23
27
  * gated behind `localStorage.guido:debug:ai === '1'`.
24
- *
25
- * If cortex-fe isn't loaded the chat store is undefined and the bridge is a
26
- * no-op (intentional — Guido must still work standalone).
27
28
  */
28
29
  export declare const useCortexBlueprintBridge: () => void;
@@ -1,4 +1,20 @@
1
1
  type ApplyStatus = 'pending' | 'applying' | 'applied' | 'failed';
2
+ /**
3
+ * Terminal outcome of an apply. Every promise this composable hands out settles
4
+ * with exactly one of these — callers (the cortex bridge) ack on it, so a
5
+ * never-settling promise would strand cortex's Apply button forever.
6
+ *
7
+ * `superseded` means a newer payload for the same blueprint replaced this one:
8
+ * not a success, not a failure, and deliberately never acked.
9
+ */
10
+ export type ApplyOutcome = {
11
+ status: 'applied';
12
+ } | {
13
+ status: 'failed';
14
+ error: unknown;
15
+ } | {
16
+ status: 'superseded';
17
+ };
2
18
  /**
3
19
  * Applies email_template blueprints from the chat agent to the Stripo editor.
4
20
  *
@@ -9,6 +25,10 @@ type ApplyStatus = 'pending' | 'applying' | 'applied' | 'failed';
9
25
  * - manual Apply click (first apply): immediate.
10
26
  * - blueprint_update after the user has applied once: debounced 250 ms trailing
11
27
  * so rapid mid-stream updates don't thrash updateHtmlAndCss.
28
+ *
29
+ * A click that lands before Stripo finished initializing is queued rather than
30
+ * dropped, and flushed when `isStripoInitialized` flips — the user does not
31
+ * have to click again.
12
32
  */
13
33
  export declare const useEmailTemplateApplier: () => {
14
34
  applyStatus: import("vue").Ref<Record<string, ApplyStatus>>;
@@ -16,11 +36,11 @@ export declare const useEmailTemplateApplier: () => {
16
36
  applyTemplate: (blueprintId: string, data: {
17
37
  html?: string;
18
38
  css?: string;
19
- }) => void;
39
+ }) => Promise<ApplyOutcome>;
20
40
  applyTemplateDebounced: (blueprintId: string, data: {
21
41
  html?: string;
22
42
  css?: string;
23
- }) => void;
43
+ }) => Promise<ApplyOutcome>;
24
44
  };
25
45
  export declare const resetEmailTemplateApplier: () => void;
26
46
  export {};
@@ -1,22 +1,26 @@
1
1
  /**
2
2
  * Outbound bridge — publishes the current editor's html/css to the shared
3
- * Pinia store `guidoEmailEditor` so cortex-fe can read it at chat-stream
4
- * submit time and forward it as `clientState.editor`. This is what enables
3
+ * Pinia store `guidoEmailEditor`. cortex-fe reads it at chat-stream submit
4
+ * time and forwards it as `clientState.editor`, which is what enables
5
5
  * sub-case 1.3 (refine an already-loaded template via the email-agent).
6
6
  *
7
7
  * The bridge writes on three triggers:
8
8
  *
9
9
  * 1. Stripo finishes initializing — first snapshot.
10
- * 2. Editor `hasChanges` flips to true — debounced 500 ms so a streaming
11
- * Stripo edit doesn't thrash the store.
10
+ * 2. Every document change, via the `changeSeq` counter — debounced 500 ms
11
+ * so a streaming Stripo edit doesn't thrash the store.
12
12
  * 3. Editor `hasChanges` flips back to false (post-save / autosave) —
13
13
  * immediate, since this is the canonical "saved" snapshot.
14
14
  *
15
15
  * `getTemplateData()` is a Stripo iframe call; it must not run before
16
16
  * `isStripoInitialized` is true. The bridge guards against that.
17
17
  *
18
- * If cortex-fe isn't loaded the store still gets written — that's fine;
19
- * the store has zero overhead and any future reader will find consistent
20
- * state. The bridge is a one-way pipe with no consumer dependency.
18
+ * The store is written even with no reader attached — it has zero overhead,
19
+ * and a late reader still finds consistent state.
20
+ *
21
+ * Readers are not optional any more, so treat the publish timing as part of
22
+ * the contract: the store ships as `@useinsider/guido/editor-state`, and
23
+ * email-fe imports it to scan the live html for its Array of Objects lock.
24
+ * Weakening a trigger here silently breaks that lock in the consumer.
21
25
  */
22
26
  export declare const useGuidoStateBridge: () => void;
@@ -2,5 +2,7 @@ export { default as Guido } from './components/Guido.vue';
2
2
  export type { GuidoConfig, GuidoConfigInput, IdentityConfig, PartnerConfig, TemplateConfig, TemplateMigrationConfig, LegacyRecommendationConfig, EditorConfig, UIConfig, FeaturesConfig, BlocksConfig, CompilerConfig, DynamicContent, EmailHeader, DefaultBlockType, CustomBlockType, ValidationResult, ValidationError, } from './@types/config';
3
3
  export { validateConfig, parseConfig, parseConfigSafe, isValidConfig, getValidationErrors, MessageType, ProductType, } from './@types/config';
4
4
  export { assignTrackingIds, readTrackFloor, readTrackSeq } from './public/trackingIds';
5
+ export { useGuidoEmailEditorStore } from './public/editorState';
6
+ export type { GuidoEmailEditorState } from './public/editorState';
5
7
  export type { StripoEventType } from './@types/events';
6
8
  export type { PositionData, DynamicContentEvent, MergeTagClickEvent, MergeTag, BaseDynamicContent, DynamicContentNode, DynamicContentList, TooltipOptions, TextValueObject, L10n, } from './@types/generic';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Published as `@useinsider/guido/editor-state` — the live editor snapshot Guido
3
+ * writes into the host's shared Pinia singleton.
4
+ *
5
+ * Importing from here pulls in nothing but pinia, so the editor never enters a
6
+ * consumer's module graph. Re-export surface only: this is the contract,
7
+ * `stores/guido-email-editor` is the implementation and stays free to move.
8
+ *
9
+ * Consumers read the store by importing it; the id `guidoEmailEditor` remains
10
+ * part of the contract for cortex-fe, which probes `pinia._s` at runtime instead
11
+ * of taking a build-time dependency on this package.
12
+ */
13
+ export { useGuidoEmailEditorStore } from '../stores/guido-email-editor';
14
+ export type { GuidoEmailEditorState } from '../stores/guido-email-editor';
@@ -4,6 +4,10 @@
4
4
  * Importing from here pulls in nothing else, so the editor never enters a
5
5
  * consumer's module graph. Re-export surface only: this is the contract,
6
6
  * `utils/linkTrackingIds` is the implementation and stays free to move.
7
+ *
8
+ * Relative, not the `@@/Utils/` alias: vue-tsc copies the specifier into the emitted
9
+ * .d.ts verbatim, and a consumer's TypeScript cannot resolve our alias table. A relative
10
+ * path resolves against dist/src/, so the published types actually load.
7
11
  */
8
- export { assignTrackingIds, readTrackFloor, readTrackSeq, TRACK_ID_ATTRIBUTE, TRACK_SEQ_ATTRIBUTE, } from '@@/Utils/linkTrackingIds';
9
- export type { TrackingIdResult } from '@@/Utils/linkTrackingIds';
12
+ export { assignTrackingIds, readTrackFloor, readTrackSeq, TRACK_ID_ATTRIBUTE, TRACK_SEQ_ATTRIBUTE, } from '../utils/linkTrackingIds';
13
+ export type { TrackingIdResult } from '../utils/linkTrackingIds';
@@ -6,6 +6,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
6
6
  isPreviewModeOpen: boolean;
7
7
  editorVisualMode: string;
8
8
  hasChanges: boolean;
9
+ changeSeq: number;
9
10
  isStripoInitialized: boolean;
10
11
  templateId: string;
11
12
  syncModulesEnabled: boolean;
@@ -19,6 +20,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
19
20
  isPreviewModeOpen: boolean;
20
21
  editorVisualMode: string;
21
22
  hasChanges: boolean;
23
+ changeSeq: number;
22
24
  isStripoInitialized: boolean;
23
25
  templateId: string;
24
26
  syncModulesEnabled: boolean;
@@ -31,6 +33,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
31
33
  isPreviewModeOpen: boolean;
32
34
  editorVisualMode: string;
33
35
  hasChanges: boolean;
36
+ changeSeq: number;
34
37
  isStripoInitialized: boolean;
35
38
  templateId: string;
36
39
  syncModulesEnabled: boolean;
@@ -44,6 +47,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
44
47
  isPreviewModeOpen: boolean;
45
48
  editorVisualMode: string;
46
49
  hasChanges: boolean;
50
+ changeSeq: number;
47
51
  isStripoInitialized: boolean;
48
52
  templateId: string;
49
53
  syncModulesEnabled: boolean;
@@ -56,6 +60,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
56
60
  isPreviewModeOpen: boolean;
57
61
  editorVisualMode: string;
58
62
  hasChanges: boolean;
63
+ changeSeq: number;
59
64
  isStripoInitialized: boolean;
60
65
  templateId: string;
61
66
  syncModulesEnabled: boolean;
@@ -69,6 +74,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
69
74
  isPreviewModeOpen: boolean;
70
75
  editorVisualMode: string;
71
76
  hasChanges: boolean;
77
+ changeSeq: number;
72
78
  isStripoInitialized: boolean;
73
79
  templateId: string;
74
80
  syncModulesEnabled: boolean;
@@ -81,6 +87,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
81
87
  isPreviewModeOpen: boolean;
82
88
  editorVisualMode: string;
83
89
  hasChanges: boolean;
90
+ changeSeq: number;
84
91
  isStripoInitialized: boolean;
85
92
  templateId: string;
86
93
  syncModulesEnabled: boolean;
@@ -94,6 +101,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
94
101
  isPreviewModeOpen: boolean;
95
102
  editorVisualMode: string;
96
103
  hasChanges: boolean;
104
+ changeSeq: number;
97
105
  isStripoInitialized: boolean;
98
106
  templateId: string;
99
107
  syncModulesEnabled: boolean;
@@ -106,6 +114,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
106
114
  isPreviewModeOpen: boolean;
107
115
  editorVisualMode: string;
108
116
  hasChanges: boolean;
117
+ changeSeq: number;
109
118
  isStripoInitialized: boolean;
110
119
  templateId: string;
111
120
  syncModulesEnabled: boolean;
@@ -119,6 +128,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
119
128
  isPreviewModeOpen: boolean;
120
129
  editorVisualMode: string;
121
130
  hasChanges: boolean;
131
+ changeSeq: number;
122
132
  isStripoInitialized: boolean;
123
133
  templateId: string;
124
134
  syncModulesEnabled: boolean;
@@ -131,6 +141,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
131
141
  isPreviewModeOpen: boolean;
132
142
  editorVisualMode: string;
133
143
  hasChanges: boolean;
144
+ changeSeq: number;
134
145
  isStripoInitialized: boolean;
135
146
  templateId: string;
136
147
  syncModulesEnabled: boolean;
@@ -144,6 +155,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
144
155
  isPreviewModeOpen: boolean;
145
156
  editorVisualMode: string;
146
157
  hasChanges: boolean;
158
+ changeSeq: number;
147
159
  isStripoInitialized: boolean;
148
160
  templateId: string;
149
161
  syncModulesEnabled: boolean;
@@ -156,6 +168,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
156
168
  isPreviewModeOpen: boolean;
157
169
  editorVisualMode: string;
158
170
  hasChanges: boolean;
171
+ changeSeq: number;
159
172
  isStripoInitialized: boolean;
160
173
  templateId: string;
161
174
  syncModulesEnabled: boolean;
@@ -169,6 +182,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
169
182
  isPreviewModeOpen: boolean;
170
183
  editorVisualMode: string;
171
184
  hasChanges: boolean;
185
+ changeSeq: number;
172
186
  isStripoInitialized: boolean;
173
187
  templateId: string;
174
188
  syncModulesEnabled: boolean;
@@ -181,6 +195,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
181
195
  isPreviewModeOpen: boolean;
182
196
  editorVisualMode: string;
183
197
  hasChanges: boolean;
198
+ changeSeq: number;
184
199
  isStripoInitialized: boolean;
185
200
  templateId: string;
186
201
  syncModulesEnabled: boolean;
@@ -194,6 +209,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
194
209
  isPreviewModeOpen: boolean;
195
210
  editorVisualMode: string;
196
211
  hasChanges: boolean;
212
+ changeSeq: number;
197
213
  isStripoInitialized: boolean;
198
214
  templateId: string;
199
215
  syncModulesEnabled: boolean;
@@ -206,6 +222,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
206
222
  isPreviewModeOpen: boolean;
207
223
  editorVisualMode: string;
208
224
  hasChanges: boolean;
225
+ changeSeq: number;
209
226
  isStripoInitialized: boolean;
210
227
  templateId: string;
211
228
  syncModulesEnabled: boolean;
@@ -219,6 +236,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
219
236
  isPreviewModeOpen: boolean;
220
237
  editorVisualMode: string;
221
238
  hasChanges: boolean;
239
+ changeSeq: number;
222
240
  isStripoInitialized: boolean;
223
241
  templateId: string;
224
242
  syncModulesEnabled: boolean;
@@ -231,6 +249,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
231
249
  isPreviewModeOpen: boolean;
232
250
  editorVisualMode: string;
233
251
  hasChanges: boolean;
252
+ changeSeq: number;
234
253
  isStripoInitialized: boolean;
235
254
  templateId: string;
236
255
  syncModulesEnabled: boolean;
@@ -244,6 +263,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
244
263
  isPreviewModeOpen: boolean;
245
264
  editorVisualMode: string;
246
265
  hasChanges: boolean;
266
+ changeSeq: number;
247
267
  isStripoInitialized: boolean;
248
268
  templateId: string;
249
269
  syncModulesEnabled: boolean;
@@ -256,6 +276,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
256
276
  isPreviewModeOpen: boolean;
257
277
  editorVisualMode: string;
258
278
  hasChanges: boolean;
279
+ changeSeq: number;
259
280
  isStripoInitialized: boolean;
260
281
  templateId: string;
261
282
  syncModulesEnabled: boolean;
@@ -269,6 +290,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
269
290
  isPreviewModeOpen: boolean;
270
291
  editorVisualMode: string;
271
292
  hasChanges: boolean;
293
+ changeSeq: number;
272
294
  isStripoInitialized: boolean;
273
295
  templateId: string;
274
296
  syncModulesEnabled: boolean;
@@ -281,6 +303,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
281
303
  isPreviewModeOpen: boolean;
282
304
  editorVisualMode: string;
283
305
  hasChanges: boolean;
306
+ changeSeq: number;
284
307
  isStripoInitialized: boolean;
285
308
  templateId: string;
286
309
  syncModulesEnabled: boolean;
@@ -294,6 +317,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
294
317
  isPreviewModeOpen: boolean;
295
318
  editorVisualMode: string;
296
319
  hasChanges: boolean;
320
+ changeSeq: number;
297
321
  isStripoInitialized: boolean;
298
322
  templateId: string;
299
323
  syncModulesEnabled: boolean;
@@ -306,6 +330,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
306
330
  isPreviewModeOpen: boolean;
307
331
  editorVisualMode: string;
308
332
  hasChanges: boolean;
333
+ changeSeq: number;
309
334
  isStripoInitialized: boolean;
310
335
  templateId: string;
311
336
  syncModulesEnabled: boolean;
@@ -1,41 +1,63 @@
1
- /**
2
- * Outbound contract Guido publishes to the host's shared Pinia singleton so
3
- * cortex-fe can pick up the current editor state and forward it as
4
- * `clientState.editor` on the next chat-stream POST.
5
- *
6
- * State + getters only — `useGuidoStateBridge` writes via `$patch`, no actions
7
- * live here (architecture invariant).
8
- *
9
- * Store id `guidoEmailEditor` is cross-MFE: cortex-fe reads it via
10
- * `pinia._s.get('guidoEmailEditor')` at runtime. The internal Guido editor
11
- * store keeps id `guidoEditor` — these are intentionally separate. The
12
- * internal store carries UI state that cortex-fe should not see; this public
13
- * store carries the contract Guido is willing to share.
14
- */
15
- export declare const useGuidoEmailEditorStore: import("pinia").StoreDefinition<"guidoEmailEditor", {
1
+ /** Terminal outcome of an editor write, keyed by `${blueprintId}:${version}`. */
2
+ export interface ApplyAck {
3
+ status: 'applied' | 'failed';
4
+ /** The `applyRequestedAt` of the click this ack answers — lets cortex drop stale acks. */
5
+ requestedAt: number;
6
+ /** Strictly increasing per key, so two acks in the same millisecond still order. */
7
+ ackAt: number;
8
+ /** Stringified — cortex JSON-clones store-derived payloads, an Error would not survive. */
9
+ error?: string;
10
+ }
11
+ /** The published editor snapshot — the shape a consumer reads off the shared store. */
12
+ export interface GuidoEmailEditorState {
16
13
  /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
17
14
  html: string;
18
15
  /** Current Stripo CSS — es-p* utility classes used by the html. */
19
16
  css: string;
20
17
  /** Wall-clock ms when html/css were last written by the bridge. */
21
18
  lastUpdatedAt: number;
22
- /** Template id from the editor config, for cortex-fe to scope per-template. */
19
+ /** Template id from the editor config, so a consumer can scope per-template. */
23
20
  templateId: string;
24
- }, {
21
+ /** True while a mounted Guido bridge is consuming the chat store. Gates cortex's Apply button. */
22
+ bridgeActive: boolean;
23
+ /** `${blueprintId}:${version}` → terminal outcome of the editor write, written only after it settles. */
24
+ applyAcks: Record<string, ApplyAck>;
25
+ }
26
+ /**
27
+ * Outbound contract Guido publishes to the host's shared Pinia singleton so a
28
+ * consumer can read the current editor state: cortex-fe forwards it as
29
+ * `clientState.editor` on the next chat-stream POST, email-fe scans it for the
30
+ * Array of Objects lock.
31
+ *
32
+ * Published as `@useinsider/guido/editor-state` and from the root barrel, so a
33
+ * consumer imports this store instead of re-declaring one under the same id.
34
+ *
35
+ * This store is **state-only, by contract**. It declares no actions and no
36
+ * non-trivial getters, so `pinia.state.value['guidoEmailEditor']` is a complete
37
+ * view of it. cortex-fe reads it through that public path; adding an action or
38
+ * a getter that cortex needs would silently break that read. Keep it
39
+ * state-only. (`hasSnapshot` stays — cortex does not read it.)
40
+ *
41
+ * `useGuidoStateBridge` (the snapshot) and `useCortexBlueprintBridge` (the
42
+ * apply handshake) are the only intended writers, but that is Guido's own
43
+ * convention rather than something the store enforces: publishing the
44
+ * definition hands a consumer `$patch` as well, exactly as the runtime
45
+ * `pinia._s` probe always did.
46
+ *
47
+ * Store id `guidoEmailEditor` is cross-MFE: cortex-fe reads it at runtime rather
48
+ * than importing it, so the id stays part of the contract. The internal Guido
49
+ * editor store keeps id `guidoEditor` — these are intentionally separate. The
50
+ * internal store carries UI state a consumer should not see; this public store
51
+ * carries the contract Guido is willing to share.
52
+ */
53
+ export declare const useGuidoEmailEditorStore: import("pinia").StoreDefinition<"guidoEmailEditor", GuidoEmailEditorState, {
25
54
  /** True once the bridge has published at least one snapshot. */
26
55
  hasSnapshot: (state: {
27
56
  html: string;
28
57
  css: string;
29
58
  lastUpdatedAt: number;
30
59
  templateId: string;
31
- } & import("pinia").PiniaCustomStateProperties<{
32
- /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
33
- html: string;
34
- /** Current Stripo CSS — es-p* utility classes used by the html. */
35
- css: string;
36
- /** Wall-clock ms when html/css were last written by the bridge. */
37
- lastUpdatedAt: number;
38
- /** Template id from the editor config, for cortex-fe to scope per-template. */
39
- templateId: string;
40
- }>) => boolean;
60
+ bridgeActive: boolean;
61
+ applyAcks: Record<string, ApplyAck>;
62
+ } & import("pinia").PiniaCustomStateProperties<GuidoEmailEditorState>) => boolean;
41
63
  }, {}>;
@@ -30,3 +30,9 @@ export declare const replaceString: (string?: string, values?: Record<string, st
30
30
  * a string nor a number becomes ''.
31
31
  */
32
32
  export declare const asText: (value: unknown) => string;
33
+ /**
34
+ * Narrows an unknown API value to a plain JSON object. Arrays and exotic
35
+ * objects (Blob, Date, FormData) are rejected, so a response body that useHttp
36
+ * parsed as text or blob can never be read as a JSON payload.
37
+ */
38
+ export declare const isPlainObject: <T>(value: T) => value is T & Record<string, unknown>;
@@ -0,0 +1,20 @@
1
+ import type { BaseDynamicContent } from '@@/Types/generic';
2
+ export type DynamicContentFormat = NonNullable<BaseDynamicContent['format']>;
3
+ /** Format key the dynamic-content picker sends for a custom date/time format. */
4
+ export declare const TIME_FORMAT_KEY = "time-format";
5
+ /** Wraps a Liquid filter argument in whichever quote style the value itself does not use. */
6
+ export declare const quoteArgument: (value: string) => string | null;
7
+ /**
8
+ * `{ key: 'time-format', value: '%Y-%m-%d' }` -> `date: "%Y-%m-%d"`.
9
+ *
10
+ * Only a custom date/time strftime pattern has a Liquid equivalent. Anything else returns null so
11
+ * the caller emits a plain tag rather than one that silently lost — or literally printed — its
12
+ * format.
13
+ * @example formatToLiquidFilter({ key: 'time-format', value: '%b %-d, %Y' }) -> date: "%b %-d, %Y"
14
+ */
15
+ export declare const formatToLiquidFilter: (format: DynamicContentFormat) => string | null;
16
+ /**
17
+ * Inverse of `formatToLiquidFilter` — reads the picker format back out of one filter segment, so a
18
+ * reopened tag restores its selection in the host's format dropdown.
19
+ */
20
+ export declare const liquidFilterToFormat: (segment: string) => DynamicContentFormat | null;
@@ -8,6 +8,10 @@ const o = e("guidoEditor", {
8
8
  isPreviewModeOpen: !1,
9
9
  editorVisualMode: "desktop",
10
10
  hasChanges: !1,
11
+ // Bumped on every Stripo document change. `hasChanges` is a latch that stays
12
+ // true until a save, so it cannot signal the second edit onwards; watchers
13
+ // that need every change (the outbound state bridge) watch this instead.
14
+ changeSeq: 0,
11
15
  isStripoInitialized: !1,
12
16
  templateId: "",
13
17
  syncModulesEnabled: !1,
@@ -1,14 +1,12 @@
1
1
  import { defineStore as e } from "pinia";
2
2
  const d = e("guidoEmailEditor", {
3
3
  state: () => ({
4
- /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
5
4
  html: "",
6
- /** Current Stripo CSS — es-p* utility classes used by the html. */
7
5
  css: "",
8
- /** Wall-clock ms when html/css were last written by the bridge. */
9
6
  lastUpdatedAt: 0,
10
- /** Template id from the editor config, for cortex-fe to scope per-template. */
11
- templateId: ""
7
+ templateId: "",
8
+ bridgeActive: !1,
9
+ applyAcks: {}
12
10
  }),
13
11
  getters: {
14
12
  /** True once the bridge has published at least one snapshot. */