@designcrowd/library.brand-page 5.18.1-undo-29 → 5.18.1-undo-32

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 (46) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-BxL6mTJW.js → CreatedOnBrandCrowd-de-de-DUgkmx89.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BPbjqfkj.js → CreatedOnBrandCrowd-en-us-C5dQGLuA.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DDsoe143.js → CreatedOnBrandCrowd-es-es-CjPcnNlq.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dak7ppy_.js → CreatedOnBrandCrowd-fr-ca-D2wnZOOE.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dak7ppy_.js → CreatedOnBrandCrowd-fr-fr-D2wnZOOE.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-BUAneEVD.js → CreatedOnBrandCrowd-pt-br-OQj-IBh_.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-BUAneEVD.js → CreatedOnBrandCrowd-pt-pt-OQj-IBh_.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BJ00bTz_.js → CreatedOnDcom-de-de-DLijuIaL.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-B0V1M9Hs.js → CreatedOnDcom-en-us-D9M_heGE.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-YDMLB7u6.js → CreatedOnDcom-es-es-PgWwbBLA.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-DppQ26s1.js → CreatedOnDcom-fr-ca-BXiB-abj.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-0Xi-3slB.js → CreatedOnDcom-fr-fr-BXhTmu7F.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-De-wHqSu.js → CreatedOnDcom-pt-br-3e6y567w.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-CQGAsn5D.js → CreatedOnDcom-pt-pt-GojOtHmO.js} +1 -1
  15. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +1 -1
  16. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
  17. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +1 -1
  18. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
  19. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +1 -1
  20. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
  21. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +1 -1
  22. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +1 -1
  23. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +1 -1
  24. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +1 -1
  25. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +46 -22
  26. package/lib/src/brand-page-maker/stores/history/history-store.js +66 -66
  27. package/lib/src/index.mjs +1 -1
  28. package/lib/src/{lib-YRlMax_i.js → lib-DVs3jUit.js} +18674 -18654
  29. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +65 -1
  30. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +12 -0
  31. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
  32. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
  33. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +1 -0
  34. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +2 -1
  35. package/lib/src/shared/logo-settings/store/editable-logo-store.d.ts +14 -0
  36. package/lib/src/shared/logo-settings/store/editable-logo-store.js +26 -10
  37. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +1 -1
  38. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +2 -1
  39. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -1
  40. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +53 -23
  43. package/src/brand-page-maker/stores/history/history-store.test.ts +29 -135
  44. package/src/brand-page-maker/stores/history/history-store.ts +85 -76
  45. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +12 -0
  46. package/src/shared/logo-settings/store/editable-logo-store.ts +28 -11
@@ -146,7 +146,7 @@ declare const _default: import("vue").DefineComponent<{}, {
146
146
  }>(payload: P): Promise<void>;
147
147
  _captureTemplateSwitchIfEnabled(): void;
