@hyperframes/studio 0.7.58 → 0.7.59
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.
- package/dist/assets/{hyperframes-player-CtTDO63S.js → hyperframes-player-Bm07FLkl.js} +1 -1
- package/dist/assets/{index-C47jAC3Q.js → index-B995FG46.js} +1 -1
- package/dist/assets/index-CrkAdJkb.js +426 -0
- package/dist/assets/index-Dj5p8U_A.css +1 -0
- package/dist/assets/{index-DeQPzqwH.js → index-FvzmPhfG.js} +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.html +2 -2
- package/dist/index.js +13514 -8528
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +3 -3
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioRightPanel.tsx +29 -51
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineTrackVisibility.test.ts +52 -0
- package/src/hooks/timelineTrackVisibility.ts +27 -10
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.ts +29 -175
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.ts +17 -5
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useElementLifecycleOps.ts +8 -3
- package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
- package/src/hooks/useFileManager.ts +82 -60
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +4 -1
- package/src/hooks/useTimelineEditing.ts +16 -10
- package/src/hooks/useTimelineEditingTypes.ts +3 -0
- package/src/hooks/useTimelineGroupEditing.ts +16 -2
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +490 -57
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +292 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +30 -27
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.ts +17 -0
- package/dist/assets/index-B_UvTX3E.js +0 -423
- package/dist/assets/index-uahwWkgw.css +0 -1
|
@@ -25,6 +25,7 @@ interface DataAttributeCommitOptions {
|
|
|
25
25
|
coalescePrefix: string;
|
|
26
26
|
skipRefresh: boolean;
|
|
27
27
|
refreshAfter?: boolean;
|
|
28
|
+
onSettled?: (ok: boolean) => void;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function resolveFullAttrName(attr: string, prefixData: boolean | undefined): string {
|
|
@@ -57,6 +58,25 @@ interface CapturedAttributeElement {
|
|
|
57
58
|
previousValue: string | null;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
interface CapturedMultiAttributeElement {
|
|
62
|
+
element: HTMLElement;
|
|
63
|
+
previousValues: Map<string, string | null>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function captureMultiAttributeElement(
|
|
67
|
+
doc: Document | null | undefined,
|
|
68
|
+
selection: DomEditSelection,
|
|
69
|
+
activeCompPath: string | null,
|
|
70
|
+
fullAttrs: string[],
|
|
71
|
+
): CapturedMultiAttributeElement | null {
|
|
72
|
+
const el = findPreviewAttributeElement(doc, selection, activeCompPath);
|
|
73
|
+
if (!el) return null;
|
|
74
|
+
const previousValues = new Map(
|
|
75
|
+
fullAttrs.map((fullAttr) => [fullAttr, el.getAttribute(fullAttr)]),
|
|
76
|
+
);
|
|
77
|
+
return { element: el, previousValues };
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
function captureAttributeElement(
|
|
61
81
|
doc: Document | null | undefined,
|
|
62
82
|
selection: DomEditSelection,
|
|
@@ -128,6 +148,7 @@ export function useDomEditAttributeCommits({
|
|
|
128
148
|
onError: (error) => reportDomEditPersistFailure(domEditSelection, [op], error, showToast),
|
|
129
149
|
shouldResync: () => isLatestCommit() && !!options.refreshAfter,
|
|
130
150
|
resync: () => refreshDomEditSelectionFromPreview(domEditSelection),
|
|
151
|
+
onSettled: options.onSettled,
|
|
131
152
|
});
|
|
132
153
|
},
|
|
133
154
|
[
|
|
@@ -140,6 +161,105 @@ export function useDomEditAttributeCommits({
|
|
|
140
161
|
],
|
|
141
162
|
);
|
|
142
163
|
|
|
164
|
+
// Commits several data-* attributes on the SAME element in ONE persist call
|
|
165
|
+
// — needed when two attributes together describe a single logical value
|
|
166
|
+
// (e.g. a pinned timing range's start+duration): committing them through two
|
|
167
|
+
// separate sequential `commitDataAttribute` calls leaves a window where the
|
|
168
|
+
// second call resolves `domEditSelection` fresh from current hook state, so
|
|
169
|
+
// a selection change between the two awaits would misdirect it at the
|
|
170
|
+
// NEWLY selected element instead of the one being edited, and a failure of
|
|
171
|
+
// just the second call would leave the two attributes in an inconsistent
|
|
172
|
+
// half-applied state. Bundling them into one `PatchOperation[]` against an
|
|
173
|
+
// explicit, caller-supplied `selection` (not the "current" one) closes both
|
|
174
|
+
// gaps — matching `onCommitAnimatedProperties`'s same-shaped fix for GSAP
|
|
175
|
+
// property batches.
|
|
176
|
+
const commitDataAttributes = useCallback(
|
|
177
|
+
async (
|
|
178
|
+
selection: DomEditSelection,
|
|
179
|
+
attrs: Record<string, string | null>,
|
|
180
|
+
options: DataAttributeCommitOptions,
|
|
181
|
+
) => {
|
|
182
|
+
const iframe = previewIframeRef.current;
|
|
183
|
+
const entries = Object.entries(attrs).map(([attr, value]) => ({
|
|
184
|
+
attr,
|
|
185
|
+
fullAttr: resolveFullAttrName(attr, true),
|
|
186
|
+
value,
|
|
187
|
+
}));
|
|
188
|
+
const commitKey = `${options.coalescePrefix}:${entries
|
|
189
|
+
.map((entry) => entry.attr)
|
|
190
|
+
.sort()
|
|
191
|
+
.join(",")}:${getDomEditTargetKey(selection)}`;
|
|
192
|
+
const isLatestCommit = bumpDomEditCommitMapVersion(
|
|
193
|
+
domAttributeCommitVersionRef.current,
|
|
194
|
+
commitKey,
|
|
195
|
+
);
|
|
196
|
+
const ops: PatchOperation[] = entries.map((entry) => ({
|
|
197
|
+
type: "attribute",
|
|
198
|
+
property: entry.attr,
|
|
199
|
+
value: entry.value,
|
|
200
|
+
}));
|
|
201
|
+
let captured: CapturedMultiAttributeElement | null = null;
|
|
202
|
+
|
|
203
|
+
await runDomEditCommit({
|
|
204
|
+
capture: () => {
|
|
205
|
+
captured = captureMultiAttributeElement(
|
|
206
|
+
iframe?.contentDocument,
|
|
207
|
+
selection,
|
|
208
|
+
activeCompPath,
|
|
209
|
+
entries.map((entry) => entry.fullAttr),
|
|
210
|
+
);
|
|
211
|
+
},
|
|
212
|
+
apply: () => {
|
|
213
|
+
if (!captured) return;
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const nextValue = entry.value === null || entry.value === "" ? null : entry.value;
|
|
216
|
+
setOrRemovePreviewAttribute(captured.element, entry.fullAttr, nextValue);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
persist: () =>
|
|
220
|
+
persistDomEditOperations(selection, ops, {
|
|
221
|
+
label: options.label,
|
|
222
|
+
coalesceKey: commitKey,
|
|
223
|
+
skipRefresh: options.skipRefresh,
|
|
224
|
+
}),
|
|
225
|
+
shouldRevert: () => isLatestCommit(),
|
|
226
|
+
revert: () => {
|
|
227
|
+
if (!captured) return;
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
setOrRemovePreviewAttribute(
|
|
230
|
+
captured.element,
|
|
231
|
+
entry.fullAttr,
|
|
232
|
+
captured.previousValues.get(entry.fullAttr) ?? null,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
onError: (error) => reportDomEditPersistFailure(selection, ops, error, showToast),
|
|
237
|
+
shouldResync: () => isLatestCommit() && !!options.refreshAfter,
|
|
238
|
+
resync: () => refreshDomEditSelectionFromPreview(selection),
|
|
239
|
+
onSettled: options.onSettled,
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
[
|
|
243
|
+
activeCompPath,
|
|
244
|
+
persistDomEditOperations,
|
|
245
|
+
refreshDomEditSelectionFromPreview,
|
|
246
|
+
showToast,
|
|
247
|
+
previewIframeRef,
|
|
248
|
+
],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const handleDomAttributesCommit = useCallback(
|
|
252
|
+
async (selection: DomEditSelection, attrs: Record<string, string>) => {
|
|
253
|
+
await commitDataAttributes(selection, attrs, {
|
|
254
|
+
label: "Edit timing",
|
|
255
|
+
coalescePrefix: "attrs",
|
|
256
|
+
skipRefresh: false,
|
|
257
|
+
refreshAfter: true,
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
[commitDataAttributes],
|
|
261
|
+
);
|
|
262
|
+
|
|
143
263
|
const handleDomAttributeCommit = useCallback(
|
|
144
264
|
async (attr: string, value: string) => {
|
|
145
265
|
await commitDataAttribute(attr, value, {
|
|
@@ -153,11 +273,12 @@ export function useDomEditAttributeCommits({
|
|
|
153
273
|
);
|
|
154
274
|
|
|
155
275
|
const handleDomAttributeLiveCommit = useCallback(
|
|
156
|
-
async (attr: string, value: string | null) => {
|
|
276
|
+
async (attr: string, value: string | null, onSettled?: (ok: boolean) => void) => {
|
|
157
277
|
await commitDataAttribute(attr, value, {
|
|
158
278
|
label: `Edit ${attr.replace(/^(data-)?/, "").replace(/-/g, " ")}`,
|
|
159
279
|
coalescePrefix: "attr-live",
|
|
160
280
|
skipRefresh: true,
|
|
281
|
+
onSettled,
|
|
161
282
|
});
|
|
162
283
|
},
|
|
163
284
|
[commitDataAttribute],
|
|
@@ -223,5 +344,6 @@ export function useDomEditAttributeCommits({
|
|
|
223
344
|
handleDomAttributeCommit,
|
|
224
345
|
handleDomAttributeLiveCommit,
|
|
225
346
|
handleDomHtmlAttributeCommit,
|
|
347
|
+
handleDomAttributesCommit,
|
|
226
348
|
};
|
|
227
349
|
}
|
|
@@ -12,11 +12,7 @@ import {
|
|
|
12
12
|
import { buildDomEditPatchTarget, type DomEditSelection } from "../components/editor/domEditing";
|
|
13
13
|
import { fontFamilyFromAssetPath, type ImportedFontAsset } from "../components/editor/fontAssets";
|
|
14
14
|
import type { EditHistoryKind } from "../utils/editHistory";
|
|
15
|
-
import type {
|
|
16
|
-
CommitDomEditPatchBatches,
|
|
17
|
-
DomEditPatchBatch,
|
|
18
|
-
PersistDomEditOperations,
|
|
19
|
-
} from "./domEditCommitTypes";
|
|
15
|
+
import type { CommitDomEditPatchBatches, PersistDomEditOperations } from "./domEditCommitTypes";
|
|
20
16
|
import type { PatchOperation } from "../utils/sourcePatcher";
|
|
21
17
|
import {
|
|
22
18
|
DomEditPersistUnsafeValueError,
|
|
@@ -27,31 +23,16 @@ import { useDomEditPositionPatchCommit } from "./useDomEditPositionPatchCommit";
|
|
|
27
23
|
import { useDomEditTextCommits } from "./useDomEditTextCommits";
|
|
28
24
|
import { useDomGeometryCommits } from "./useDomGeometryCommits";
|
|
29
25
|
import { useElementLifecycleOps } from "./useElementLifecycleOps";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function readErrorResponseBody(
|
|
43
|
-
response: Response,
|
|
44
|
-
): Promise<{ error?: string; fields?: string[] } | null> {
|
|
45
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
46
|
-
if (!contentType.includes("application/json")) return null;
|
|
47
|
-
return (await response.json().catch(() => null)) as { error?: string; fields?: string[] } | null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function formatPatchRejectionMessage(body: { error?: string; fields?: string[] } | null): string {
|
|
51
|
-
if (!body?.error) return "Couldn't save edit";
|
|
52
|
-
return `Couldn't save edit: ${body.error}${formatFieldsSuffix(body.fields)}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
26
|
+
import {
|
|
27
|
+
AtomicElementPatchConvergenceError,
|
|
28
|
+
batchesAreInlineStyleOnly,
|
|
29
|
+
formatPatchRejectionMessage,
|
|
30
|
+
formatUnsafeFieldList,
|
|
31
|
+
getErrorDetail,
|
|
32
|
+
patchElementBatches,
|
|
33
|
+
readErrorResponseBody,
|
|
34
|
+
} from "./useDomEditCommitsHelpers";
|
|
35
|
+
import { cutoverCommittedOrThrow, type CutoverResult } from "../utils/sdkCutover";
|
|
55
36
|
interface RecordEditInput {
|
|
56
37
|
label: string;
|
|
57
38
|
kind: EditHistoryKind;
|
|
@@ -60,138 +41,6 @@ interface RecordEditInput {
|
|
|
60
41
|
files: Record<string, { before: string; after: string }>;
|
|
61
42
|
}
|
|
62
43
|
|
|
63
|
-
/** Human-readable identifier for a batch patch target (for the unmatched warning). */
|
|
64
|
-
function describeBatchPatchTarget(patch: DomEditPatchBatch["patches"][number]): string {
|
|
65
|
-
return patch.target.id ?? patch.target.hfId ?? patch.target.selector ?? "(unaddressed)";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Surface server-reported unmatched patches. The server atomically refuses the
|
|
70
|
-
* whole multi-file gesture; the caller uses `durable: false` to roll back and
|
|
71
|
-
* reload, so report the refusal without turning it into a second failure.
|
|
72
|
-
*/
|
|
73
|
-
function reportUnmatchedBatchPatches(batch: DomEditPatchBatch, matched: boolean[]): void {
|
|
74
|
-
const unmatchedIds = batch.patches
|
|
75
|
-
.filter((_, index) => matched[index] === false)
|
|
76
|
-
.map(describeBatchPatchTarget);
|
|
77
|
-
if (unmatchedIds.length === 0) return;
|
|
78
|
-
console.warn(
|
|
79
|
-
`[studio] z-index reorder: server could not match ${unmatchedIds.length} patch target(s) in ` +
|
|
80
|
-
`${batch.sourceFile} (the whole z-order gesture will revert on reload):`,
|
|
81
|
-
unmatchedIds.join(", "),
|
|
82
|
-
);
|
|
83
|
-
trackStudioSaveFailure({
|
|
84
|
-
source: "dom_edit",
|
|
85
|
-
error: new Error(`Batch patch target(s) unmatched: ${unmatchedIds.join(", ")}`),
|
|
86
|
-
filePath: batch.sourceFile,
|
|
87
|
-
mutationType: "z-reorder-unmatched",
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface AtomicElementPatchFile {
|
|
92
|
-
sourceFile: string;
|
|
93
|
-
changed: boolean;
|
|
94
|
-
matched?: boolean[];
|
|
95
|
-
before: string;
|
|
96
|
-
after: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
class AtomicElementPatchConvergenceError extends Error {
|
|
100
|
-
constructor(message: string, options?: { cause?: unknown }) {
|
|
101
|
-
super(message, options);
|
|
102
|
-
this.name = "AtomicElementPatchConvergenceError";
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Keep the atomic response contract in one guard so callers do not compose validity.
|
|
107
|
-
// fallow-ignore-next-line complexity
|
|
108
|
-
function isAtomicElementPatchFile(value: unknown): value is AtomicElementPatchFile {
|
|
109
|
-
return (
|
|
110
|
-
typeof value === "object" &&
|
|
111
|
-
value !== null &&
|
|
112
|
-
"sourceFile" in value &&
|
|
113
|
-
typeof value.sourceFile === "string" &&
|
|
114
|
-
"changed" in value &&
|
|
115
|
-
typeof value.changed === "boolean" &&
|
|
116
|
-
(!("matched" in value) ||
|
|
117
|
-
(Array.isArray(value.matched) &&
|
|
118
|
-
value.matched.every((matched) => typeof matched === "boolean"))) &&
|
|
119
|
-
"before" in value &&
|
|
120
|
-
typeof value.before === "string" &&
|
|
121
|
-
"after" in value &&
|
|
122
|
-
typeof value.after === "string" &&
|
|
123
|
-
value.changed === (value.before !== value.after)
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// This is the single client owner for dispatching and validating the aggregate
|
|
128
|
-
// atomic endpoint. Splitting validation from the request would weaken that wire contract.
|
|
129
|
-
// fallow-ignore-next-line complexity
|
|
130
|
-
async function patchElementBatches(projectId: string, batches: DomEditPatchBatch[]) {
|
|
131
|
-
const body = JSON.stringify({ batches });
|
|
132
|
-
try {
|
|
133
|
-
const response = await fetch(
|
|
134
|
-
`/api/projects/${encodeURIComponent(projectId)}/file-mutations/patch-element-batches`,
|
|
135
|
-
{
|
|
136
|
-
method: "POST",
|
|
137
|
-
headers: { "Content-Type": "application/json" },
|
|
138
|
-
body,
|
|
139
|
-
},
|
|
140
|
-
);
|
|
141
|
-
if (!response.ok) {
|
|
142
|
-
const rejection = await readErrorResponseBody(response);
|
|
143
|
-
throw new StudioSaveHttpError(formatPatchRejectionMessage(rejection), response.status);
|
|
144
|
-
}
|
|
145
|
-
const result: unknown = await response.json().catch(() => null);
|
|
146
|
-
if (
|
|
147
|
-
typeof result !== "object" ||
|
|
148
|
-
result === null ||
|
|
149
|
-
!("durable" in result) ||
|
|
150
|
-
typeof result.durable !== "boolean" ||
|
|
151
|
-
!("files" in result) ||
|
|
152
|
-
!Array.isArray(result.files) ||
|
|
153
|
-
result.files.length !== batches.length ||
|
|
154
|
-
!result.files.every(isAtomicElementPatchFile) ||
|
|
155
|
-
(!result.durable && result.files.some((file) => file.changed))
|
|
156
|
-
) {
|
|
157
|
-
throw new StudioSaveHttpError("Invalid atomic element patch response", 502);
|
|
158
|
-
}
|
|
159
|
-
const files = result.files.map((file, index) => {
|
|
160
|
-
const batch = batches[index];
|
|
161
|
-
const matched = file.matched ?? [];
|
|
162
|
-
if (
|
|
163
|
-
!batch ||
|
|
164
|
-
file.sourceFile !== batch.sourceFile ||
|
|
165
|
-
(matched.length !== 0 && matched.length !== batch.patches.length)
|
|
166
|
-
) {
|
|
167
|
-
throw new StudioSaveHttpError("Invalid atomic element patch response", 502);
|
|
168
|
-
}
|
|
169
|
-
reportUnmatchedBatchPatches(batch, matched);
|
|
170
|
-
return {
|
|
171
|
-
...file,
|
|
172
|
-
matched,
|
|
173
|
-
allMatched: matched.length === batch.patches.length && matched.every(Boolean),
|
|
174
|
-
};
|
|
175
|
-
});
|
|
176
|
-
return { durable: result.durable, files };
|
|
177
|
-
} catch (error) {
|
|
178
|
-
throw new AtomicElementPatchConvergenceError(getErrorDetail(error), { cause: error });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* A batch is reload-skippable only when it is style-only: every operation is an
|
|
184
|
-
* `inline-style` write. The z-reorder commit applies those exact styles to the
|
|
185
|
-
* live iframe DOM synchronously, so persisting them adds nothing the preview
|
|
186
|
-
* doesn't already show. Any other op type (attribute / text-content / …) can
|
|
187
|
-
* have server-side semantics the live DOM hasn't mirrored — reload for those.
|
|
188
|
-
*/
|
|
189
|
-
function batchesAreInlineStyleOnly(batches: DomEditPatchBatch[]): boolean {
|
|
190
|
-
return batches.every((batch) =>
|
|
191
|
-
batch.patches.every((patch) => patch.operations.every((op) => op.type === "inline-style")),
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
44
|
export interface UseDomEditCommitsParams {
|
|
196
45
|
activeCompPath: string | null;
|
|
197
46
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
@@ -222,16 +71,20 @@ export interface UseDomEditCommitsParams {
|
|
|
222
71
|
* path, whose session is already current) so a later SDK edit doesn't
|
|
223
72
|
* serialize the pre-write doc and revert the server's change. */
|
|
224
73
|
forceReloadSdkSession?: () => void;
|
|
225
|
-
/** Stage 7 Step 3c: called before the server-side patch path
|
|
74
|
+
/** Stage 7 Step 3c: called before the server-side patch path. */
|
|
226
75
|
onTrySdkPersist?: (
|
|
227
76
|
selection: DomEditSelection,
|
|
228
77
|
operations: PatchOperation[],
|
|
229
78
|
originalContent: string,
|
|
230
79
|
targetPath: string,
|
|
231
80
|
options?: { label?: string; coalesceKey?: string; skipRefresh?: boolean },
|
|
232
|
-
) => Promise<
|
|
233
|
-
/** Stage 7 §3.1: called before the server-side delete path
|
|
234
|
-
onTrySdkDelete?: (
|
|
81
|
+
) => Promise<CutoverResult>;
|
|
82
|
+
/** Stage 7 §3.1: called before the server-side delete path. */
|
|
83
|
+
onTrySdkDelete?: (
|
|
84
|
+
hfId: string,
|
|
85
|
+
originalContent: string,
|
|
86
|
+
targetPath: string,
|
|
87
|
+
) => Promise<CutoverResult>;
|
|
235
88
|
/** Resolver-shadow tripwire for z-index reorder targets (telemetry-only, decoupled from cutover). */
|
|
236
89
|
onReorderShadow?: (targets: string[]) => void;
|
|
237
90
|
}
|
|
@@ -326,18 +179,17 @@ export function useDomEditCommits({
|
|
|
326
179
|
// Skip the SDK path when prepareContent is set (e.g. @font-face injection
|
|
327
180
|
// for a custom font): sdkCutoverPersist serializes only the patched DOM
|
|
328
181
|
// and would drop the injected content. Let the server path run prepareContent.
|
|
329
|
-
if (
|
|
330
|
-
onTrySdkPersist
|
|
331
|
-
!options?.prepareContent &&
|
|
332
|
-
(await onTrySdkPersist(selection, operations, originalContent, targetPath, {
|
|
182
|
+
if (onTrySdkPersist && !options?.prepareContent) {
|
|
183
|
+
const cutover = await onTrySdkPersist(selection, operations, originalContent, targetPath, {
|
|
333
184
|
label: options?.label,
|
|
334
185
|
coalesceKey: options?.coalesceKey,
|
|
335
186
|
skipRefresh: options?.skipRefresh,
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
187
|
+
});
|
|
188
|
+
if (cutoverCommittedOrThrow(cutover)) {
|
|
189
|
+
// SDK handled it — its in-memory doc is already current, so do NOT
|
|
190
|
+
// forceReload (that would echo-reload the session we just wrote).
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
341
193
|
}
|
|
342
194
|
|
|
343
195
|
// Mark the save timestamp before the file write so the SSE file-change
|
|
@@ -523,6 +375,7 @@ export function useDomEditCommits({
|
|
|
523
375
|
handleDomAttributeCommit,
|
|
524
376
|
handleDomAttributeLiveCommit,
|
|
525
377
|
handleDomHtmlAttributeCommit,
|
|
378
|
+
handleDomAttributesCommit,
|
|
526
379
|
handleDomTextCommit,
|
|
527
380
|
commitDomTextFields,
|
|
528
381
|
handleDomTextFieldStyleCommit,
|
|
@@ -585,6 +438,7 @@ export function useDomEditCommits({
|
|
|
585
438
|
handleDomAttributeCommit,
|
|
586
439
|
handleDomAttributeLiveCommit,
|
|
587
440
|
handleDomHtmlAttributeCommit,
|
|
441
|
+
handleDomAttributesCommit,
|
|
588
442
|
handleDomTextCommit,
|
|
589
443
|
commitDomTextFields,
|
|
590
444
|
handleDomTextFieldStyleCommit,
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { StudioSaveHttpError, trackStudioSaveFailure } from "../utils/studioSaveDiagnostics";
|
|
2
|
+
import type { DomEditPatchBatch } from "./domEditCommitTypes";
|
|
3
|
+
import { formatFieldsSuffix } from "./gsapScriptCommitHelpers";
|
|
4
|
+
|
|
5
|
+
export function formatUnsafeFieldList(fields: Array<{ path: string }>): string {
|
|
6
|
+
return fields.map((field) => field.path).join(", ");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getErrorDetail(error: unknown): string {
|
|
10
|
+
return error instanceof Error ? error.message : String(error);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function readErrorResponseBody(
|
|
14
|
+
response: Response,
|
|
15
|
+
): Promise<{ error?: string; fields?: string[] } | null> {
|
|
16
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
17
|
+
if (!contentType.includes("application/json")) return null;
|
|
18
|
+
return (await response.json().catch(() => null)) as { error?: string; fields?: string[] } | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function formatPatchRejectionMessage(
|
|
22
|
+
body: { error?: string; fields?: string[] } | null,
|
|
23
|
+
): string {
|
|
24
|
+
if (!body?.error) return "Couldn't save edit";
|
|
25
|
+
return `Couldn't save edit: ${body.error}${formatFieldsSuffix(body.fields)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Human-readable identifier for a batch patch target (for the unmatched warning). */
|
|
29
|
+
function describeBatchPatchTarget(patch: DomEditPatchBatch["patches"][number]): string {
|
|
30
|
+
return patch.target.id ?? patch.target.hfId ?? patch.target.selector ?? "(unaddressed)";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Surface server-reported unmatched patches. The server atomically refuses the
|
|
35
|
+
* whole multi-file gesture; the caller uses `durable: false` to roll back and
|
|
36
|
+
* reload, so report the refusal without turning it into a second failure.
|
|
37
|
+
*/
|
|
38
|
+
function reportUnmatchedBatchPatches(batch: DomEditPatchBatch, matched: boolean[]): void {
|
|
39
|
+
const unmatchedIds = batch.patches
|
|
40
|
+
.filter((_, index) => matched[index] === false)
|
|
41
|
+
.map(describeBatchPatchTarget);
|
|
42
|
+
if (unmatchedIds.length === 0) return;
|
|
43
|
+
console.warn(
|
|
44
|
+
`[studio] z-index reorder: server could not match ${unmatchedIds.length} patch target(s) in ` +
|
|
45
|
+
`${batch.sourceFile} (the whole z-order gesture will revert on reload):`,
|
|
46
|
+
unmatchedIds.join(", "),
|
|
47
|
+
);
|
|
48
|
+
trackStudioSaveFailure({
|
|
49
|
+
source: "dom_edit",
|
|
50
|
+
error: new Error(`Batch patch target(s) unmatched: ${unmatchedIds.join(", ")}`),
|
|
51
|
+
filePath: batch.sourceFile,
|
|
52
|
+
mutationType: "z-reorder-unmatched",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface AtomicElementPatchFile {
|
|
57
|
+
sourceFile: string;
|
|
58
|
+
changed: boolean;
|
|
59
|
+
matched?: boolean[];
|
|
60
|
+
before: string;
|
|
61
|
+
after: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class AtomicElementPatchConvergenceError extends Error {
|
|
65
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
66
|
+
super(message, options);
|
|
67
|
+
this.name = "AtomicElementPatchConvergenceError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Keep the atomic response contract in one guard so callers do not compose validity.
|
|
72
|
+
// fallow-ignore-next-line complexity
|
|
73
|
+
function isAtomicElementPatchFile(value: unknown): value is AtomicElementPatchFile {
|
|
74
|
+
return (
|
|
75
|
+
typeof value === "object" &&
|
|
76
|
+
value !== null &&
|
|
77
|
+
"sourceFile" in value &&
|
|
78
|
+
typeof value.sourceFile === "string" &&
|
|
79
|
+
"changed" in value &&
|
|
80
|
+
typeof value.changed === "boolean" &&
|
|
81
|
+
(!("matched" in value) ||
|
|
82
|
+
(Array.isArray(value.matched) &&
|
|
83
|
+
value.matched.every((matched) => typeof matched === "boolean"))) &&
|
|
84
|
+
"before" in value &&
|
|
85
|
+
typeof value.before === "string" &&
|
|
86
|
+
"after" in value &&
|
|
87
|
+
typeof value.after === "string" &&
|
|
88
|
+
value.changed === (value.before !== value.after)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// This is the single client owner for dispatching and validating the aggregate
|
|
93
|
+
// atomic endpoint. Splitting validation from the request would weaken that wire contract.
|
|
94
|
+
// fallow-ignore-next-line complexity
|
|
95
|
+
export async function patchElementBatches(projectId: string, batches: DomEditPatchBatch[]) {
|
|
96
|
+
const body = JSON.stringify({ batches });
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(
|
|
99
|
+
`/api/projects/${encodeURIComponent(projectId)}/file-mutations/patch-element-batches`,
|
|
100
|
+
{
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const rejection = await readErrorResponseBody(response);
|
|
108
|
+
throw new StudioSaveHttpError(formatPatchRejectionMessage(rejection), response.status);
|
|
109
|
+
}
|
|
110
|
+
const result: unknown = await response.json().catch(() => null);
|
|
111
|
+
if (
|
|
112
|
+
typeof result !== "object" ||
|
|
113
|
+
result === null ||
|
|
114
|
+
!("durable" in result) ||
|
|
115
|
+
typeof result.durable !== "boolean" ||
|
|
116
|
+
!("files" in result) ||
|
|
117
|
+
!Array.isArray(result.files) ||
|
|
118
|
+
result.files.length !== batches.length ||
|
|
119
|
+
!result.files.every(isAtomicElementPatchFile) ||
|
|
120
|
+
(!result.durable && result.files.some((file) => file.changed))
|
|
121
|
+
) {
|
|
122
|
+
throw new StudioSaveHttpError("Invalid atomic element patch response", 502);
|
|
123
|
+
}
|
|
124
|
+
const files = result.files.map((file, index) => {
|
|
125
|
+
const batch = batches[index];
|
|
126
|
+
const matched = file.matched ?? [];
|
|
127
|
+
if (
|
|
128
|
+
!batch ||
|
|
129
|
+
file.sourceFile !== batch.sourceFile ||
|
|
130
|
+
(matched.length !== 0 && matched.length !== batch.patches.length)
|
|
131
|
+
) {
|
|
132
|
+
throw new StudioSaveHttpError("Invalid atomic element patch response", 502);
|
|
133
|
+
}
|
|
134
|
+
reportUnmatchedBatchPatches(batch, matched);
|
|
135
|
+
return {
|
|
136
|
+
...file,
|
|
137
|
+
matched,
|
|
138
|
+
allMatched: matched.length === batch.patches.length && matched.every(Boolean),
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
return { durable: result.durable, files };
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new AtomicElementPatchConvergenceError(getErrorDetail(error), { cause: error });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A batch is reload-skippable only when it is style-only: every operation is an
|
|
149
|
+
* `inline-style` write. The z-reorder commit applies those exact styles to the
|
|
150
|
+
* live iframe DOM synchronously, so persisting them adds nothing the preview
|
|
151
|
+
* doesn't already show. Any other op type (attribute / text-content / …) can
|
|
152
|
+
* have server-side semantics the live DOM hasn't mirrored — reload for those.
|
|
153
|
+
*/
|
|
154
|
+
export function batchesAreInlineStyleOnly(batches: DomEditPatchBatch[]): boolean {
|
|
155
|
+
return batches.every((batch) =>
|
|
156
|
+
batch.patches.every((patch) => patch.operations.every((op) => op.type === "inline-style")),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -7,7 +7,7 @@ import type { RightPanelTab } from "../utils/studioHelpers";
|
|
|
7
7
|
import type { PatchTarget } from "../utils/sourcePatcher";
|
|
8
8
|
import type { SidebarTab } from "../components/sidebar/LeftSidebar";
|
|
9
9
|
import type { Composition } from "@hyperframes/sdk";
|
|
10
|
-
import { sdkCutoverPersist, sdkDeletePersist } from "../utils/sdkCutover";
|
|
10
|
+
import { sdkCutoverPersist, sdkDeletePersist, type PublishSdkSession } from "../utils/sdkCutover";
|
|
11
11
|
import { runResolverShadow, recordResolverParity } from "../utils/sdkResolverShadow";
|
|
12
12
|
import { useAskAgentModal } from "./useAskAgentModal";
|
|
13
13
|
import { useDomSelection } from "./useDomSelection";
|
|
@@ -67,6 +67,7 @@ export interface UseDomEditSessionParams {
|
|
|
67
67
|
selectSidebarTab?: (tab: SidebarTab) => void;
|
|
68
68
|
getSidebarTab?: () => SidebarTab;
|
|
69
69
|
sdkSession?: Composition | null;
|
|
70
|
+
publishSdkSession?: PublishSdkSession;
|
|
70
71
|
forceReloadSdkSession?: () => void;
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -108,10 +109,10 @@ export function useDomEditSession({
|
|
|
108
109
|
selectSidebarTab,
|
|
109
110
|
getSidebarTab,
|
|
110
111
|
sdkSession,
|
|
112
|
+
publishSdkSession,
|
|
111
113
|
forceReloadSdkSession,
|
|
112
114
|
}: UseDomEditSessionParams) {
|
|
113
115
|
void _setRefreshKey;
|
|
114
|
-
|
|
115
116
|
// ── Selection ──
|
|
116
117
|
|
|
117
118
|
const {
|
|
@@ -178,7 +179,6 @@ export function useDomEditSession({
|
|
|
178
179
|
previewDocumentVersion,
|
|
179
180
|
refreshDomEditSelectionFromPreview,
|
|
180
181
|
});
|
|
181
|
-
|
|
182
182
|
// ── GSAP cache (hoisted so both useGsapScriptCommits and useDomEditWiring share the same instance) ──
|
|
183
183
|
|
|
184
184
|
const { version: gsapCacheVersion, bump: bumpGsapCache } = useGsapCacheVersion();
|
|
@@ -217,6 +217,7 @@ export function useDomEditSession({
|
|
|
217
217
|
onFileContentChanged: updateEditingFileContent,
|
|
218
218
|
showToast,
|
|
219
219
|
sdkSession,
|
|
220
|
+
publishSdkSession,
|
|
220
221
|
writeProjectFile,
|
|
221
222
|
forceReloadSdkSession,
|
|
222
223
|
});
|
|
@@ -229,6 +230,7 @@ export function useDomEditSession({
|
|
|
229
230
|
handleDomAttributeCommit,
|
|
230
231
|
handleDomAttributeLiveCommit,
|
|
231
232
|
handleDomHtmlAttributeCommit,
|
|
233
|
+
handleDomAttributesCommit,
|
|
232
234
|
handleDomTextCommit,
|
|
233
235
|
handleDomTextFieldStyleCommit,
|
|
234
236
|
handleDomAddTextField,
|
|
@@ -260,8 +262,13 @@ export function useDomEditSession({
|
|
|
260
262
|
? (selection, operations, originalContent, targetPath, options) => {
|
|
261
263
|
// Resolver shadow runs regardless of the cutover flag — decoupled tripwire.
|
|
262
264
|
// Pass originalContent so the runtime-node filter can suppress hf-ids
|
|
263
|
-
// absent from source (script-created nodes the SDK can't model)
|
|
264
|
-
|
|
265
|
+
// absent from source (script-created nodes the SDK can't model), and
|
|
266
|
+
// the paths so cross-file edits (session models only the active comp)
|
|
267
|
+
// skip instead of emitting structural element_not_found noise.
|
|
268
|
+
runResolverShadow(sdkSession, selection.hfId, operations, originalContent, {
|
|
269
|
+
targetPath,
|
|
270
|
+
compositionPath: activeCompPath,
|
|
271
|
+
});
|
|
265
272
|
return sdkCutoverPersist(
|
|
266
273
|
selection,
|
|
267
274
|
operations,
|
|
@@ -274,6 +281,8 @@ export function useDomEditSession({
|
|
|
274
281
|
reloadPreview,
|
|
275
282
|
domEditSaveTimestampRef,
|
|
276
283
|
compositionPath: activeCompPath,
|
|
284
|
+
readProjectFile,
|
|
285
|
+
publishSession: publishSdkSession,
|
|
277
286
|
},
|
|
278
287
|
options,
|
|
279
288
|
);
|
|
@@ -287,6 +296,8 @@ export function useDomEditSession({
|
|
|
287
296
|
reloadPreview,
|
|
288
297
|
domEditSaveTimestampRef,
|
|
289
298
|
compositionPath: activeCompPath,
|
|
299
|
+
readProjectFile,
|
|
300
|
+
publishSession: publishSdkSession,
|
|
290
301
|
})
|
|
291
302
|
: undefined,
|
|
292
303
|
// Resolver shadow for the z-index reorder edit: it takes the server path (no
|
|
@@ -524,6 +535,7 @@ export function useDomEditSession({
|
|
|
524
535
|
handleDomAttributeCommit,
|
|
525
536
|
handleDomAttributeLiveCommit,
|
|
526
537
|
handleDomHtmlAttributeCommit,
|
|
538
|
+
handleDomAttributesCommit,
|
|
527
539
|
handleDomPathOffsetCommit: handleGsapAwarePathOffsetCommit,
|
|
528
540
|
handleDomGroupPathOffsetCommit: handleGsapAwareGroupPathOffsetCommit,
|
|
529
541
|
handleDomZIndexReorderCommit,
|