@designcrowd/library.brand-page 5.18.3-button-fix-1 → 5.18.3-undo-35

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 (112) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DQLtpdLe.js → CreatedOnBrandCrowd-de-de-nGPiEkaH.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CvEhlADd.js → CreatedOnBrandCrowd-en-us-D3ibxOrN.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-pYQeTTC0.js → CreatedOnBrandCrowd-es-es-BAq0PV5c.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CcE-iCEq.js → CreatedOnBrandCrowd-fr-ca-9foHt2a-.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CcE-iCEq.js → CreatedOnBrandCrowd-fr-fr-9foHt2a-.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-OyhWWWyO.js → CreatedOnBrandCrowd-pt-br-DO739Uxl.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-OyhWWWyO.js → CreatedOnBrandCrowd-pt-pt-DO739Uxl.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DH98FFLS.js → CreatedOnDcom-de-de-CFbNW8tl.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DK67NN1r.js → CreatedOnDcom-en-us-BL34V8eu.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-9-QU4BU6.js → CreatedOnDcom-es-es-BKXWvHkV.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-U9J9H9VB.js → CreatedOnDcom-fr-ca-DCyU-m6J.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DqdzB1DJ.js → CreatedOnDcom-fr-fr-DEciA_zh.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-c4UiWtkB.js → CreatedOnDcom-pt-br-BVB2zdvU.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-Do2qJsYp.js → CreatedOnDcom-pt-pt-DatUuIK6.js} +1 -1
  15. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +7 -2
  16. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +7 -2
  17. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +10 -0
  18. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +7 -2
  19. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +1 -0
  20. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +23 -2
  21. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +7 -2
  22. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +3 -0
  23. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.js +11 -0
  24. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +7 -2
  25. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +7 -2
  26. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +7 -2
  27. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +7 -2
  28. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +9 -0
  29. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +20 -1
  30. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +20 -0
  31. package/lib/src/brand-page-maker/composables/useEventTracker.js +17 -0
  32. package/lib/src/brand-page-maker/composables/useUndoRedoFlag.d.ts +16 -0
  33. package/lib/src/brand-page-maker/composables/useUndoRedoFlag.js +22 -0
  34. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +41 -2
  35. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +307 -5
  36. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +83 -0
  37. package/lib/src/brand-page-maker/stores/history/history-store.js +488 -0
  38. package/lib/src/brand-page-maker/stores/logo-store/logo-store.d.ts +1 -0
  39. package/lib/src/brand-page-maker/stores/user/user-store.js +1 -0
  40. package/lib/src/bundled-translations-D1q3rSEz.js +175 -0
  41. package/lib/src/bundles/bundled-translations.json +3 -1
  42. package/lib/src/constants/ui.d.ts +1 -0
  43. package/lib/src/css/library.brand-page-brandCrowd.min.css +50 -54
  44. package/lib/src/css/library.brand-page-brandPage.min.css +50 -55
  45. package/lib/src/css/library.brand-page-designCom.min.css +50 -54
  46. package/lib/src/css/library.brand-page-preview.min.css +50 -54
  47. package/lib/src/index.mjs +1 -1
  48. package/lib/src/{lib-BI3J4Mbk.js → lib-C_Pc-5bF.js} +34307 -33712
  49. package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +13 -0
  50. package/lib/src/metadata-builder/builders/reorder-pages-builder.js +49 -0
  51. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +87 -2
  52. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +5 -0
  53. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.stories.js +1 -0
  54. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +23 -2
  55. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +16 -0
  56. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +7 -2
  57. package/lib/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.d.ts +3 -0
  58. package/lib/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.js +19 -2
  59. package/lib/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.js +18 -8
  60. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +64 -0
  61. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.js +6 -0
  62. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +19 -0
  63. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.js +11 -0
  64. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +10 -2
  65. package/lib/src/shared/logo-settings/store/editable-logo-store.d.ts +5 -1
  66. package/lib/src/shared/logo-settings/store/editable-logo-store.js +31 -0
  67. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +24 -2
  68. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +10 -2
  69. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +23 -2
  70. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +16 -0
  71. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +23 -2
  72. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +16 -0
  73. package/package.json +1 -1
  74. package/src/brand-page/components/button/Button.vue +2 -10
  75. package/src/brand-page/components/contact-form/ContactForm.vue +2 -2
  76. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +11 -0
  77. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +2 -3
  78. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +1 -0
  79. package/src/brand-page-maker/components/cookie-collection/CookieSettings.vue +18 -17
  80. package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.ts +13 -0
  81. package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.vue +26 -3
  82. package/src/brand-page-maker/components/quick-link-bar/RedoButton.vue +66 -0
  83. package/src/brand-page-maker/components/quick-link-bar/UndoButton.vue +66 -0
  84. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.json +3 -1
  85. package/src/brand-page-maker/components/quick-link-bar/undo-redo-buttons.test.ts +116 -0
  86. package/src/brand-page-maker/components/save-contact/SaveContact.vue +12 -19
  87. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +10 -0
  88. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +24 -1
  89. package/src/brand-page-maker/composables/useEventTracker.ts +29 -0
  90. package/src/brand-page-maker/composables/useUndoRedoFlag.ts +26 -0
  91. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +335 -0
  92. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +371 -4
  93. package/src/brand-page-maker/stores/history/history-store.test.ts +1120 -0
  94. package/src/brand-page-maker/stores/history/history-store.ts +651 -0
  95. package/src/brand-page-maker/stores/logo-store/logo-store.ts +5 -0
  96. package/src/brand-page-maker/stores/user/user-store.ts +1 -0
  97. package/src/bundles/bundled-translations.json +3 -1
  98. package/src/constants/ui.ts +1 -0
  99. package/src/metadata-builder/builders/reorder-pages-builder.ts +80 -0
  100. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +5 -0
  101. package/src/shared/content-settings/components/content-settings/ContentSettings.stories.ts +1 -0
  102. package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.ts +19 -2
  103. package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.vue +1 -1
  104. package/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.ts +26 -10
  105. package/src/shared/content-settings/components/logo-selection-field/LogoSelection.vue +63 -48
  106. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +4 -0
  107. package/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.ts +7 -0
  108. package/src/shared/content-settings/components/text-field/TextField.vue +1 -0
  109. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +33 -4
  110. package/src/shared/logo-settings/components/EditLogoSettings.mixin.ts +11 -0
  111. package/src/shared/logo-settings/store/editable-logo-store.ts +42 -1
  112. package/lib/src/bundled-translations-B5Fkt3qd.js +0 -175