148
148
  applyHistorySnapshot(snapshot: import("@brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
149
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
149
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
150
150
  _navigateToActivePage(activePageId?: string | undefined): void;
151
151
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page").BrandPageModel): void;
152
152
  export(): import("@brand-page").BrandPageModel;
@@ -844,6 +844,70 @@ declare const _default: import("vue").DefineComponent<{}, {
844
844
  sendAiSendPromptEvent: (ctaLocation: string) => void;
845
845
  sendAiStopGenerationEvent: (ctaLocation: string) => void;
846
846
  };
847
+ historyStore: import("pinia").Store<"history", Pick<{
848
+ entries: import("vue").ShallowRef<import("@brand-page-maker/stores/history/history-store").HistoryEntry[], import("@brand-page-maker/stores/history/history-store").HistoryEntry[]>;
849
+ currentIndex: import("vue").Ref<number, number>;
850
+ isApplyingSnapshot: import("vue").Ref<boolean, boolean>;
851
+ baselineCaptured: import("vue").Ref<boolean, boolean>;
852
+ canUndo: import("vue").ComputedRef<boolean>;
853
+ canRedo: import("vue").ComputedRef<boolean>;
854
+ captureBaseline: () => void;
855
+ capture: (args: {
856
+ source: "manual_edit" | "template_switch" | "ai_changeset" | "rich_text";
857
+ actor: "user" | "ai_builder";
858
+ label?: string | undefined;
859
+ }) => "committed" | "oversized" | "noop" | "skipped";
860
+ captureTemplateSwitch: (label?: string | undefined) => "committed" | "oversized" | "noop" | "skipped";
861
+ captureAiChangeset: (label?: string | undefined) => "committed" | "oversized" | "noop" | "skipped";
862
+ mergeNextMutation: () => void;
863
+ undo: () => Promise<void>;
864
+ redo: () => Promise<void>;
865
+ reset: () => void;
866
+ getCurrentSizeBytes: () => number;
867
+ getCurrentEntryCount: () => number;
868
+ }, "entries" | "currentIndex" | "isApplyingSnapshot" | "baselineCaptured">, Pick<{
869
+ entries: import("vue").ShallowRef<import("@brand-page-maker/stores/history/history-store").HistoryEntry[], import("@brand-page-maker/stores/history/history-store").HistoryEntry[]>;
870
+ currentIndex: import("vue").Ref<number, number>;
871
+ isApplyingSnapshot: import("vue").Ref<boolean, boolean>;
872
+ baselineCaptured: import("vue").Ref<boolean, boolean>;
873
+ canUndo: import("vue").ComputedRef<boolean>;
874
+ canRedo: import("vue").ComputedRef<boolean>;
875
+ captureBaseline: () => void;
876
+ capture: (args: {
877
+ source: "manual_edit" | "template_switch" | "ai_changeset" | "rich_text";
878
+ actor: "user" | "ai_builder";
879
+ label?: string | undefined;
880
+ }) => "committed" | "oversized" | "noop" | "skipped";
881
+ captureTemplateSwitch: (label?: string | undefined) => "committed" | "oversized" | "noop" | "skipped";
882
+ captureAiChangeset: (label?: string | undefined) => "committed" | "oversized" | "noop" | "skipped";
883
+ mergeNextMutation: () => void;
884
+ undo: () => Promise<void>;
885
+ redo: () => Promise<void>;
886
+ reset: () => void;
887
+ getCurrentSizeBytes: () => number;
888
+ getCurrentEntryCount: () => number;
889
+ }, "canUndo" | "canRedo">, Pick<{
890
+ entries: import("vue").ShallowRef<import("@brand-page-maker/stores/history/history-store").HistoryEntry[], import("@brand-page-maker/stores/history/history-store").HistoryEntry[]>;
891
+ currentIndex: import("vue").Ref<number, number>;
892
+ isApplyingSnapshot: import("vue").Ref<boolean, boolean>;
893
+ baselineCaptured: import("vue").Ref<boolean, boolean>;
894
+ canUndo: import("vue").ComputedRef<boolean>;
895
+ canRedo: import("vue").ComputedRef<boolean>;
896
+ captureBaseline: () => void;
897
+ capture: (args: {
898
+ source: "manual_edit" | "template_switch" | "ai_changeset" | "rich_text";
899
+ actor: "user" | "ai_builder";
900
+ label?: string | undefined;
901
+ }) => "committed" | "oversized" | "noop" | "skipped";
902
+ captureTemplateSwitch: (label?: string | undefined) => "committed" | "oversized" | "noop" | "skipped";
903
+ captureAiChangeset: (label?: string | undefined) => "committed" | "oversized" | "noop" | "skipped";
904
+ mergeNextMutation: () => void;
905
+ undo: () => Promise<void>;
906
+ redo: () => Promise<void>;
907
+ reset: () => void;
908
+ getCurrentSizeBytes: () => number;
909
+ getCurrentEntryCount: () => number;
910
+ }, "reset" | "undo" | "redo" | "captureBaseline" | "capture" | "captureTemplateSwitch" | "captureAiChangeset" | "mergeNextMutation" | "getCurrentSizeBytes" | "getCurrentEntryCount">>;
847
911
  externalEditor: import("@tiptap/vue-3").Editor;
848
912
  }, {
849
913
  categoryTabs: CategoryTab[];
@@ -11,6 +11,7 @@ import { useExternalRichTextEditor } from '@brand-page-maker/composables/rich-te
11
11
  import { switchAllContentToRTE, transformToRTE, RTE_ALLOWED_SECTION_TYPES, } from '@brand-page-maker/utils';
12
12
  import { useUiStyleSettingsStore } from '../../../style-settings/store';
13
13
  import { usePageNavigationStore } from '../../../../shared/page-navigation/store/page-navigation-store';
14
+ import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
14
15
  export default defineComponent({
15
16
  setup() {
16
17
  const brandPageMakerStore = useBrandPageMakerStore();
@@ -20,6 +21,7 @@ export default defineComponent({
20
21
  const styleSettingsStore = useUiStyleSettingsStore();
21
22
  const userStore = useBrandPageMakerUserStore();
22
23
  const eventTracker = useEventTracker();
24
+ const historyStore = useHistoryStore();
23
25
  const { externalEditor } = useExternalRichTextEditor();
24
26
  return {
25
27
  mode: computed(() => contentSettingsStore.mode),
@@ -36,6 +38,7 @@ export default defineComponent({
36
38
  pageSettingsStore,
37
39
  styleSettingsStore,
38
40
  eventTracker,
41
+ historyStore,
39
42
  externalEditor,
40
43
  };
41
44
  },
@@ -52,6 +55,15 @@ export default defineComponent({
52
55
  },
53
56
  mounted() {
54
57
  if (this.canUseRichTextEditor) {
58
+ // `switchAllContentToRTE` walks every content item on every page
59
+ // and rewraps plain-text titles/paragraphs in TipTap HTML so they
60
+ // render through the inline RTE. That's a hydration write — it
61
+ // happens whenever the Content sidebar tab mounts (incl. when the
62
+ // user clicks the header in the canvas) and produces no
63
+ // user-visible change. Arm the merge flag so the watcher folds
64
+ // the resulting flush into the current head entry instead of
65
+ // pushing a phantom undo step.
66
+ this.historyStore.mergeNextMutation();
55
67
  switchAllContentToRTE(this.pageSettingsStore, this.externalEditor, this.styleSettingsStore);
56
68
  }
57
69
  },
@@ -662,7 +662,7 @@ declare const _default: import("vue").DefineComponent<{}, {
662
662
  }>(payload: P): Promise<void>;
663
663
  _captureTemplateSwitchIfEnabled(): void;
664
664
  applyHistorySnapshot(snapshot: import("@brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
665
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
665
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
666
666
  _navigateToActivePage(activePageId?: string | undefined): void;
667
667
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page").BrandPageModel): void;
668
668
  export(): import("@brand-page").BrandPageModel;
@@ -609,7 +609,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
609
609
  }>(payload: P): Promise<void>;
610
610
  _captureTemplateSwitchIfEnabled(): void;
611
611
  applyHistorySnapshot(snapshot: import("@brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
612
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
612
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
613
613
  _navigateToActivePage(activePageId?: string | undefined): void;
614
614
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page").BrandPageModel): void;
615
615
  export(): import("@brand-page").BrandPageModel;
@@ -111,6 +111,7 @@ declare const _default: import("vue").DefineComponent<{}, {
111
111
  getEditableLogoVariationByVariationId(id: number): any;
112
112
  getEditableLogoVariationByBase64(base64: string): any;
113
113
  updateEditableLogoImageUrlAsync(defaultPrimaryText?: string | undefined, overrideLogoVariation?: number | undefined, layout?: string | undefined): Promise<void>;
114
+ stampLogoBase64IntoBrandPageData(overrideLogoVariation?: number | undefined): void;
114
115
  restoreVariationsForDraftToken(logoDraftToken: string): boolean;
115
116
  updateMetadataAsync(defaultPrimaryText?: string | undefined, resetLogoTemplateMetadata?: boolean, layout?: string | undefined): Promise<void>;
116
117
  updateEditableLogoImageByVariationAsync(editableLogoVariation: import("../store/editable-logo-store").EditableLogoVariation, defaultPrimaryText?: string | undefined, layout?: string | undefined): Promise<import("../../../clients/fabric-render-client").ModifyLogoDataResponse | null>;
@@ -136,7 +136,7 @@ declare const _default: import("vue").DefineComponent<{}, {
136
136
  }>(payload: P): Promise<void>;
137
137
  _captureTemplateSwitchIfEnabled(): void;
138
138
  applyHistorySnapshot(snapshot: import("../../../brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
139
- _syncLogoSettingsFromSnapshot(snapshot: import("../../../brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
139
+ _syncLogoSettingsFromSnapshot(snapshot: import("../../../brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
140
140
  _navigateToActivePage(activePageId?: string | undefined): void;
141
141
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("../../../brand-page").BrandPageModel): void;
142
142
  export(): import("../../../brand-page").BrandPageModel;
@@ -300,6 +300,7 @@ declare const _default: import("vue").DefineComponent<{}, {
300
300
  getEditableLogoVariationByVariationId(id: number): any;
301
301
  getEditableLogoVariationByBase64(base64: string): any;
302
302
  updateEditableLogoImageUrlAsync(defaultPrimaryText?: string | undefined, overrideLogoVariation?: number | undefined, layout?: string | undefined): Promise<void>;
303
+ stampLogoBase64IntoBrandPageData(overrideLogoVariation?: number | undefined): void;
303
304
  restoreVariationsForDraftToken(logoDraftToken: string): boolean;
304
305
  updateMetadataAsync(defaultPrimaryText?: string | undefined, resetLogoTemplateMetadata?: boolean, layout?: string | undefined): Promise<void>;
305
306
  updateEditableLogoImageByVariationAsync(editableLogoVariation: import("../store").EditableLogoVariation, defaultPrimaryText?: string | undefined, layout?: string | undefined): Promise<import("../../../clients/fabric-render-client").ModifyLogoDataResponse | null>;
@@ -186,6 +186,20 @@ export declare const useUiEditableLogoStore: import("pinia").StoreDefinition<"ui
186
186
  getEditableLogoVariationByVariationId(id: number): any;
187
187
  getEditableLogoVariationByBase64(base64: string): any;
188
188
  updateEditableLogoImageUrlAsync(defaultPrimaryText?: string, overrideLogoVariation?: number | undefined, layout?: string): Promise<void>;
189
+ /**
190
+ * Stamp `editableLogoVariations[*].base64` across every consumer in
191
+ * `brandPageData`: header/footer logo sections (desktop + mobile)
192
+ * and every `LogoContent` block on every page. Used by the live edit
193
+ * path after a fabric-render POST AND by the undo/redo BYO branch
194
+ * after `restoreVariationsForDraftToken` has rehydrated the cache.
195
+ *
196
+ * The history store strips `logoImageBase64Url` from snapshots to
197
+ * keep them under the 25 MB per-entry cap, so something has to
198
+ * write the base64 back into `brandPageData` after restore — for
199
+ * designed logos it's the awaited fabric-render call; for BYO it's
200
+ * this method.
201
+ */
202
+ stampLogoBase64IntoBrandPageData(overrideLogoVariation?: number): void;
189
203
  /**
190
204
  * Restore the variations grid for `logoDraftToken` from the per-token
191
205
  * cache. Called by `_syncLogoSettingsFromSnapshot` during undo/redo.
@@ -218,6 +218,32 @@ export const useUiEditableLogoStore = defineStore('uiEditableLogo', {
218
218
  async updateEditableLogoImageUrlAsync(defaultPrimaryText, overrideLogoVariation, layout) {
219
219
  const tasks = this.editableLogoVariations.map((x) => this.updateEditableLogoImageByVariationAsync(x, defaultPrimaryText, layout));
220
220
  await Promise.all(tasks);
221
+ this.stampLogoBase64IntoBrandPageData(overrideLogoVariation);
222
+ // Stash the freshly rendered variation set under the active draft
223
+ // token so undo can restore it later without re-rendering. This is
224
+ // the only place in production that meaningfully populates
225
+ // `editableLogoVariations` (BYO upload + designed-logo setStateAsync
226
+ // both funnel here), so caching here keeps every distinct draft's
227
+ // grid available for the lifetime of the session.
228
+ if (this.logoDraftToken) {
229
+ this.variationsByDraftToken[this.logoDraftToken] =
230
+ this.editableLogoVariations.map((v) => ({ ...v }));
231
+ }
232
+ },
233
+ /**
234
+ * Stamp `editableLogoVariations[*].base64` across every consumer in
235
+ * `brandPageData`: header/footer logo sections (desktop + mobile)
236
+ * and every `LogoContent` block on every page. Used by the live edit
237
+ * path after a fabric-render POST AND by the undo/redo BYO branch
238
+ * after `restoreVariationsForDraftToken` has rehydrated the cache.
239
+ *
240
+ * The history store strips `logoImageBase64Url` from snapshots to
241
+ * keep them under the 25 MB per-entry cap, so something has to
242
+ * write the base64 back into `brandPageData` after restore — for
243
+ * designed logos it's the awaited fabric-render call; for BYO it's
244
+ * this method.
245
+ */
246
+ stampLogoBase64IntoBrandPageData(overrideLogoVariation) {
221
247
  const pageSettingStore = useUiPageSettingsStore();
222
248
  [PAGE_SECTIONS.header, PAGE_SECTIONS.footer].forEach((section) => {
223
249
  [VIEWPORTS.desktop, VIEWPORTS.mobile].forEach((viewport) => {
@@ -243,16 +269,6 @@ export const useUiEditableLogoStore = defineStore('uiEditableLogo', {
243
269
  }
244
270
  });
245
271
  });
246
- // Stash the freshly rendered variation set under the active draft
247
- // token so undo can restore it later without re-rendering. This is
248
- // the only place in production that meaningfully populates
249
- // `editableLogoVariations` (BYO upload + designed-logo setStateAsync
250
- // both funnel here), so caching here keeps every distinct draft's
251
- // grid available for the lifetime of the session.
252
- if (this.logoDraftToken) {
253
- this.variationsByDraftToken[this.logoDraftToken] =
254
- this.editableLogoVariations.map((v) => ({ ...v }));
255
- }
256
272
  },
257
273
  /**
258
274
  * Restore the variations grid for `logoDraftToken` from the per-token
@@ -3848,7 +3848,7 @@ declare const _default: import("vue").DefineComponent<{}, {
3848
3848
  }>(payload: P): Promise<void>;
3849
3849
  _captureTemplateSwitchIfEnabled(): void;
3850
3850
  applyHistorySnapshot(snapshot: import("@brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
3851
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
3851
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
3852
3852
  _navigateToActivePage(activePageId?: string | undefined): void;
3853
3853
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page").BrandPageModel): void;
3854
3854
  export(): import("@brand-page").BrandPageModel;
@@ -4287,6 +4287,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
4287
4287
  getEditableLogoVariationByVariationId(id: number): any;
4288
4288
  getEditableLogoVariationByBase64(base64: string): any;
4289
4289
  updateEditableLogoImageUrlAsync(defaultPrimaryText?: string | undefined, overrideLogoVariation?: number | undefined, layout?: string | undefined): Promise<void>;
4290
+ stampLogoBase64IntoBrandPageData(overrideLogoVariation?: number | undefined): void;
4290
4291
  restoreVariationsForDraftToken(logoDraftToken: string): boolean;
4291
4292
  updateMetadataAsync(defaultPrimaryText?: string | undefined, resetLogoTemplateMetadata?: boolean, layout?: string | undefined): Promise<void>;
4292
4293
  updateEditableLogoImageByVariationAsync(editableLogoVariation: import("@shared/logo-settings/store").EditableLogoVariation, defaultPrimaryText?: string | undefined, layout?: string | undefined): Promise<import("../../../../clients/fabric-render-client").ModifyLogoDataResponse | null>;
@@ -4407,7 +4408,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
4407
4408
  }>(payload: P): Promise<void>;
4408
4409
  _captureTemplateSwitchIfEnabled(): void;
4409
4410
  applyHistorySnapshot(snapshot: import("@brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
4410
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
4411
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
4411
4412
  _navigateToActivePage(activePageId?: string | undefined): void;
4412
4413
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page").BrandPageModel): void;
4413
4414
  export(): import("@brand-page").BrandPageModel;
@@ -185,7 +185,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
185
185
  }>(payload: P): Promise<void>;
186
186
  _captureTemplateSwitchIfEnabled(): void;
187
187
  applyHistorySnapshot(snapshot: import("@brand-page/models").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
188
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page/models").BrandPageModel, logoEditsStr?: string | undefined): void;
188
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page/models").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
189
189
  _navigateToActivePage(activePageId?: string | undefined): void;
190
190
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page/models").BrandPageModel): void;
191
191
  export(): import("@brand-page/models").BrandPageModel;
@@ -577,7 +577,7 @@ declare const _default: import("vue").DefineComponent<{}, {
577
577
  }>(payload: P): Promise<void>;
578
578
  _captureTemplateSwitchIfEnabled(): void;
579
579
  applyHistorySnapshot(snapshot: import("@brand-page").BrandPageModel, activePageId?: string | undefined, logoEditsStr?: string | undefined): Promise<void>;
580
- _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): void;
580
+ _syncLogoSettingsFromSnapshot(snapshot: import("@brand-page").BrandPageModel, logoEditsStr?: string | undefined): Promise<void>;
581
581
  _navigateToActivePage(activePageId?: string | undefined): void;
582
582
  _fallbackSidebarIfSelectedContentMissing(snapshot: import("@brand-page").BrandPageModel): void;
583
583
  export(): import("@brand-page").BrandPageModel;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "5.18.1-undo-29",
4
+ "version": "5.18.1-undo-32",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -33,6 +33,7 @@ import type {
33
33
  import type { UserFeatures } from '../user';
34
34
  import { useBrandPageMakerUserStore } from '../user';
35
35
  import { defineStore } from 'pinia';
36
+ import { toRaw } from 'vue';
36
37
  import { useUiStore } from '../ui';
37
38
  import type { LogoSettingsState } from '@shared/logo-settings/store';
38
39
  import {
@@ -894,7 +895,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
894
895
  // text, support text, logo colors, fonts), `editableLogoStore.logoEdits`
895
896
  // is restored from that string so the sidebar form fields and the
896
897
  // save-time fabric-render POST both pick up the pre-edit state.
897
- this._syncLogoSettingsFromSnapshot(snapshot, logoEditsStr);
898
+ await this._syncLogoSettingsFromSnapshot(snapshot, logoEditsStr);
898
899
 
899
900
  // 9. If the sidebar is editing a content item that no longer exists in
900
901
  // the restored page, fall back to the Content tab so the user doesn't
@@ -914,15 +915,24 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
914
915
  this._navigateToActivePage(activePageId);
915
916
  },
916
917
 
917
- _syncLogoSettingsFromSnapshot(
918
+ async _syncLogoSettingsFromSnapshot(
918
919
  snapshot: BrandPageModel,
919
920
  logoEditsStr?: string,
920
- ): void {
921
+ ): Promise<void> {
921
922
  const logoSettingsStore = useUiLogoSettingsStore();
922
923
  const editableLogoStore = useUiEditableLogoStore();
923
924
  const logoStore = useLogoStore();
924
925
  const containerStore = useBrandPageContainerStore();
925
926
 
927
+ // Snapshot the pre-restore logo identity BEFORE we $patch logoEdits.
928
+ // Used below to short-circuit the fabric-render call when the entry
929
+ // we're restoring has the same logoEdits + draft token as the live
930
+ // state — i.e. only non-logo state changed between the two entries.
931
+ const prevLogoEditsStr = JSON.stringify(
932
+ toRaw(editableLogoStore.logoEdits),
933
+ );
934
+ const prevDraftToken = editableLogoStore.logoDraftToken;
935
+
926
936
  // Restore `editableLogoStore.logoEdits` first — sidecar to `brandPageData`,
927
937
  // captured per-entry. Wholesale-replace via `$patch` so a previous
928
938
  // entry's textObjects/colors don't leak through (e.g. switching
@@ -981,6 +991,22 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
981
991
 
982
992
  editableLogoStore.setLogoDraftToken(draftToken);
983
993
 
994
+ // Did the logo identity actually change between the two entries?
995
+ // If both `logoEdits` and the draft token match what was already
996
+ // live, the cached `editableLogoVariations` is still the right
997
+ // render and we just need to stamp it back into `brandPageData`
998
+ // (history-store strips `logoImageBase64Url` from snapshots, so
999
+ // the page-level base64 fields are now empty).
1000
+ const logoUnchanged =
1001
+ prevDraftToken === draftToken &&
1002
+ (logoEditsStr === undefined || logoEditsStr === prevLogoEditsStr);
1003
+
1004
+ if (logoUnchanged) {
1005
+ editableLogoStore.stampLogoBase64IntoBrandPageData();
1006
+
1007
+ return;
1008
+ }
1009
+
984
1010
  const isByo = !!draft?.isByo;
985
1011
 
986
1012
  if (isByo) {
@@ -988,41 +1014,45 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
988
1014
  // `variationsByDraftToken` keyed off that token always matches the
989
1015
  // base64 the user saw at capture. No re-render — the uploaded
990
1016
  // image bytes are the source of truth and there's nothing for
991
- // fabric-render to derive.
992
- editableLogoStore.restoreVariationsForDraftToken(draftToken);
1017
+ // fabric-render to derive. After restoring the variations cache,
1018
+ // stamp the base64 back across header/footer/per-page logo
1019
+ // content; the snapshot itself doesn't carry `logoImageBase64Url`.
1020
+ if (editableLogoStore.restoreVariationsForDraftToken(draftToken)) {
1021
+ editableLogoStore.stampLogoBase64IntoBrandPageData();
1022
+ }
993
1023
 
994
1024
  return;
995
1025
  }
996
1026
 
997
- // Designed (non-BYO) logo. The draft token is reused across every
998
- // form-field edit, so any per-token cache only ever holds the latest
999
- // render — undoing past it would leave `editableLogoVariations` (and
1000
- // every consumer that reads from it: the sidebar preview, the
1001
- // canvas, post-undo save metadata, recalculateLogoSizes) showing
1002
- // the post-edit base64 while the form fields show pre-edit state.
1003
- //
1004
- // Fire-and-forget the same render the live edit path runs:
1027
+ // Designed (non-BYO) logo whose form state actually differs from
1028
+ // the live state. The draft token is reused across every form-field
1029
+ // edit, so the per-token cache only holds the latest render —
1030
+ // re-issue the same fabric-render call the live edit path runs.
1005
1031
  // `updateEditableLogoImageUrlAsync` POSTs the freshly-restored
1006
- // `logoEdits` to fabric-render and writes the returned base64 across
1007
- // every header/footer + per-page logo content + `editableLogoVariations`.
1008
- // The undo/redo apply itself stays synchronous; the canvas updates
1009
- // a moment later when fabric-render responds.
1032
+ // `logoEdits`, writes the returned base64 across every header/footer
1033
+ // + per-page logo content + `editableLogoVariations`.
1010
1034
  //
1011
- // Arm `mergeNextMutation` so the watcher folds the post-render
1012
- // mutation back into the entry we just restored instead of pushing
1013
- // a phantom entry.
1035
+ // Awaited (not fire-and-forget): two undos in quick succession would
1036
+ // otherwise kick off two parallel render bursts whose `Promise.all`
1037
+ // tasks interleave, leaving each variation showing whichever request
1038
+ // happened to win the race rather than the variation that belongs
1039
+ // to the snapshot the user landed on. Awaiting also keeps the call
1040
+ // inside the `applyHistorySnapshot`/`isApplyingSnapshot` window, so
1041
+ // the post-render writeback is naturally suppressed by the existing
1042
+ // watcher guard — no `mergeNextMutation()` needed.
1014
1043
  if (
1015
1044
  containerStore.isPrePurchase &&
1016
1045
  logoEditsStr &&
1017
1046
  logoStore.logoTemplateAsset
1018
1047
  ) {
1019
- useHistoryStore().mergeNextMutation();
1020
- editableLogoStore.updateEditableLogoImageUrlAsync().catch((error) => {
1048
+ try {
1049
+ await editableLogoStore.updateEditableLogoImageUrlAsync();
1050
+ } catch (error) {
1021
1051
  console.error(
1022
1052
  'brandpage-maker-store: failed to re-render logo on history apply',
1023
1053
  error,
1024
1054
  );
1025
- });
1055
+ }
1026
1056
  }
1027
1057
  },
1028
1058
 
@@ -22,16 +22,6 @@ const mockPageNavigationStore = {
22
22
  activePageId: '',
23
23
  };
24
24
 
25
- const mockShopConfigStore: { paymentConfig: unknown } = {
26
- paymentConfig: undefined,
27
- };
28
-
29
- const mockPageSettingsStore: {
30
- pages: { contents: { settings: { content: Record<string, unknown> } }[] }[];
31
- } = {
32
- pages: [],
33
- };
34
-
35
25
  vi.mock('../brandpage-maker/brandpage-maker-store', () => ({
36
26
  useBrandPageMakerStore: vi.fn(() => mockMakerStore),
37
27
  }));
@@ -46,14 +36,6 @@ vi.mock('@shared/page-navigation/store/page-navigation-store', () => ({
46
36
  usePageNavigationStore: vi.fn(() => mockPageNavigationStore),
47
37
  }));
48
38
 
49
- vi.mock('@shared/ecommerce-settings/store/shop-settings-store', () => ({
50
- useShopConfigStore: vi.fn(() => mockShopConfigStore),
51
- }));
52
-
53
- vi.mock('@shared/page-settings/store', () => ({
54
- useUiPageSettingsStore: vi.fn(() => mockPageSettingsStore),
55
- }));
56
-
57
39
  // Mutate the mock between captures so each capture produces a distinct
58
40
  // snapshot. The store now drops byte-identical captures (BP-5506: prevents
59
41
  // undo/redo steps that do nothing visible), so tests that previously relied
@@ -86,8 +68,6 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
86
68
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
69
  (mockMakerStore.brandPageData as any).v = 1;
88
70
  mockPageNavigationStore.activePageId = '';
89
- mockShopConfigStore.paymentConfig = undefined;
90
- mockPageSettingsStore.pages = [];
91
71
  });
92
72
 
93
73
  it('captures baseline and initialises the pointer to 0', async () => {
@@ -187,6 +167,35 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
187
167
  expect(store.currentIndex).toBe(1);
188
168
  });
189
169
 
170
+ it('captures pre-purchase logo edits even when brandPageData is byte-identical', async () => {
171
+ // Pre-purchase logo-tab edits (text/color/font) live on
172
+ // `editableLogoStore.logoEdits` (sidecar) and the rendered base64 is
173
+ // stripped from snapshots to stay under the 25 MB cap. Two
174
+ // consecutive logo edits therefore produce identical snapshot bytes
175
+ // — without comparing the sidecar, every logo edit would dedup as
176
+ // a no-op and history would silently stop growing.
177
+ const editableLogoStore = useUiEditableLogoStore();
178
+
179
+ editableLogoStore.logoEdits.textObjects = [
180
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
181
+ { id: 't1', role: 'primary-text', text: 'Acme' } as any,
182
+ ];
183
+
184
+ const store = useHistoryStore();
185
+
186
+ await store.captureBaseline();
187
+ expect(store.entries.length).toBe(1);
188
+
189
+ // User changes the logo text — only the sidecar mutates. Snapshot
190
+ // bytes are identical (no brandPageData diff). Must still commit.
191
+ editableLogoStore.logoEdits.textObjects[0].text = 'AcmeCo';
192
+
193
+ const r = store.capture({ source: 'manual_edit', actor: 'user' });
194
+
195
+ expect(r).toBe('committed');
196
+ expect(store.entries.length).toBe(2);
197
+ });
198
+
190
199
  // BP-5506 phantom-undo prevention via the `mergeNextMutation()` flag.
191
200
  // Hydration write sites (TipTap mount, lazy paymentConfig fill,
192
201
  // stale-page-ref clear) call this immediately before the imperative
@@ -402,121 +411,6 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
402
411
  });
403
412
  });
404
413
 
405
- // BP-5506 follow-up: paymentConfig is the user's Stripe Connect linkage
406
- // (backend-confirmed, per project guardrails). It must be invariant
407
- // under undo/redo regardless of what other edits the user makes — the
408
- // restore-time re-stamp in `applySnapshotString` re-derives every live
409
- // wrapper's `paymentConfig` from `shopConfigStore.paymentConfig` after
410
- // each undo / redo apply. Snapshot bytes themselves can carry whatever
411
- // value `brandPageData` happened to have at capture time (including
412
- // undefined for sections the user never opened); the re-stamp is what
413
- // makes the contract hold.
414
- describe('paymentConfig invariance under undo/redo', () => {
415
- it('re-stamps wrapper.paymentConfig from shopStore on undo', async () => {
416
- const store = useHistoryStore();
417
-
418
- // Live shop store has the user's Stripe linkage.
419
- mockShopConfigStore.paymentConfig = {
420
- id: 'live-stripe',
421
- name: 'Stripe',
422
- status: 'Active',
423
- };
424
-
425
- // Live page settings has a tipping section whose wrapper holds
426
- // paymentConfig = undefined (e.g. the user has just undone past the
427
- // baseline and `applyHistorySnapshot` rebuilt content from a
428
- // paymentConfig-free snapshot).
429
- mockPageSettingsStore.pages = [
430
- {
431
- contents: [
432
- {
433
- settings: {
434
- content: {
435
- type: 'support-me',
436
- supportMeConfig: {
437
- paymentOptions: [],
438
- paymentConfig: undefined,
439
- },
440
- },
441
- },
442
- },
443
- ],
444
- },
445
- ];
446
-
447
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
448
- (mockMakerStore.brandPageData as any).pages = [
449
- {
450
- id: 'p',
451
- contents: [{ type: 'support-me', supportMeConfig: {} }],
452
- },
453
- ];
454
- await store.captureBaseline();
455
- captureMutated(store);
456
-
457
- // Undo back to baseline. The mock applyHistorySnapshot is a no-op,
458
- // but the restore-side re-stamp inside `applySnapshotString` should
459
- // still run after it resolves.
460
- await store.undo();
461
-
462
- // Live wrapper now reflects shopStore.paymentConfig.
463
- const wrapper =
464
- mockPageSettingsStore.pages[0].contents[0].settings.content
465
- .supportMeConfig;
466
-
467
- expect((wrapper as { paymentConfig: unknown }).paymentConfig).toEqual({
468
- id: 'live-stripe',
469
- name: 'Stripe',
470
- status: 'Active',
471
- });
472
- });
473
-
474
- it('re-stamps to undefined when shopStore has no paymentConfig', async () => {
475
- const store = useHistoryStore();
476
-
477
- // No Stripe linkage in the shop store.
478
- mockShopConfigStore.paymentConfig = undefined;
479
-
480
- mockPageSettingsStore.pages = [
481
- {
482
- contents: [
483
- {
484
- settings: {
485
- content: {
486
- type: 'support-me',
487
- supportMeConfig: {
488
- paymentConfig: { id: 'stale', name: 'Stripe' },
489
- },
490
- },
491
- },
492
- },
493
- ],
494
- },
495
- ];
496
-
497
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
498
- (mockMakerStore.brandPageData as any).pages = [
499
- {
500
- id: 'p',
501
- contents: [{ type: 'support-me', supportMeConfig: {} }],
502
- },
503
- ];
504
- await store.captureBaseline();
505
- captureMutated(store);
506
-
507
- await store.undo();
508
-
509
- const wrapper =
510
- mockPageSettingsStore.pages[0].contents[0].settings.content
511
- .supportMeConfig;
512
-
513
- // Stale value cleared; live wrapper now matches shopStore (undefined).
514
- expect(
515
- (wrapper as { paymentConfig: unknown }).paymentConfig,
516
- ).toBeUndefined();
517
- });
518
- });
519
-
520
414
  it('reentrancy guard: capture no-ops when isApplyingSnapshot is true', async () => {
521
415
  const store = useHistoryStore();
522
416