@hyperframes/studio 0.8.23 → 0.8.25

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 (90) hide show
  1. package/dist/assets/{hyperframes-player-oZ6g4tHJ.js → hyperframes-player-CTP_00ix.js} +1 -1
  2. package/dist/assets/{index-UyvIa_cE.js → index-Bjb4YqS_.js} +1 -1
  3. package/dist/assets/{index-Tiu74FPg.js → index-DSk_ALFr.js} +218 -218
  4. package/dist/assets/{index-DW-H7Y2b.js → index-Di-8Jxqc.js} +1 -1
  5. package/dist/assets/index-DnRfAiK2.css +1 -0
  6. package/dist/{chunk-6BT6DTB4.js → chunk-A7S5SGNA.js} +2 -2
  7. package/dist/chunk-A7S5SGNA.js.map +1 -0
  8. package/dist/{domEditingLayers-URA7BLWE.js → domEditingLayers-JMMVOGTO.js} +2 -2
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +6206 -5011
  12. package/dist/index.js.map +1 -1
  13. package/package.json +8 -7
  14. package/src/components/editor/TopologyLens.test.tsx +294 -0
  15. package/src/components/editor/TopologyLens.tsx +206 -0
  16. package/src/components/editor/domEditOverlayGeometry.ts +14 -0
  17. package/src/components/editor/domEditOverlayGeometryBatch.test.ts +18 -0
  18. package/src/components/editor/domEditingLayers.test.ts +9 -0
  19. package/src/components/editor/domEditingLayers.ts +8 -8
  20. package/src/components/editor/topologyLensGeometry.test.ts +44 -0
  21. package/src/components/editor/topologyLensGeometry.ts +119 -0
  22. package/src/components/editor/topologyLensState.test.ts +116 -0
  23. package/src/components/editor/topologyLensState.ts +72 -0
  24. package/src/components/nle/PreviewOverlays.topologyLens.test.tsx +126 -0
  25. package/src/components/nle/PreviewOverlays.tsx +24 -18
  26. package/src/components/sidebar/CompositionsTab.drag.test.tsx +16 -0
  27. package/src/components/sidebar/CompositionsTab.tsx +10 -3
  28. package/src/components/ui/HyperframesLoader.tsx +3 -48
  29. package/src/components/ui/HyperframesMark.tsx +53 -0
  30. package/src/contexts/DomEditContext.tsx +12 -0
  31. package/src/hooks/domEditCommitRunner.test.ts +59 -0
  32. package/src/hooks/domEditCommitRunner.ts +55 -20
  33. package/src/hooks/domEditCommitTypes.ts +8 -1
  34. package/src/hooks/domEditTextCommitPlan.ts +47 -0
  35. package/src/hooks/useDomEditAttributeCommits.ts +4 -1
  36. package/src/hooks/useDomEditCommits.test.tsx +207 -8
  37. package/src/hooks/useDomEditCommits.ts +54 -14
  38. package/src/hooks/useDomEditPositionPatchCommit.test.tsx +3 -4
  39. package/src/hooks/useDomEditPositionPatchCommit.ts +27 -29
  40. package/src/hooks/useDomEditSession.ts +6 -2
  41. package/src/hooks/useDomEditTextCommits.test.tsx +86 -2
  42. package/src/hooks/useDomEditTextCommits.ts +64 -86
  43. package/src/hooks/useDomEditWiring.ts +1 -1
  44. package/src/hooks/useDomSelection.ts +2 -0
  45. package/src/hooks/useDomSelectionTypes.ts +2 -0
  46. package/src/hooks/useExternalFileChangeCoordinator.test.tsx +71 -4
  47. package/src/hooks/useExternalFileChangeCoordinator.ts +18 -9
  48. package/src/hooks/useGsapAnimationFetchFallback.test.ts +13 -1
  49. package/src/hooks/useGsapAnimationFetchFallback.ts +7 -3
  50. package/src/hooks/useGsapAnimationOps.test.tsx +101 -0
  51. package/src/hooks/useGsapAnimationOps.ts +4 -4
  52. package/src/hooks/useGsapAwareEditing.test.tsx +30 -2
  53. package/src/hooks/useGsapAwareEditing.ts +31 -10
  54. package/src/hooks/useGsapSelectionHandlers.test.tsx +3 -1
  55. package/src/hooks/useGsapSelectionHandlers.ts +21 -11
  56. package/src/hooks/useRenderClipContent.test.ts +27 -0
  57. package/src/hooks/useRenderClipContent.ts +5 -0
  58. package/src/hooks/useStudioExternalFileChanges.ts +5 -0
  59. package/src/player/components/CompositionThumbnail.test.ts +66 -1
  60. package/src/player/components/CompositionThumbnail.tsx +6 -0
  61. package/src/player/store/playerStore.test.ts +24 -0
  62. package/src/player/store/thumbnailSlice.ts +6 -0
  63. package/src/styles/studio.css +179 -0
  64. package/src/utils/domEditSaveQueue.test.ts +5 -3
  65. package/src/utils/domEditSaveQueue.ts +6 -1
  66. package/src/webmcp/StudioAgentTools.test.ts +70 -0
  67. package/src/webmcp/StudioAgentTools.tsx +89 -21
  68. package/src/webmcp/handles.test.ts +172 -15
  69. package/src/webmcp/handles.ts +152 -35
  70. package/src/webmcp/tools/animationTools.test.ts +241 -60
  71. package/src/webmcp/tools/animationTools.ts +191 -104
  72. package/src/webmcp/tools/contentTools.test.ts +103 -41
  73. package/src/webmcp/tools/contentTools.ts +149 -105
  74. package/src/webmcp/tools/inspectTools.test.ts +47 -5
  75. package/src/webmcp/tools/inspectTools.ts +59 -19
  76. package/src/webmcp/tools/lookTools.test.ts +185 -38
  77. package/src/webmcp/tools/lookTools.ts +134 -21
  78. package/src/webmcp/tools/selectionTools.test.ts +105 -9
  79. package/src/webmcp/tools/selectionTools.ts +34 -19
  80. package/src/webmcp/tools/targetedWriteTools.test.ts +125 -0
  81. package/src/webmcp/tools/transformTools.test.ts +140 -54
  82. package/src/webmcp/tools/transformTools.ts +215 -56
  83. package/src/webmcp/useStudioAgentTools.test.tsx +310 -10
  84. package/src/webmcp/useStudioAgentTools.ts +27 -15
  85. package/src/webmcp/webmcpTestUtils.ts +37 -0
  86. package/src/webmcp/writeCoordinator.test.ts +375 -0
  87. package/src/webmcp/writeCoordinator.ts +473 -0
  88. package/dist/assets/index-yGhfxxoL.css +0 -1
  89. package/dist/chunk-6BT6DTB4.js.map +0 -1
  90. /package/dist/{domEditingLayers-URA7BLWE.js.map → domEditingLayers-JMMVOGTO.js.map} +0 -0