@@ -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 {
@@ -60,6 +61,7 @@ import {
60
61
  BRANDPAGE_DEFAULT_SAVE_MESSAGE_DESCRIPTION,
61
62
  MAKER_MOBILE_BREAKPOINT,
62
63
  MESSAGE_VISIBLE_DURATION,
64
+ SIDE_BAR_TAB_NAMES,
63
65
  } from '@brand-page-maker/constants';
64
66
  import { useUiSideBarStore } from '@shared/side-bar/store';
65
67
  import { delayInMS } from '../../../utils/ui-util';
@@ -104,6 +106,7 @@ import { useConsent } from '@brand-page/composables/consent.composable';
104
106
  import { commonTr, errorTr } from '@src/composables/useTranslate';
105
107
  import { UPGRADE_CTA_LOCATIONS } from '../../composables/useEventTracker';
106
108
  import { useVideoStore } from '../video';
109
+ import { useHistoryStore } from '../history/history-store';
107
110
  import { findValuesByKey } from '../../../helpers/ts.helper';
108
111
  import { useLogoStore } from '../logo-store';
109
112
 
@@ -668,6 +671,16 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
668
671
  description: payload.noDescription ? '' : description,
669
672
  },
670
673
  });
674
+
675
+ // Any successful save re-baselines the undo stack so a user can't
676
+ // undo back past the persisted state. This covers Save Progress /
677
+ // Publish AND settings-only saves (updateBrandPage,
678
+ // updateSensitiveContent, admin saveTemplate) — settings-tab
679
+ // actions persist the metadata, so allowing undo to roll them
680
+ // back would let the local stack drift from what's saved.
681
+ // reset() no-ops in non-undo-redo sessions (no baseline = nothing
682
+ // to clear).
683
+ useHistoryStore().reset();
671
684
  },
