@designcrowd/library.brand-page 6.0.6-undo-6 → 6.0.6-undo-8

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 (28) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-BfSQG4or.js → CreatedOnBrandCrowd-de-de-DdT7VIEh.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CO07UsMZ.js → CreatedOnBrandCrowd-en-us-YSkiGVKr.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BElFyN72.js → CreatedOnBrandCrowd-es-es-1EPcCaOy.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DnTxsYIb.js → CreatedOnBrandCrowd-fr-ca-C8fnEHlw.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DnTxsYIb.js → CreatedOnBrandCrowd-fr-fr-C8fnEHlw.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DSMhWz5Z.js → CreatedOnBrandCrowd-pt-br-Dl3Se_ur.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DSMhWz5Z.js → CreatedOnBrandCrowd-pt-pt-Dl3Se_ur.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-C2469ycC.js → CreatedOnDcom-de-de-rXbL8YwY.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-Cvz7kq6J.js → CreatedOnDcom-en-us-Bm9CAhKw.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-BvDTs1Y_.js → CreatedOnDcom-es-es-B8diEgO8.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-vJ9zwwPI.js → CreatedOnDcom-fr-ca-b6jqk1vU.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-D90gm3ay.js → CreatedOnDcom-fr-fr-ECBv8SyF.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-C1VGGfTF.js → CreatedOnDcom-pt-br-C7THecsn.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DGL1py4g.js → CreatedOnDcom-pt-pt-C7f06hrr.js} +1 -1
  15. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +65 -54
  16. package/lib/src/css/library.brand-page-brandCrowd.min.css +13 -13
  17. package/lib/src/css/library.brand-page-brandPage.min.css +13 -13
  18. package/lib/src/css/library.brand-page-designCom.min.css +13 -13
  19. package/lib/src/css/library.brand-page-preview.min.css +13 -13
  20. package/lib/src/index.mjs +1 -1
  21. package/lib/src/{lib-nGjOXase.js → lib-DQvE17ni.js} +612 -602
  22. package/package.json +1 -1
  23. package/src/brand-page/components/shop/Shop.vue +0 -1
  24. package/src/brand-page-maker/components/quick-link-bar/RedoButton.vue +9 -1
  25. package/src/brand-page-maker/components/quick-link-bar/UndoButton.vue +9 -1
  26. package/src/brand-page-maker/components/quick-link-bar/undo-redo-buttons.test.ts +29 -2
  27. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +130 -0
  28. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +71 -62
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": "6.0.6-undo-6",
4
+ "version": "6.0.6-undo-8",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -4,7 +4,6 @@
4
4
  v-if="shouldShowContainer"
5
5
  class="tw-relative outer-container"
6
6
  :class="{
7
- 'tw-my-8': context.isBrandSite,
8
7
  'tw-mb-4': context.isBrandPageOrIsBrandContact,
9
8
  }"
10
9
  :style="[backgroundStyle, contentPaddingStyle]"
@@ -39,17 +39,25 @@ import { Icon, Tooltip } from '@designcrowd/fe-shared-lib';
39
39
  import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
40
40
  import { useAiWebsiteBuilderThreadStore } from '@brand-page-maker/stores/ai-thread/ai-website-builder-thread-store';
41
41
  import { useBrandPageContainerStore } from '@brand-page-maker/stores/brandpage-container';
42
+ import { useUiTemplateSettingsStore } from '@shared/template-settings/store';
42
43
  import { quickLinkBarTr } from '@src/composables/useTranslate';
43
44
 
44
45
  const historyStore = useHistoryStore();
45
46
  const aiThreadStore = useAiWebsiteBuilderThreadStore();
46
47
  const containerStore = useBrandPageContainerStore();
48
+ const templateSettingsStore = useUiTemplateSettingsStore();
47
49
 
48
50
  const isDisabled = computed(
49
51
  () =>
50
52
  !historyStore.canRedo ||
51
53
  aiThreadStore.isProcessing ||
52
- containerStore.isSaving,
54
+ containerStore.isSaving ||
55
+ // Locked while a restore is applying (the apply awaits the designed-logo
56
+ // re-render) or the template previews are refetching: re-entering
57
+ // mid-regeneration overlapped two reloads and left the sidebar keyed to
58
+ // the wrong logo (fast undo→redo desync).
59
+ historyStore.isApplyingSnapshot ||
60
+ templateSettingsStore.isBusy,
53
61
  );
54
62
 