@@ -0,0 +1,53 @@
1
+ import { useId, type SVGProps } from "react";
2
+
3
+ export function HyperframesMark({ viewBox = "0 0 100 100", ...props }: SVGProps<SVGSVGElement>) {
4
+ const gradientId = useId();
5
+ const leftGradient = `${gradientId}-left`;
6
+ const rightGradient = `${gradientId}-right`;
7
+ return (
8
+ <svg
9
+ viewBox={viewBox}
10
+ fill="none"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ aria-hidden="true"
13
+ {...props}
14
+ >
15
+ <g transform="translate(50 50)" opacity=".92">
16
+ <g transform="translate(-50 -50)">
17
+ <path
18
+ d="M10.1851 57.8021L33.1145 73.8313C36.2202 75.9978 41.5173 73.5433 42.4816 69.4984L51.7611 30.4271C52.7253 26.3822 48.5802 23.9277 44.4602 26.0942L13.917 42.1235C6.96677 45.7676 4.97564 54.1579 10.1851 57.8021Z"
19
+ fill={`url(#${leftGradient})`}
20
+ />
21
+ <path
22
+ d="M87.5129 57.5141L56.9696 73.5433C52.8371 75.7098 48.7046 73.2553 49.6688 69.2104L58.9483 30.1391C59.9125 26.0942 65.2097 23.6397 68.3154 25.8062L91.2447 41.8354C96.4668 45.4796 94.4631 53.8699 87.5129 57.5141Z"
23
+ fill={`url(#${rightGradient})`}
24
+ />
25
+ </g>
26
+ </g>
27
+ <defs>
28
+ <linearGradient
29
+ id={leftGradient}
30
+ x1="48.5676"
31
+ y1="25"
32
+ x2="44.7804"
33
+ y2="71.9384"
34
+ gradientUnits="userSpaceOnUse"
35
+ >
36
+ <stop stopColor="#06E3FA" />
37
+ <stop offset="1" stopColor="#4FDB5E" />
38
+ </linearGradient>
39
+ <linearGradient
40
+ id={rightGradient}
41
+ x1="54.8282"
42
+ y1="73.8392"
43
+ x2="72.0989"
44
+ y2="32.8932"
45
+ gradientUnits="userSpaceOnUse"
46
+ >
47
+ <stop stopColor="#06E3FA" />
48
+ <stop offset="1" stopColor="#4FDB5E" />
49
+ </linearGradient>
50
+ </defs>
51
+ </svg>
52
+ );
53
+ }
@@ -14,6 +14,7 @@ export interface DomEditActionsValue extends Pick<
14
14
  | "applyDomSelection"