672
685
  hideChangeTemplateConfirmModal() {
673
686
  this.setIsChangeTemplateConfirmModalVisible({
@@ -681,10 +694,11 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
681
694
  this.setSelectedTemplateId({ templateId: false });
682
695
  this.hideChangeTemplateConfirmModal();
683
696
  },
684
- onChangeTemplateConfirmModalConfirm() {
685
- this.applySelectedTemplate();
697
+ async onChangeTemplateConfirmModalConfirm() {
698
+ await this.applySelectedTemplate();
686
699
  this.hideChangeTemplateConfirmModal();
687
700
  this.isModifiedAfterTemplateApplied = false;
701
+ this._captureTemplateSwitchIfEnabled();
688
702
  },
689
703
  onContentChanged() {
690
704
  this.setIsModifiedAfterTemplateApplied();
@@ -692,7 +706,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
692
706
  onStyleChanged(): void {
693
707
  this.setIsModifiedAfterTemplateApplied();
694
708
  },
695
- onTemplateSelected(payload: OnTemplateSelectedPayload) {
709
+ async onTemplateSelected(payload: OnTemplateSelectedPayload) {
696
710
  this.setSelectedTemplateId({
697
711
  templateId: payload.templateId,
698
712
  });
@@ -702,7 +716,8 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
702
716
  isVisible: true,
703
717
  });
704
718
  } else {
705
- this.applySelectedTemplate();
719
+ await this.applySelectedTemplate();
720
+ this._captureTemplateSwitchIfEnabled();
706
721
  }
707
722
  },
708
723
  setSelectedTabName(payload) {
@@ -739,6 +754,358 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
739
754
  const uiContentSettingsStore = useUiContentSettingsStore();
740
755
 
741
756
  uiContentSettingsStore.updateContent<TContent>(payload);
757
+
758
+ // NOTE (Bolt 1, 2026-04-23): this action is not the actual edit path used
759
+ // by the UI — sidebar inputs call uiContentSettingsStore.updateItemValue
760
+ // directly. History capture is driven by a deep `watch(brandPageData, ...)`
761
+ // installed in history-store once the baseline is captured, so we do not
762
+ // need to instrument this action. Kept here as a pass-through because
763
+ // removing it may affect other callers.
764
+ },
765
+ /**
766
+ * Restore every store that the `brandPageData` getter reads from, so undo
767
+ * and redo bring back the EXACT document state captured at snapshot time.
768
+ *
769
+ * `initialiseStoresByBrandPageModelAsync` is too narrow for undo/redo — it
770
+ * only rehydrates pages, sections, and nav structure, which is enough for
771
+ * the AI-apply path but leaves navigation styles, backgrounds, button/text/
772
+ * logo/gallery styles, shop config, and several own-store fields unchanged.
773
+ * This action mirrors the full hydration surface of `applySelectedTemplate`
774
+ * (minus the template-specific logo/personalisation logic) so a restore is
775
+ * a complete round-trip of what `brandPageData` returns.
776
+ *
777
+ * Called ONLY by history-store during undo/redo. The history store sets
778
+ * `isApplyingSnapshot=true` across this call to prevent the deep-watcher
779
+ * on `brandPageData` from re-capturing the restored state.
780
+ */
781
+ /**
782
+ * Force-commit a `template_switch` entry to the history store, if the
783
+ * history store has a baseline (which only happens when the undo/redo
784
+ * feature flag is on — see `BrandPageMaker.mixin.ts`).
785
+ */
786
+ _captureTemplateSwitchIfEnabled(): void {
787
+ const historyStore = useHistoryStore();
788
+
789
+ if (!historyStore.baselineCaptured) return;
790
+
791
+ historyStore.captureTemplateSwitch();
792
+ },
793
+ async applyHistorySnapshot(
794
+ snapshot: BrandPageModel,
795
+ activePageId?: string,
796
+ logoEditsStr?: string,
797
+ ): Promise<void> {
798
+ const styleSettingsStore = useUiStyleSettingsStore();
799
+ const navigationStyleSettingsStore = useUiNavigationStyleSettingsStore();
800
+ const backgroundSettingsStore = useUiBackgroundSettingsStore();
801
+ const pageNavigationStore = usePageNavigationStore();
802
+ const shopStore = useShopConfigStore();
803
+
804
+ // 1. Pages + header/footer sections (also covers content items).
805
+ this.initialisePagesForCurrentPage({
806
+ pages: snapshot.pages,
807
+ sections: {
808
+ [PAGE_SECTIONS.header]: {
809
+ [VIEWPORTS.desktop]: snapshot.pageHeaderDesktop,
810
+ [VIEWPORTS.mobile]: snapshot.pageHeaderMobile,
811
+ },
812
+ [PAGE_SECTIONS.footer]: {
813
+ [VIEWPORTS.desktop]: snapshot.pageFooterDesktop,
814
+ [VIEWPORTS.mobile]: snapshot.pageFooterMobile,
815
+ },
816
+ },
817
+ });
818
+
819
+ // 2. Page-level nav structure (routes, orders).
820
+ pageNavigationStore.setNav({ nav: snapshot.nav });
821
+
822
+ // 3. Navigation STYLES (header/footer nav colors, alignment, etc.) —
823
+ // this is the bit that was being dropped before.
824
+ // Use function-form $patch so each nested style object is replaced as a
825
+ // whole reference. The object-form $patch (used by setState) deep-merges
826
+ // and would leave keys present in the live state but absent from the
827
+ // snapshot in place — e.g. switching button styles then undoing would
828
+ // leave the new style's borderRadius/borderStyle/borderWidth applied.
829
+ navigationStyleSettingsStore.$patch((state) => {
830
+ state.navigation[PAGE_SECTIONS.header][VIEWPORTS.desktop] =
831
+ new NavigationDesktop(snapshot.pageHeaderDesktop?.navigation);
832
+ state.navigation[PAGE_SECTIONS.header][VIEWPORTS.mobile] =
833
+ new NavigationMobile(snapshot.pageHeaderMobile?.navigation);
834
+ });
835
+
836
+ // 4. Global styles (logo, button, text, gallery). Same whole-reference
837
+ // replacement reasoning as step 3.
838
+ styleSettingsStore.$patch((state) => {
839
+ state.buttonStyles = snapshot.styles.buttonStyles;
840
+ state.logoStyles = snapshot.styles.logoStyles;
841
+ state.textStyles = snapshot.styles.textStyles;
842
+ state.galleryStyles = snapshot.styles.galleryStyles;
843
+ });
844
+
845
+ // 5. Background styles. Same whole-reference replacement reasoning as
846
+ // step 3 — `setBackgroundStyles` calls per-field setters that
847
+ // `Object.assign` partials onto the live `style` and
848
+ // `backgroundShapeStyle` objects, so keys from the previous mode
849
+ // (e.g. background image url, shape style props) would survive an
850
+ // undo back to a state that doesn't have them.
851
+ backgroundSettingsStore.$patch((state) => {
852
+ state.backgroundStyles = snapshot.styles.backgroundStyles;
853
+ });
854
+ backgroundSettingsStore.onBackgroundChanged();
855
+
856
+ // 6. Shop products are derived from shop-content blocks inside `pages`,
857
+ // so they're already implicitly restored at step 1. Re-run the shop
858
+ // store's derivation to keep `products` in sync with the snapshot
859
+ // pages. paymentConfig is intentionally NOT restored — selecting it is
860
+ // backend-confirmed (Stripe Connect / payment-provider linkage) per
861
+ // project-guardrails.md, so it must persist across undo. We pass the
862
+ // live paymentConfig back in to preserve it.
863
+ shopStore.initialiseShopStore({
864
+ paymentConfig: shopStore.paymentConfig,
865
+ pages: snapshot.pages ?? [],
866
+ });
867
+
868
+ // 7. Own-store scalar fields surfaced by the brandPageData getter.
869
+ if (snapshot.isModifiedAfterTemplateApplied !== undefined) {
870
+ this.isModifiedAfterTemplateApplied =
871
+ snapshot.isModifiedAfterTemplateApplied;
872
+ }
873
+
874
+ if (snapshot.brandPageType) {
875
+ this.setBrandPageType({ brandPageType: snapshot.brandPageType });
876
+ }
877
+
878
+ this.faviconUrl = snapshot.faviconUrl;
879
+ this.ogImageUrl = snapshot.ogImageUrl;
880
+ this.measurementID = snapshot.measurementID;
881
+ this.adsenseID = snapshot.adsenseID;
882
+ this.ogTitle = snapshot.ogTitle;
883
+ this.ogDescription = snapshot.ogDescription;
884
+ this.cookieOptions = snapshot.cookieOptions;
885
+ this.saveContactSettings = snapshot.saveContactSettings;
886
+
887
+ // 8. Re-sync the logo-settings sidebar's `selectedLogoImage` from the
888
+ // restored header logo. The preview rehydrates via the page contents,
889
+ // but `uiLogoSettings` holds its own copy that drives the sidebar
890
+ // "current logo" state — same pattern as `initialiseStoresByBrandPageModelAsync`.
891
+ // Also restores the variations grid + template asset for the
892
+ // snapshot's draft token (both are sidecars to `brandPageData` —
893
+ // BYO upload mutates them out of band). When the captured entry
894
+ // carries a `logoEdits` sidecar (pre-purchase logo-tab edits —
895
+ // text, support text, logo colors, fonts), `editableLogoStore.logoEdits`
896
+ // is restored from that string so the sidebar form fields and the
897
+ // save-time fabric-render POST both pick up the pre-edit state.
898
+ await this._syncLogoSettingsFromSnapshot(snapshot, logoEditsStr);
899
+
900
+ // 9. If the sidebar is editing a content item that no longer exists in
901
+ // the restored page, fall back to the Content tab so the user doesn't
902
+ // stare at a blank settings panel.
903
+ this._fallbackSidebarIfSelectedContentMissing(snapshot);
904
+
905
+ // 10. Switch to the page the user was viewing when this entry was
906
+ // captured (or the closest still-existing page if the captured
907
+ // page was the one being undone-away). The history store does the
908
+ // walk-back resolution and passes us a pageId that's guaranteed to
909
+ // exist in the restored nav — or undefined if no entry walking
910
+ // back has a viable id, in which case we fall through to the
911
+ // first/default route. Without this, the page-tabs carousel keeps
912
+ // highlighting whatever page slug is in the URL (which can no
913
+ // longer match any nav item after a page-add undo) and the
914
+ // sidebar's `currentPage` getter silently falls back to page 0.
915
+ this._navigateToActivePage(activePageId);
916
+ },
917
+
918
+ async _syncLogoSettingsFromSnapshot(
919
+ snapshot: BrandPageModel,
920
+ logoEditsStr?: string,
921
+ ): Promise<void> {
922
+ const logoSettingsStore = useUiLogoSettingsStore();
923
+ const editableLogoStore = useUiEditableLogoStore();
924
+ const logoStore = useLogoStore();
925
+ const containerStore = useBrandPageContainerStore();
926
+
927
+ // Pre-restore logo identity, used to short-circuit the
928
+ // fabric-render call when only non-logo state changed.
929
+ const prevLogoEditsStr = JSON.stringify(
930
+ toRaw(editableLogoStore.logoEdits),
931
+ );
932
+ const prevDraftToken = editableLogoStore.logoDraftToken;
933
+
934
+ // Wholesale-replace via $patch so prior textObjects/colors don't
935
+ // leak across template switches.
936
+ if (logoEditsStr) {
937
+ try {
938
+ const parsed = JSON.parse(logoEditsStr);
939
+
940
+ editableLogoStore.$patch({ logoEdits: parsed });
941
+ } catch (error) {
942
+ console.error(
943
+ 'brandpage-maker-store: failed to restore logoEdits',
944
+ error,
945
+ );
946
+ }
947
+ }
948
+
949
+ const logoImage =
950
+ snapshot.brandPageType === BRAND_PAGE_TYPES.default
951
+ ? (
952
+ snapshot.pages?.[0]?.contents?.find(
953
+ (c) => c.type === 'logo',
954
+ ) as any
955
+ )?.logoImage
956
+ : snapshot.pageHeaderDesktop?.logoImage;
957
+
958
+ if (!logoImage) return;
959
+
960
+ logoSettingsStore.setState({
961
+ newState: { selectedLogoImage: logoImage },
962
+ });
963
+
964
+ const draftToken: string | undefined = logoImage.logoDraftToken;
965
+
966
+ if (!draftToken) return;
967
+
968
+ // Re-derive enableEditMode + templateAsset from the draft registry
969
+ // (append-only for the session) instead of stashing on every entry.
970
+ const draft = logoStore.getLogoDraftAssetByToken(draftToken);
971
+
972
+ if (draft) {
973
+ editableLogoStore.setEnableEditMode(!draft.isByo);
974
+
975
+ if (draft.templateAsset) {
976
+ logoStore.setLogoTemplateAsset(draft.templateAsset);
977
+ }
978
+ }
979
+
980
+ editableLogoStore.setLogoDraftToken(draftToken);
981
+
982
+ // No logo change → cache is still correct; just stamp base64 back
983
+ // (snapshot doesn't carry logoImageBase64Url).
984
+ const logoUnchanged =
985
+ prevDraftToken === draftToken &&
986
+ (logoEditsStr === undefined || logoEditsStr === prevLogoEditsStr);
987
+
988
+ if (logoUnchanged) {
989
+ editableLogoStore.stampLogoBase64IntoBrandPageData();
990
+
991
+ return;
992
+ }
993
+
994
+ const isByo = !!draft?.isByo;
995
+
996
+ if (isByo) {
997
+ // BYO mints a fresh draftToken per upload so the per-token
998
+ // cache always matches; no re-render needed.
999
+ if (editableLogoStore.restoreVariationsForDraftToken(draftToken)) {
1000
+ editableLogoStore.stampLogoBase64IntoBrandPageData();
1001
+ }
1002
+
1003
+ return;
1004
+ }
1005
+
1006
+ // Designed: draft token is reused across edits, so the cache
1007
+ // only holds the latest render. Re-issue fabric-render — same
1008
+ // call as the live edit path. Awaited so concurrent undos
1009
+ // serialize and the call sits inside the isApplyingSnapshot
1010
+ // window (watcher guard suppresses the writeback).
1011
+ if (
1012
+ containerStore.isPrePurchase &&
1013
+ logoEditsStr &&
1014
+ logoStore.logoTemplateAsset
1015
+ ) {
1016
+ // Stamp the cached (pre-undo) base64 onto brandPageData first
1017
+ // so the canvas keeps showing the previous logo while the
1018
+ // re-render is in flight. Without this the logoImageBase64Url
1019
+ // fields are undefined (snapshot is stripped pre-purchase) and
1020
+ // the canvas flickers blank for ~500-1500ms.
1021
+ editableLogoStore.stampLogoBase64IntoBrandPageData();
1022
+
1023
+ try {
1024
+ await editableLogoStore.updateEditableLogoImageUrlAsync();
1025
+ } catch (error) {
1026
+ console.error(
1027
+ 'brandpage-maker-store: failed to re-render logo on history apply',
1028
+ error,
1029
+ );
1030
+ }
1031
+ }
1032
+ },
1033
+
1034
+ /**
1035
+ * Restore the active page after a history apply. The id passed in is
1036
+ * already validated against the restored nav by the history store; if
1037
+ * it's undefined (no entry walking back had a viable page), fall back
1038
+ * to the first/default route so the URL, `routeStore.currentRoute.params`,
1039
+ * `pageNavigation.activePageId`, and the page-tab highlight all line up.
1040
+ *
1041
+ * `setCurrentActivePage` is called explicitly in addition to
1042
+ * `navigateToPageByPageId` because `navigateToPageByPageId` is a no-op
1043
+ * when the router instance is undefined (e.g. SSR / unit tests), but the
1044
+ * Pinia state must still be updated so the sidebar's `currentPage`
1045
+ * getter returns the right page.
1046
+ */
1047
+ _navigateToActivePage(activePageId?: string): void {
1048
+ const pageNavigationStore = usePageNavigationStore();
1049
+
1050
+ let targetPageId = activePageId;
1051
+
1052
+ if (!targetPageId) {
1053
+ const fallbackRoute = pageNavigationStore.routes.length
1054
+ ? pageNavigationStore.getFirstOrDefaultRoute()
1055
+ : undefined;
1056
+
1057
+ targetPageId = fallbackRoute?.pageId;
1058
+ }
1059
+
1060
+ if (!targetPageId) return;
1061
+
1062
+ // Skip both the Pinia write and the router push when the target page
1063
+ // is already active. The router push fires `router.afterEach` even
1064
+ // for same-page navigation, and BrandPageMaker.mixin's afterEach hook
1065
+ // resets the content-settings modeStack — which would snap the
1066
+ // sidebar back to the default Content tab on every undo/redo,
1067
+ // regardless of whether the user was deep-editing a content item.
1068
+ if (pageNavigationStore.activePageId === targetPageId) return;
1069
+
1070
+ pageNavigationStore.setCurrentActivePage({ pageId: targetPageId });
1071
+ navigateToPageByPageId(targetPageId);
1072
+ },
1073
+
1074
+ _fallbackSidebarIfSelectedContentMissing(snapshot: BrandPageModel): void {
1075
+ const contentSettingsStore = useUiContentSettingsStore();
1076
+ const sideBarStore = useUiSideBarStore();
1077
+
1078
+ const selectedItemId = contentSettingsStore.selectedItemId;
1079
+
1080
+ if (!selectedItemId) return;
1081
+
1082
+ const existingContentIds = new Set<string>();
1083
+
1084
+ snapshot.pages?.forEach((page) => {
1085
+ page.contents?.forEach((content) => {
1086
+ if (content?.id) existingContentIds.add(content.id);
1087
+ });
1088
+ });
1089
+
1090
+ if (existingContentIds.has(selectedItemId)) return;
1091
+
1092
+ // The previously-selected content was undone away. Restore the
1093
+ // Content tab's natural landing state — the same shape produced
1094
+ // when the user clicks the Content tab fresh — instead of leaving
1095
+ // the sidebar stuck rendering `<FocusedContentItemSettings>` for
1096
+ // a missing item, which paints a blank panel.
1097
+ //
1098
+ // `resetMode()` pops the modeStack back to the base `edit` entry
1099
+ // (the default landing mode); clearing `selectedItemId` releases
1100
+ // the pointer to the now-deleted item.
1101
+ sideBarStore.setSelectedTabName({
1102
+ tabName: SIDE_BAR_TAB_NAMES.CONTENT_SETTINGS,
1103
+ });
1104
+ contentSettingsStore.resetMode();
1105
+ contentSettingsStore.selectedItemId = null;
1106
+ contentSettingsStore.selectedItemFieldName = null;
1107
+ contentSettingsStore.selectedItemIndex = null;
1108
+ contentSettingsStore.selectedItemValue = null;
742
1109
  },
743
1110
  export(): BrandPageModel {
744
1111
  const data = this.brandPageData;