55
63
  const onClick = () => {
@@ -39,17 +39,25 @@ import { Icon, Tooltip } from '@designcrowd/fe-shared-lib';
39
39
  import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
40
40
  import { useAiWebsiteBuilderThreadStore } from '@brand-page-maker/stores/ai-thread/ai-website-builder-thread-store';
41
41
  import { useBrandPageContainerStore } from '@brand-page-maker/stores/brandpage-container';
42
+ import { useUiTemplateSettingsStore } from '@shared/template-settings/store';
42
43
  import { quickLinkBarTr } from '@src/composables/useTranslate';
43
44
 
44
45
  const historyStore = useHistoryStore();
45
46
  const aiThreadStore = useAiWebsiteBuilderThreadStore();
46
47
  const containerStore = useBrandPageContainerStore();
48
+ const templateSettingsStore = useUiTemplateSettingsStore();
47
49
 
48
50
  const isDisabled = computed(
49
51
  () =>
50
52
  !historyStore.canUndo ||
51
53
  aiThreadStore.isProcessing ||
52
- containerStore.isSaving,
54
+ containerStore.isSaving ||
55
+ // Locked while a restore is applying (the apply awaits the designed-logo
56
+ // re-render) or the template previews are refetching: re-entering
57
+ // mid-regeneration overlapped two reloads and left the sidebar keyed to
58
+ // the wrong logo (fast undo→redo desync).
59
+ historyStore.isApplyingSnapshot ||
60
+ templateSettingsStore.isBusy,
53
61
  );
54
62
 
55
63
  const onClick = () => {
@@ -6,8 +6,10 @@ import RedoButton from './RedoButton.vue';
6
6
 
7
7
  // These tests cover the click-dispatch contract for the undo/redo buttons:
8
8
  // - Click → historyStore.undo()/redo() ONLY when enabled.
9
- // - Disabled when any of three guards is true: canUndo/canRedo,
10
- // aiThread.isProcessing, container.isSaving.
9
+ // - Disabled when any of five guards is true: canUndo/canRedo,
10
+ // aiThread.isProcessing, container.isSaving, history.isApplyingSnapshot
11
+ // (a restore is applying — spans the awaited logo re-render), or
12
+ // templateSettings.isBusy (template previews are refetching).
11
13
  //
12
14
  // Markup, class names, and i18n strings are intentionally not asserted —
13
15
  // they're covered by e2e tests and the data-component / data-test-*
@@ -34,6 +36,7 @@ vi.mock('@designcrowd/fe-shared-lib', () => ({
34
36
  const historyStoreMock = {
35
37
  canUndo: true,
36
38
  canRedo: true,
39
+ isApplyingSnapshot: false,
37
40
  undo: vi.fn(),
38
41
  redo: vi.fn(),
39
42
  };
@@ -57,14 +60,22 @@ vi.mock('@brand-page-maker/stores/brandpage-container', () => ({
57
60
  useBrandPageContainerStore: vi.fn(() => containerStoreMock),
58
61
  }));
59
62
 
63
+ const templateSettingsStoreMock = { isBusy: false };
64
+
65
+ vi.mock('@shared/template-settings/store', () => ({
66
+ useUiTemplateSettingsStore: vi.fn(() => templateSettingsStoreMock),
67
+ }));
68
+
60
69
  beforeEach(() => {
61
70
  setActivePinia(createPinia());
62
71
  historyStoreMock.canUndo = true;
63
72
  historyStoreMock.canRedo = true;
73
+ historyStoreMock.isApplyingSnapshot = false;
64
74
  historyStoreMock.undo = vi.fn();
65
75
  historyStoreMock.redo = vi.fn();
66
76
  aiStoreMock.isProcessing = false;
67
77
  containerStoreMock.isSaving = false;
78
+ templateSettingsStoreMock.isBusy = false;
68
79
  });
69
80
 
70
81
  describe('UndoButton', () => {
@@ -80,6 +91,14 @@ describe('UndoButton', () => {
80
91
  ['canUndo is false', () => (historyStoreMock.canUndo = false)],
81
92
  ['AI is processing', () => (aiStoreMock.isProcessing = true)],
82
93
  ['save is in flight', () => (containerStoreMock.isSaving = true)],
94
+ [
95
+ 'a restore is applying',
96
+ () => (historyStoreMock.isApplyingSnapshot = true),
97
+ ],
98
+ [
99
+ 'template previews are refetching',
100
+ () => (templateSettingsStoreMock.isBusy = true),
101
+ ],
83
102
  ])('does not dispatch when %s', async (_label, applyGuard) => {
84
103
  applyGuard();
85
104
 
@@ -104,6 +123,14 @@ describe('RedoButton', () => {
104
123
  ['canRedo is false', () => (historyStoreMock.canRedo = false)],
105
124
  ['AI is processing', () => (aiStoreMock.isProcessing = true)],
106
125
  ['save is in flight', () => (containerStoreMock.isSaving = true)],
126
+ [
127
+ 'a restore is applying',
128
+ () => (historyStoreMock.isApplyingSnapshot = true),
129
+ ],
130
+ [
131
+ 'template previews are refetching',
132
+ () => (templateSettingsStoreMock.isBusy = true),
133
+ ],
107
134
  ])('does not dispatch when %s', async (_label, applyGuard) => {
108
135
  applyGuard();
109
136
 
@@ -50,6 +50,9 @@ import {
50
50
  } from '@shared/content-settings/store';
51
51
  import { useUiSideBarStore } from '@shared/side-bar/store';
52
52
  import { useUiEditableLogoStore } from '@shared/logo-settings/store/editable-logo-store';
53
+ import { useUiLogoSettingsStore } from '@shared/logo-settings/store';
54
+ import { useLogoStore } from '@brand-page-maker/stores';
55
+ import { getLogoImageFromModel } from '@brand-page/utils/logo.util';
53
56
  import { SIDE_BAR_TAB_NAMES } from '@brand-page-maker/constants';
54
57
  import { PAGE_SECTIONS } from '@shared/page-settings/models';
55
58
  import { FabricRenderClient } from '../../../clients/fabric-render-client';
@@ -988,4 +991,131 @@ describe('brand page maker store module', () => {
988
991
  // Still 'Acme' — no restore attempted.
989
992
  expect(editableLogoStore.logoEdits.textObjects[0].text).toBe('Acme');
990
993
  });
994
+
995
+ // The template sidebar regenerates off the selectedLogoImage watcher, so
996
+ // the restore must hand it a READY preview key. For a designed logo
997
+ // (pre-purchase) that key is the s3 render token minted by the awaited
998
+ // fabric re-render — exposing selectedLogoImage BEFORE the render made the
999
+ // sidebar fetch a list keyed to the previous render, which repainted
1000
+ // seconds later (the undo "flash"). Same precondition that makes the BYO
1001
+ // upload flow work: the key exists before the watcher fires.
1002
+ test('_syncLogoSettingsFromSnapshot re-renders the designed logo BEFORE exposing selectedLogoImage', async () => {
1003
+ const makerStore = useBrandPageMakerStore();
1004
+ const containerStore = useBrandPageContainerStore();
1005
+ const editableLogoStore = useUiEditableLogoStore();
1006
+ const logoSettingsStore = useUiLogoSettingsStore();
1007
+ const logoStore = useLogoStore();
1008
+
1009
+ await makerStore.initialise(INITIALISE_PAYLOAD);
1010
+
1011
+ const originalStamp = editableLogoStore.stampLogoBase64IntoBrandPageData;
1012
+ const originalRender = editableLogoStore.updateEditableLogoImageUrlAsync;
1013
+ const originalSetSelected = logoSettingsStore.setSelectedLogoImage;
1014
+
1015
+ try {
1016
+ containerStore.isPrePurchase = true;
1017
+ logoStore.setLogoTemplateAsset({
1018
+ token: 'designed-template',
1019
+ isByo: false,
1020
+ } as never);
1021
+ logoStore.addLogoDraftAsset({
1022
+ logoDraftAsset: { token: 'designed-2', tick: 1, isByo: false },
1023
+ } as never);
1024
+ // Previous token differs from the snapshot's → not the
1025
+ // logoUnchanged short-circuit.
1026
+ editableLogoStore.setLogoDraftToken('designed-1');
1027
+
1028
+ const snapshot = JSON.parse(
1029
+ JSON.stringify(makerStore.brandPageData),
1030
+ ) as BrandPageModel;
1031
+ const snapshotLogoImage = getLogoImageFromModel(snapshot);
1032
+
1033
+ expect(snapshotLogoImage).toBeTruthy();
1034
+ snapshotLogoImage!.logoDraftToken = 'designed-2';
1035
+
1036
+ const callOrder: string[] = [];
1037
+
1038
+ editableLogoStore.stampLogoBase64IntoBrandPageData = vi.fn();
1039
+ editableLogoStore.updateEditableLogoImageUrlAsync = vi.fn(async () => {
1040
+ // Async like the real fabric render — the order assertion below
1041
+ // fails if the sync doesn't await it before exposing the logo.
1042
+ await new Promise((resolve) => setTimeout(resolve, 0));
1043
+ callOrder.push('render');
1044
+ });
1045
+ logoSettingsStore.setSelectedLogoImage = vi.fn(() => {
1046
+ callOrder.push('selectedLogoImage');
1047
+ });
1048
+
1049
+ await makerStore._syncLogoSettingsFromSnapshot(
1050
+ snapshot,
1051
+ '{"colors":["#123456"]}',
1052
+ );
1053
+
1054
+ expect(callOrder).toEqual(['render', 'selectedLogoImage']);
1055
+ expect(logoSettingsStore.setSelectedLogoImage).toHaveBeenCalledWith(
1056
+ expect.objectContaining({ logoDraftToken: 'designed-2' }),
1057
+ );
1058
+ } finally {
1059
+ containerStore.isPrePurchase = false;
1060
+ editableLogoStore.stampLogoBase64IntoBrandPageData = originalStamp;
1061
+ editableLogoStore.updateEditableLogoImageUrlAsync = originalRender;
1062
+ logoSettingsStore.setSelectedLogoImage = originalSetSelected;
1063
+ }
1064
+ });
1065
+
1066
+ test('_syncLogoSettingsFromSnapshot still exposes selectedLogoImage on the BYO and token-less paths', async () => {
1067
+ const makerStore = useBrandPageMakerStore();
1068
+ const editableLogoStore = useUiEditableLogoStore();
1069
+ const logoSettingsStore = useUiLogoSettingsStore();
1070
+ const logoStore = useLogoStore();
1071
+
1072
+ await makerStore.initialise(INITIALISE_PAYLOAD);
1073
+
1074
+ const originalStamp = editableLogoStore.stampLogoBase64IntoBrandPageData;
1075
+ const originalSetSelected = logoSettingsStore.setSelectedLogoImage;
1076
+
1077
+ try {
1078
+ editableLogoStore.stampLogoBase64IntoBrandPageData = vi.fn();
1079
+
1080
+ const setSelectedSpy = vi.fn();
1081
+
1082
+ logoSettingsStore.setSelectedLogoImage = setSelectedSpy;
1083
+
1084
+ // BYO path: registered byo draft, differing previous token.
1085
+ logoStore.addLogoDraftAsset({
1086
+ logoDraftAsset: { token: 'byo-1', tick: 2, isByo: true },
1087
+ } as never);
1088
+ editableLogoStore.setLogoDraftToken('designed-1');
1089
+
1090
+ const byoSnapshot = JSON.parse(
1091
+ JSON.stringify(makerStore.brandPageData),
1092
+ ) as BrandPageModel;
1093
+ const byoLogoImage = getLogoImageFromModel(byoSnapshot);
1094
+
1095
+ byoLogoImage!.logoDraftToken = 'byo-1';
1096
+
1097
+ await makerStore._syncLogoSettingsFromSnapshot(byoSnapshot);
1098
+
1099
+ expect(setSelectedSpy).toHaveBeenCalledWith(
1100
+ expect.objectContaining({ logoDraftToken: 'byo-1' }),
1101
+ );
1102
+
1103
+ // Token-less path: the logoImage exists but has no draft token.
1104
+ setSelectedSpy.mockClear();
1105
+
1106
+ const tokenlessSnapshot = JSON.parse(
1107
+ JSON.stringify(makerStore.brandPageData),
1108
+ ) as BrandPageModel;
1109
+ const tokenlessLogoImage = getLogoImageFromModel(tokenlessSnapshot);
1110
+
1111
+ tokenlessLogoImage!.logoDraftToken = '';
1112
+
1113
+ await makerStore._syncLogoSettingsFromSnapshot(tokenlessSnapshot);
1114
+
1115
+ expect(setSelectedSpy).toHaveBeenCalledTimes(1);
1116
+ } finally {
1117
+ editableLogoStore.stampLogoBase64IntoBrandPageData = originalStamp;
1118
+ logoSettingsStore.setSelectedLogoImage = originalSetSelected;
1119
+ }
1120
+ });
991
1121
  });
@@ -960,83 +960,92 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
960
960
 
961
961
  if (!logoImage) return;
962
962
 
963
- // Assign a FRESH object — the same way the upload/selection flows
964
- // write selectedLogoImage. A setState/$patch here deep-merges into the
965
- // existing object, so the templates store's deep watcher receives
966
- // old === new (same reference), sees no logoDraftToken change, and
967
- // never refetches the template previews. Post-purchase that watcher
968
- // is the ONLY refetch path (previews are keyed by logoDraftToken and
969
- // the designed-logo re-render below is pre-purchase-only), which left
970
- // the sidebar stuck on the undone logo (BP-5814).
971
- logoSettingsStore.setSelectedLogoImage({ ...logoImage });
972
-
973
- const draftToken: string | undefined = logoImage.logoDraftToken;
963
+ try {
964
+ const draftToken: string | undefined = logoImage.logoDraftToken;
974
965
 
975
- if (!draftToken) return;
966
+ if (!draftToken) return;
976
967
 
977
- // Re-derive enableEditMode + templateAsset from the draft registry
978
- // (append-only for the session) instead of stashing on every entry.
979
- const draft = logoStore.getLogoDraftAssetByToken(draftToken);
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);
980
971
 
981
- if (draft) {
982
- editableLogoStore.setEnableEditMode(!draft.isByo);
972
+ if (draft) {
973
+ editableLogoStore.setEnableEditMode(!draft.isByo);
983
974
 
984
- if (draft.templateAsset) {
985
- logoStore.setLogoTemplateAsset(draft.templateAsset);
975
+ if (draft.templateAsset) {
976
+ logoStore.setLogoTemplateAsset(draft.templateAsset);
977
+ }
986
978
  }
987
- }
988
979
 
989
- editableLogoStore.setLogoDraftToken(draftToken);
980
+ editableLogoStore.setLogoDraftToken(draftToken);
990
981
 
991
- // No logo change → cache is still correct; just stamp base64 back
992
- // (snapshot doesn't carry logoImageBase64Url).
993
- const logoUnchanged =
994
- prevDraftToken === draftToken &&
995
- (logoEditsStr === undefined || logoEditsStr === prevLogoEditsStr);
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);
996
987
 
997
- if (logoUnchanged) {
998
- editableLogoStore.stampLogoBase64IntoBrandPageData();
988
+ if (logoUnchanged) {
989
+ editableLogoStore.stampLogoBase64IntoBrandPageData();
999
990
 
1000
- return;
1001
- }
991
+ return;
992
+ }
1002
993
 
1003
- const isByo = !!draft?.isByo;
994
+ const isByo = !!draft?.isByo;
1004
995
 
1005
- if (isByo) {
1006
- // BYO mints a fresh draftToken per upload so the per-token
1007
- // cache always matches; no re-render needed.
1008
- if (editableLogoStore.restoreVariationsForDraftToken(draftToken)) {
1009
- editableLogoStore.stampLogoBase64IntoBrandPageData();
1010
- }
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
+ }
1011
1002
 
1012
- return;
1013
- }
1003
+ return;
1004
+ }
1014
1005
 
1015
- // Designed: draft token is reused across edits, so the cache
1016
- // only holds the latest render. Re-issue fabric-render — same
1017
- // call as the live edit path. Awaited so concurrent undos
1018
- // serialize and the call sits inside the isApplyingSnapshot
1019
- // window (watcher guard suppresses the writeback).
1020
- if (
1021
- containerStore.isPrePurchase &&
1022
- logoEditsStr &&
1023
- logoStore.logoTemplateAsset
1024
- ) {
1025
- // Stamp the cached (pre-undo) base64 onto brandPageData first
1026
- // so the canvas keeps showing the previous logo while the
1027
- // re-render is in flight. Without this the logoImageBase64Url
1028
- // fields are undefined (snapshot is stripped pre-purchase) and
1029
- // the canvas flickers blank for ~500-1500ms.
1030
- editableLogoStore.stampLogoBase64IntoBrandPageData();
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();
1031
1022
 
1032
- try {
1033
- await editableLogoStore.updateEditableLogoImageUrlAsync();
1034
- } catch (error) {
1035
- console.error(
1036
- 'brandpage-maker-store: failed to re-render logo on history apply',
1037
- error,
1038
- );
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
+ }
1039
1031
  }
1032
+ } finally {
1033
+ // Assign a FRESH object — the same way the upload/selection flows
1034
+ // write selectedLogoImage. A setState/$patch here deep-merges into
1035
+ // the existing object, so the templates store's deep watcher would
1036
+ // receive old === new (same reference), see no logoDraftToken
1037
+ // change, and never refetch the template previews (BP-5814).
1038
+ //
1039
+ // Set LAST — after the branch above has prepared the preview key
1040
+ // (BYO cache restore, or the awaited designed re-render that mints
1041
+ // the s3 token) — so the watcher always fires with a fetchable key,
1042
+ // the same precondition that makes the upload flow work. Firing it
1043
+ // before the re-render fetched the template list with the previous
1044
+ // render's s3 token: an obsolete list that repainted seconds later
1045
+ // (the undo "flash"). The sidebar instead holds the previous
1046
+ // previews while the render is in flight, matching the canvas
1047
+ // policy above.
1048
+ logoSettingsStore.setSelectedLogoImage({ ...logoImage });
1040
1049
  }
1041
1050
  },
1042
1051