15
15
  | "clearDomSelection"
16
16
  | "handleDomStyleCommit"
17
+ | "handleDomStyleCommitForSelection"
17
18
  | "handleDomAttributeCommit"
18
19
  | "handleDomAttributeLiveCommit"
19
20
  | "handleDomAttributeQuietCommit"
@@ -26,10 +27,12 @@ export interface DomEditActionsValue extends Pick<
26
27
  | "handleDomRotationCommit"
27
28
  | "handleDomManualEditsReset"
28
29
  | "handleDomTextCommit"
30
+ | "handleDomTextCommitForSelection"
29
31
  | "handleDomRichTextCommit"
30
32
  | "handleDomTextFieldStyleCommit"
31
33
  | "handleDomAddTextField"
32
34
  | "handleDomRemoveTextField"
35
+ | "getGsapAnimationsForSelection"
33
36
  | "handleAskAgent"
34
37
  | "handleAgentModalSubmit"
35
38
  | "handleBlockedDomMove"
@@ -153,6 +156,7 @@ export function DomEditProvider({
153
156
  applyDomSelection,
154
157
  clearDomSelection,
155
158
  handleDomStyleCommit,
159
+ handleDomStyleCommitForSelection,
156
160
  handleDomAttributeCommit,
157
161
  handleDomAttributeLiveCommit,
158
162
  handleDomAttributeQuietCommit,
@@ -166,10 +170,12 @@ export function DomEditProvider({
166
170
  handleDomManualEditsReset,
167
171
 
168
172
  handleDomTextCommit,
173
+ handleDomTextCommitForSelection,
169
174
  handleDomRichTextCommit,
170
175
  handleDomTextFieldStyleCommit,
171
176
  handleDomAddTextField,
172
177
  handleDomRemoveTextField,
178
+ getGsapAnimationsForSelection,
173
179
  handleAskAgent,
174
180
  handleAgentModalSubmit,
175
181
  handleBlockedDomMove,
@@ -243,6 +249,7 @@ export function DomEditProvider({
243
249
  applyDomSelection,
244
250
  clearDomSelection,
245
251
  handleDomStyleCommit,
252
+ handleDomStyleCommitForSelection,
246
253
  handleDomAttributeCommit,
247
254
  handleDomAttributeLiveCommit,
248
255
  handleDomAttributeQuietCommit,
@@ -255,10 +262,12 @@ export function DomEditProvider({
255
262
  handleDomRotationCommit,
256
263
  handleDomManualEditsReset,
257
264
  handleDomTextCommit,
265
+ handleDomTextCommitForSelection,
258
266
  handleDomRichTextCommit,
259
267
  handleDomTextFieldStyleCommit,
260
268
  handleDomAddTextField,
261
269
  handleDomRemoveTextField,
270
+ getGsapAnimationsForSelection,
262
271
  handleAskAgent,
263
272
  handleAgentModalSubmit,
264
273
  handleBlockedDomMove,
@@ -314,6 +323,7 @@ export function DomEditProvider({
314
323
  applyDomSelection,
315
324
  clearDomSelection,
316
325
  handleDomStyleCommit,
326
+ handleDomStyleCommitForSelection,
317
327
  handleDomAttributeCommit,
318
328
  handleDomAttributeLiveCommit,
319
329
  handleDomAttributeQuietCommit,
@@ -326,10 +336,12 @@ export function DomEditProvider({
326
336
  handleDomRotationCommit,
327
337
  handleDomManualEditsReset,
328
338
  handleDomTextCommit,
339
+ handleDomTextCommitForSelection,
329
340
  handleDomRichTextCommit,
330
341
  handleDomTextFieldStyleCommit,
331
342
  handleDomAddTextField,
332
343
  handleDomRemoveTextField,
344
+ getGsapAnimationsForSelection,
333
345
  handleAskAgent,
334
346
  handleAgentModalSubmit,
335
347
  handleBlockedDomMove,
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { bumpDomEditCommitMapVersion, runDomEditCommit } from "./domEditCommitRunner";
3
+
4
+ function commitConfig(overrides: Partial<Parameters<typeof runDomEditCommit>[0]> = {}) {
5
+ return {
6
+ capture: vi.fn(),
7
+ apply: vi.fn(),
8
+ persist: vi.fn(async () => undefined),
9
+ shouldRevert: vi.fn(() => true),
10
+ revert: vi.fn(),
11
+ onError: vi.fn(),
12
+ shouldResync: vi.fn(() => false),
13
+ resync: vi.fn(),
14
+ ...overrides,
15
+ };
16
+ }
17
+
18
+ describe("DOM edit commit version cleanup", () => {
19
+ it("releases a settled target without deleting a newer target version", () => {
20
+ const versions = new Map<string, symbol>();
21
+ const first = bumpDomEditCommitMapVersion(versions, "headline");
22
+ const second = bumpDomEditCommitMapVersion(versions, "headline");
23
+
24
+ first.release();
25
+ expect(versions.size).toBe(1);
26
+ expect(second()).toBe(true);
27
+
28
+ second.release();
29
+ expect(versions.size).toBe(0);
30
+ });
31
+
32
+ it("never lets an old commit become latest again after cleanup and reuse", () => {
33
+ const versions = new Map<string, symbol>();
34
+ const first = bumpDomEditCommitMapVersion(versions, "headline");
35
+ const second = bumpDomEditCommitMapVersion(versions, "headline");
36
+ second.release();
37
+
38
+ const third = bumpDomEditCommitMapVersion(versions, "headline");
39
+
40
+ expect(first()).toBe(false);
41
+ expect(third()).toBe(true);
42
+ });
43
+
44
+ it("runs settlement cleanup even when capture throws", async () => {
45
+ const onFinally = vi.fn();
46
+ await expect(
47
+ runDomEditCommit(
48
+ commitConfig({
49
+ capture: () => {
50
+ throw new Error("capture failed");
51
+ },
52
+ onFinally,
53
+ }),
54
+ ),
55
+ ).rejects.toThrow("capture failed");
56
+
57
+ expect(onFinally).toHaveBeenCalledOnce();
58
+ });
59
+ });
@@ -1,7 +1,9 @@
1
+ import type { DomEditPersistOutcome } from "./domEditCommitTypes";
2
+
1
3
  interface DomEditCommitRunnerConfig {
2
4
  capture: () => void;
3
5
  apply: () => void;
4
- persist: () => Promise<void>;
6
+ persist: () => Promise<unknown>;
5
7
  shouldRevert: (error: unknown) => boolean;
6
8
  revert: () => void;
7
9
  onError: (error: unknown) => void;
@@ -16,6 +18,8 @@ interface DomEditCommitRunnerConfig {
16
18
  * instead of relying on a rejection that will never come.
17
19
  */
18
20
  onSettled?: (ok: boolean) => void;
21
+ /** Always runs once after persistence, resync, or an unexpected runner error. */
22
+ onFinally?: () => void;
19
23
  }
20
24
 
21
25
  interface CommitVersionRef {
@@ -28,32 +32,46 @@ export function bumpDomEditCommitVersion(versionRef: CommitVersionRef): () => bo
28
32
  return () => versionRef.current === commitVersion;
29
33
  }
30
34
 
35
+ export interface DomEditCommitVersionGuard {
36
+ (): boolean;
37
+ release: () => void;
38
+ }
39
+
31
40
  export function bumpDomEditCommitMapVersion<TKey>(
32
- versionMap: Map<TKey, number>,
41
+ versionMap: Map<TKey, symbol>,
33
42
  versionKey: TKey,
34
- ): () => boolean {
35
- const commitVersion = (versionMap.get(versionKey) ?? 0) + 1;
43
+ ): DomEditCommitVersionGuard {
44
+ const commitVersion = Symbol("dom-edit-commit");
36
45
  versionMap.set(versionKey, commitVersion);
37
- return () => versionMap.get(versionKey) === commitVersion;
46
+ const isLatest = () => versionMap.get(versionKey) === commitVersion;
47
+ return Object.assign(isLatest, {
48
+ release: () => {
49
+ if (isLatest()) versionMap.delete(versionKey);
50
+ },
51
+ });
38
52
  }
39
53
 
40
54
  export async function runDomEditCommit(config: DomEditCommitRunnerConfig): Promise<void> {
41
- config.capture();
42
- config.apply();
43
-
44
55
  try {
45
- await config.persist();
46
- config.onSettled?.(true);
47
- } catch (error) {
48
- if (config.shouldRevert(error)) {
49
- config.revert();
56
+ config.capture();
57
+ config.apply();
58
+
59
+ try {
60
+ await config.persist();
61
+ config.onSettled?.(true);
62
+ } catch (error) {
63
+ if (config.shouldRevert(error)) {
64
+ config.revert();
65
+ }
66
+ config.onError(error);
67
+ config.onSettled?.(false);
50
68
  }
51
- config.onError(error);
52
- config.onSettled?.(false);
53
- }
54
69
 
55
- if (!config.shouldResync()) return;
56
- await config.resync();
70
+ if (!config.shouldResync()) return;
71
+ await config.resync();
72
+ } finally {
73
+ config.onFinally?.();
74
+ }
57
75
  }
58
76
 
59
77
  /**
@@ -75,7 +93,9 @@ export type DomEditCommitDeclineReason =
75
93
  | "preview-stale"
76
94
  | "persist-failed";
77
95
 
78
- export type DomEditCommitOutcome = { ok: true } | { ok: false; reason: DomEditCommitDeclineReason };
96
+ export type DomEditCommitOutcome =
97
+ | { ok: true; persistence?: DomEditPersistOutcome }
98
+ | { ok: false; reason: DomEditCommitDeclineReason };
79
99
 
80
100
  export function domEditCommitDeclined(reason: DomEditCommitDeclineReason): DomEditCommitOutcome {
81
101
  return { ok: false, reason };
@@ -93,12 +113,27 @@ export async function runReportedDomEditCommit(
93
113
  config: DomEditCommitRunnerConfig,
94
114
  ): Promise<DomEditCommitOutcome> {
95
115
  let landed = false;
116
+ let persistence: DomEditPersistOutcome | undefined;
96
117
  await runDomEditCommit({
97
118
  ...config,
119
+ persist: async () => {
120
+ const result = await config.persist();
121
+ if (isDomEditPersistOutcome(result)) persistence = result;
122
+ },
98
123
  onSettled: (ok) => {
99
124
  landed = ok;
100
125
  config.onSettled?.(ok);
101
126
  },
102
127
  });
103
- return landed ? { ok: true } : domEditCommitDeclined("persist-failed");
128
+ if (!landed) return domEditCommitDeclined("persist-failed");
129
+ return persistence ? { ok: true, persistence } : { ok: true };
130
+ }
131
+
132
+ function isDomEditPersistOutcome(value: unknown): value is DomEditPersistOutcome {
133
+ if (typeof value !== "object" || value === null) return false;
134
+ return (
135
+ typeof Reflect.get(value, "sourceFile") === "string" &&
136
+ typeof Reflect.get(value, "version") === "string" &&
137
+ typeof Reflect.get(value, "changed") === "boolean"
138
+ );
104
139
  }
@@ -6,6 +6,13 @@ export interface DomEditPatchBatch {
6
6
  patches: Array<{ target: PatchTarget; operations: PatchOperation[] }>;
7
7
  }
8
8
 
9
+ /** Durable evidence returned by the one Studio DOM persistence actor. */
10
+ export interface DomEditPersistOutcome {
11
+ sourceFile: string;
12
+ version: string;
13
+ changed: boolean;
14
+ }
15
+
9
16
  export type CommitDomEditPatchBatches = (
10
17
  batches: DomEditPatchBatch[],
11
18
  options: {
@@ -49,4 +56,4 @@ export type PersistDomEditOperations = (
49
56
  prepareContent?: (html: string, sourceFile: string) => string;
50
57
  shouldSave?: () => boolean;
51
58
  },
52
- ) => Promise<void>;
59
+ ) => Promise<DomEditPersistOutcome | undefined>;
@@ -0,0 +1,47 @@
1
+ import {
2
+ buildDomEditRichTextPatchOperation,
3
+ buildDomEditTextPatchOperation,
4
+ serializeDomEditTextFields,
5
+ type DomEditTextField,
6
+ } from "../components/editor/domEditing";
7
+ import type { PatchOperation } from "../utils/sourcePatcher";
8
+ import { buildTextFieldChildOperations } from "./domEditTextFieldCommitOps";
9
+
10
+ interface DomTextCommitPlan {
11
+ usesSerializedTextFields: boolean;
12
+ nextContent: string;
13
+ operations: PatchOperation[];
14
+ }
15
+
16
+ export function buildNextDomTextFields(
17
+ textFields: DomEditTextField[],
18
+ value: string,
19
+ fieldKey?: string,
20
+ ): DomEditTextField[] {
21
+ if (textFields.length === 0) return [];
22
+ return textFields.map((field) => (field.key === fieldKey ? { ...field, value } : field));
23
+ }
24
+
25
+ export function planDomTextCommit(
26
+ originalTextFields: DomEditTextField[],
27
+ nextTextFields: DomEditTextField[],
28
+ plainTextContent: string,
29
+ ): DomTextCommitPlan {
30
+ const usesSerializedTextFields =
31
+ nextTextFields.length > 1 || nextTextFields.some((field) => field.source === "child");
32
+ const nextContent = usesSerializedTextFields
33
+ ? serializeDomEditTextFields(nextTextFields)
34
+ : plainTextContent;
35
+ const childOperations = usesSerializedTextFields
36
+ ? buildTextFieldChildOperations(originalTextFields, nextTextFields)
37
+ : null;
38
+ // Per-child operations when the layers still line up one-for-one. Structure
39
+ // changes write the element's markup because no stable child address exists.
40
+ const operations =
41
+ childOperations ??
42
+ (usesSerializedTextFields
43
+ ? [buildDomEditRichTextPatchOperation(nextContent)]
44
+ : [buildDomEditTextPatchOperation(nextContent)]);
45
+
46
+ return { usesSerializedTextFields, nextContent, operations };
47
+ }
@@ -135,7 +135,7 @@ export function useDomEditAttributeCommits({
135
135
  refreshDomEditSelectionFromPreview,
136
136
  persistDomEditOperations,
137
137
  }: UseDomEditAttributeCommitsParams) {
138
- const domAttributeCommitVersionRef = useRef(new Map<string, number>());
138
+ const domAttributeCommitVersionRef = useRef(new Map<string, symbol>());
139
139
 
140
140
  const commitDataAttribute = useCallback(
141
141
  async (attr: string, value: string | null, options: DataAttributeCommitOptions) => {
@@ -200,6 +200,7 @@ export function useDomEditAttributeCommits({
200
200
  syncStoredAutomationFromPreview(previewIframeRef.current?.contentDocument ?? null);
201
201
  },
202
202
  onSettled: options.onSettled,
203
+ onFinally: isLatestCommit.release,
203
204
  });
204
205
  },
205
206
  [
@@ -288,6 +289,7 @@ export function useDomEditAttributeCommits({
288
289
  shouldResync: () => isLatestCommit() && !!options.refreshAfter,
289
290
  resync: () => refreshDomEditSelectionFromPreview(selection),
290
291
  onSettled: options.onSettled,
292
+ onFinally: isLatestCommit.release,
291
293
  });
292
294
  },
293
295
  [
@@ -420,6 +422,7 @@ export function useDomEditAttributeCommits({
420
422
  // shipped without one.
421
423
  syncStoredAutomationFromPreview(previewIframeRef.current?.contentDocument ?? null);
422
424
  },
425
+ onFinally: isLatestCommit.release,
423
426
  });
424
427
  },
425
428
  [