@hyperframes/studio 0.7.57 → 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--Z69cEkE.js → hyperframes-player-Bm07FLkl.js} +1 -1
- package/dist/assets/{index-KsfE1bUu.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-Bf1x1y8H.js → index-FvzmPhfG.js} +1 -1
- package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
- package/dist/chunk-OBAG3GWK.js.map +1 -0
- package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
- package/dist/index.d.ts +27 -7
- package/dist/index.html +2 -2
- package/dist/index.js +18029 -11563
- 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 +6 -6
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioFeedbackBar.tsx +2 -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/CanvasContextMenu.test.tsx +27 -14
- package/src/components/editor/CanvasContextMenu.tsx +71 -6
- package/src/components/editor/DomEditOverlay.tsx +5 -2
- 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/LayersPanel.tsx +110 -99
- 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/canvasContextMenuZOrder.test.ts +57 -0
- package/src/components/editor/canvasContextMenuZOrder.ts +40 -1
- package/src/components/editor/domEditingDom.ts +5 -6
- package/src/components/editor/domEditingElement.ts +15 -32
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/layersPanelSort.ts +80 -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/editor/useLayerRevealOverride.test.ts +169 -0
- package/src/components/editor/useLayerRevealOverride.ts +389 -0
- package/src/components/nle/PreviewOverlays.tsx +27 -2
- package/src/components/nle/TimelinePane.test.ts +3 -0
- package/src/components/nle/TimelinePane.tsx +11 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
- package/src/components/nle/zLaneGesture.test.ts +176 -0
- package/src/components/nle/zLaneGesture.ts +77 -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/domEditCommitTypes.ts +17 -1
- package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
- package/src/hooks/fetchStubTestUtils.ts +16 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineEditingHelpers.test.ts +92 -1
- package/src/hooks/timelineEditingHelpers.ts +32 -15
- package/src/hooks/timelineMoveAdapter.ts +2 -1
- package/src/hooks/timelineTimingSync.test.ts +703 -33
- package/src/hooks/timelineTimingSync.ts +282 -97
- package/src/hooks/timelineTrackVisibility.test.ts +55 -1
- package/src/hooks/timelineTrackVisibility.ts +31 -13
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useClipboard.ts +19 -17
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.test.tsx +316 -38
- package/src/hooks/useDomEditCommits.ts +81 -162
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.test.tsx +3 -1
- package/src/hooks/useDomEditSession.ts +18 -6
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useElementLifecycleOps.test.tsx +426 -60
- package/src/hooks/useElementLifecycleOps.ts +182 -84
- 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/usePreviewPersistence.ts +2 -2
- 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 +306 -155
- package/src/hooks/useTimelineEditing.ts +111 -101
- package/src/hooks/useTimelineEditingTypes.ts +4 -1
- package/src/hooks/useTimelineGroupEditing.ts +51 -5
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/Timeline.test.ts +31 -21
- package/src/player/components/Timeline.tsx +62 -91
- package/src/player/components/TimelineCanvas.tsx +36 -5
- package/src/player/components/TimelineLanes.tsx +32 -10
- package/src/player/components/TimelineOverlays.tsx +36 -0
- package/src/player/components/TimelineRuler.tsx +18 -5
- package/src/player/components/TrackGapContextMenu.tsx +116 -0
- package/src/player/components/timelineCallbacks.ts +4 -1
- package/src/player/components/timelineClipDragCommit.test.ts +279 -3
- package/src/player/components/timelineClipDragCommit.ts +173 -71
- package/src/player/components/timelineGapCommit.test.ts +163 -0
- package/src/player/components/timelineGapCommit.ts +103 -0
- package/src/player/components/timelineGaps.test.ts +221 -0
- package/src/player/components/timelineGaps.ts +182 -0
- package/src/player/components/timelineLayout.test.ts +2 -1
- package/src/player/components/timelineLayout.ts +22 -6
- package/src/player/components/timelineMarquee.test.ts +20 -10
- package/src/player/components/timelineMarquee.ts +9 -2
- package/src/player/components/timelineStackingSync.test.ts +42 -1
- package/src/player/components/timelineStackingSync.ts +31 -16
- package/src/player/components/timelineZMirror.test.ts +667 -0
- package/src/player/components/timelineZMirror.ts +393 -0
- package/src/player/components/timelineZoom.test.ts +2 -2
- package/src/player/components/timelineZoom.ts +7 -3
- package/src/player/components/useTimelineClipDrag.ts +1 -1
- package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
- package/src/player/components/useTimelineGapHighlights.ts +110 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -3
- package/src/player/components/useTimelineRangeSelection.ts +6 -4
- package/src/player/components/useTimelineScrollViewport.ts +73 -0
- package/src/player/components/useTimelineStackingSync.test.tsx +0 -1
- package/src/player/components/useTimelineStackingSync.ts +5 -3
- package/src/player/components/useTimelineTrackDerivations.ts +38 -0
- package/src/player/components/useTrackGapMenu.ts +155 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
- package/src/player/hooks/useExpandedTimelineElements.ts +12 -1
- package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
- package/src/player/lib/layerOrdering.test.ts +1 -15
- package/src/player/lib/layerOrdering.ts +5 -28
- package/src/player/lib/timelineDOM.test.ts +72 -0
- package/src/player/lib/timelineDOM.ts +3 -0
- package/src/player/lib/timelineElementHelpers.ts +35 -11
- package/src/player/store/playerStore.ts +12 -1
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/telemetry/events.test.ts +10 -0
- package/src/telemetry/events.ts +1 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/domEditSaveQueue.ts +4 -3
- package/src/utils/editHistory.test.ts +82 -0
- package/src/utils/editHistory.ts +14 -2
- package/src/utils/gsapSoftReload.test.ts +54 -115
- package/src/utils/gsapSoftReload.ts +42 -152
- package/src/utils/gsapUndoRestore.test.ts +269 -0
- package/src/utils/gsapUndoRestore.ts +269 -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/sourceScopedSelectorIndex.ts +29 -0
- 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-DfmYkU44.js +0 -423
- package/dist/assets/index-_pqzyxB1.css +0 -1
- package/dist/chunk-5QSIMBEJ.js.map +0 -1
- /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
|
@@ -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,32 +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
|
-
return error instanceof Error ? error.message : String(error);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function readErrorResponseBody(
|
|
44
|
-
response: Response,
|
|
45
|
-
): Promise<{ error?: string; fields?: string[] } | null> {
|
|
46
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
47
|
-
if (!contentType.includes("application/json")) return null;
|
|
48
|
-
return (await response.json().catch(() => null)) as { error?: string; fields?: string[] } | null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function formatPatchRejectionMessage(body: { error?: string; fields?: string[] } | null): string {
|
|
52
|
-
if (!body?.error) return "Couldn't save edit";
|
|
53
|
-
return `Couldn't save edit: ${body.error}${formatFieldsSuffix(body.fields)}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
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";
|
|
56
36
|
interface RecordEditInput {
|
|
57
37
|
label: string;
|
|
58
38
|
kind: EditHistoryKind;
|
|
@@ -61,88 +41,11 @@ interface RecordEditInput {
|
|
|
61
41
|
files: Record<string, { before: string; after: string }>;
|
|
62
42
|
}
|
|
63
43
|
|
|
64
|
-
/** Human-readable identifier for a batch patch target (for the unmatched warning). */
|
|
65
|
-
function describeBatchPatchTarget(patch: DomEditPatchBatch["patches"][number]): string {
|
|
66
|
-
return patch.target.id ?? patch.target.hfId ?? patch.target.selector ?? "(unaddressed)";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Surface server-reported unmatched patches. The matched subset already
|
|
71
|
-
* persisted, so this must NOT throw (a throw would roll back applied state) —
|
|
72
|
-
* warn and emit save-failure telemetry with a distinct reason instead.
|
|
73
|
-
*/
|
|
74
|
-
function reportUnmatchedBatchPatches(batch: DomEditPatchBatch, matched: boolean[]): void {
|
|
75
|
-
const unmatchedIds = batch.patches
|
|
76
|
-
.filter((_, index) => matched[index] === false)
|
|
77
|
-
.map(describeBatchPatchTarget);
|
|
78
|
-
if (unmatchedIds.length === 0) return;
|
|
79
|
-
console.warn(
|
|
80
|
-
`[studio] z-index reorder: server could not match ${unmatchedIds.length} patch target(s) in ` +
|
|
81
|
-
`${batch.sourceFile} (their z-order will revert on reload):`,
|
|
82
|
-
unmatchedIds.join(", "),
|
|
83
|
-
);
|
|
84
|
-
trackStudioSaveFailure({
|
|
85
|
-
source: "dom_edit",
|
|
86
|
-
error: new Error(`Batch patch target(s) unmatched: ${unmatchedIds.join(", ")}`),
|
|
87
|
-
filePath: batch.sourceFile,
|
|
88
|
-
mutationType: "z-reorder-unmatched",
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function patchElementBatch(projectId: string, batch: DomEditPatchBatch) {
|
|
93
|
-
const before = await readProjectFileContent(projectId, batch.sourceFile);
|
|
94
|
-
const response = await fetch(
|
|
95
|
-
`/api/projects/${encodeURIComponent(projectId)}/file-mutations/patch-elements-batch/${encodeURIComponent(batch.sourceFile)}`,
|
|
96
|
-
{
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: { "Content-Type": "application/json" },
|
|
99
|
-
body: JSON.stringify({ patches: batch.patches }),
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
if (!response.ok) {
|
|
103
|
-
const rejection = await readErrorResponseBody(response);
|
|
104
|
-
throw new StudioSaveHttpError(formatPatchRejectionMessage(rejection), response.status);
|
|
105
|
-
}
|
|
106
|
-
const result = (await response.json()) as {
|
|
107
|
-
changed?: boolean;
|
|
108
|
-
matched?: boolean[];
|
|
109
|
-
content?: string;
|
|
110
|
-
};
|
|
111
|
-
if (Array.isArray(result.matched)) reportUnmatchedBatchPatches(batch, result.matched);
|
|
112
|
-
return {
|
|
113
|
-
sourceFile: batch.sourceFile,
|
|
114
|
-
changed: result.changed === true,
|
|
115
|
-
// Skip-reload safety: the persist is only provably in sync with the live
|
|
116
|
-
// DOM when the server confirmed EVERY patch target matched. A missing /
|
|
117
|
-
// short matched[] is treated as unknown (false) so the caller falls back
|
|
118
|
-
// to reloading rather than silently diverging from disk.
|
|
119
|
-
allMatched:
|
|
120
|
-
Array.isArray(result.matched) &&
|
|
121
|
-
result.matched.length === batch.patches.length &&
|
|
122
|
-
result.matched.every(Boolean),
|
|
123
|
-
before,
|
|
124
|
-
after: typeof result.content === "string" ? result.content : before,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* A batch is reload-skippable only when it is style-only: every operation is an
|
|
130
|
-
* `inline-style` write. The z-reorder commit applies those exact styles to the
|
|
131
|
-
* live iframe DOM synchronously, so persisting them adds nothing the preview
|
|
132
|
-
* doesn't already show. Any other op type (attribute / text-content / …) can
|
|
133
|
-
* have server-side semantics the live DOM hasn't mirrored — reload for those.
|
|
134
|
-
*/
|
|
135
|
-
function batchesAreInlineStyleOnly(batches: DomEditPatchBatch[]): boolean {
|
|
136
|
-
return batches.every((batch) =>
|
|
137
|
-
batch.patches.every((patch) => patch.operations.every((op) => op.type === "inline-style")),
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
44
|
export interface UseDomEditCommitsParams {
|
|
142
45
|
activeCompPath: string | null;
|
|
143
46
|
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
144
47
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
145
|
-
queueDomEditSave: (save: () => Promise<
|
|
48
|
+
queueDomEditSave: <T>(save: () => Promise<T>) => Promise<T>;
|
|
146
49
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
147
50
|
domEditSaveTimestampRef: React.MutableRefObject<number>;
|
|
148
51
|
editHistory: { recordEdit: (entry: RecordEditInput) => Promise<void> };
|
|
@@ -168,16 +71,20 @@ export interface UseDomEditCommitsParams {
|
|
|
168
71
|
* path, whose session is already current) so a later SDK edit doesn't
|
|
169
72
|
* serialize the pre-write doc and revert the server's change. */
|
|
170
73
|
forceReloadSdkSession?: () => void;
|
|
171
|
-
/** Stage 7 Step 3c: called before the server-side patch path
|
|
74
|
+
/** Stage 7 Step 3c: called before the server-side patch path. */
|
|
172
75
|
onTrySdkPersist?: (
|
|
173
76
|
selection: DomEditSelection,
|
|
174
77
|
operations: PatchOperation[],
|
|
175
78
|
originalContent: string,
|
|
176
79
|
targetPath: string,
|
|
177
80
|
options?: { label?: string; coalesceKey?: string; skipRefresh?: boolean },
|
|
178
|
-
) => Promise<
|
|
179
|
-
/** Stage 7 §3.1: called before the server-side delete path
|
|
180
|
-
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>;
|
|
181
88
|
/** Resolver-shadow tripwire for z-index reorder targets (telemetry-only, decoupled from cutover). */
|
|
182
89
|
onReorderShadow?: (targets: string[]) => void;
|
|
183
90
|
}
|
|
@@ -272,18 +179,17 @@ export function useDomEditCommits({
|
|
|
272
179
|
// Skip the SDK path when prepareContent is set (e.g. @font-face injection
|
|
273
180
|
// for a custom font): sdkCutoverPersist serializes only the patched DOM
|
|
274
181
|
// and would drop the injected content. Let the server path run prepareContent.
|
|
275
|
-
if (
|
|
276
|
-
onTrySdkPersist
|
|
277
|
-
!options?.prepareContent &&
|
|
278
|
-
(await onTrySdkPersist(selection, operations, originalContent, targetPath, {
|
|
182
|
+
if (onTrySdkPersist && !options?.prepareContent) {
|
|
183
|
+
const cutover = await onTrySdkPersist(selection, operations, originalContent, targetPath, {
|
|
279
184
|
label: options?.label,
|
|
280
185
|
coalesceKey: options?.coalesceKey,
|
|
281
186
|
skipRefresh: options?.skipRefresh,
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
}
|
|
287
193
|
}
|
|
288
194
|
|
|
289
195
|
// Mark the save timestamp before the file write so the SSE file-change
|
|
@@ -382,48 +288,59 @@ export function useDomEditCommits({
|
|
|
382
288
|
|
|
383
289
|
const commitDomEditPatchBatches: CommitDomEditPatchBatches = useCallback(
|
|
384
290
|
(batches, options) =>
|
|
385
|
-
queueDomEditSave(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
`DOM patch contains unsafe values: ${formatUnsafeFieldList(unsafeFields)}`,
|
|
395
|
-
{ alreadyToasted: true },
|
|
291
|
+
queueDomEditSave(
|
|
292
|
+
// One queued transaction owns validation, persistence, history, reload,
|
|
293
|
+
// and its durable result; splitting those phases risks partial commits.
|
|
294
|
+
// fallow-ignore-next-line complexity
|
|
295
|
+
async () => {
|
|
296
|
+
const pid = projectIdRef.current;
|
|
297
|
+
if (!pid) throw new Error("No active project");
|
|
298
|
+
const unsafeFields = batches.flatMap((batch) =>
|
|
299
|
+
batch.patches.flatMap((patch) => findUnsafeDomPatchValues(patch)),
|
|
396
300
|
);
|
|
397
|
-
|
|
301
|
+
if (unsafeFields.length > 0) {
|
|
302
|
+
showToast("Couldn't save edit because it contains invalid layout values", "error");
|
|
303
|
+
throw new DomEditPersistUnsafeValueError(
|
|
304
|
+
`DOM patch contains unsafe values: ${formatUnsafeFieldList(unsafeFields)}`,
|
|
305
|
+
{ alreadyToasted: true },
|
|
306
|
+
);
|
|
307
|
+
}
|
|
398
308
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
309
|
+
domEditSaveTimestampRef.current = Date.now();
|
|
310
|
+
const atomicResult = await patchElementBatches(pid, batches);
|
|
311
|
+
const allMatched =
|
|
312
|
+
atomicResult.durable && atomicResult.files.every((result) => result.allMatched);
|
|
313
|
+
const files = Object.fromEntries(
|
|
314
|
+
atomicResult.files
|
|
315
|
+
.filter((result) => result.changed)
|
|
316
|
+
.map((result) => [result.sourceFile, { before: result.before, after: result.after }]),
|
|
317
|
+
);
|
|
318
|
+
const changed = Object.keys(files).length > 0;
|
|
319
|
+
if (changed) {
|
|
320
|
+
await editHistory.recordEdit({
|
|
321
|
+
label: options.label,
|
|
322
|
+
kind: "manual",
|
|
323
|
+
coalesceKey: options.coalesceKey,
|
|
324
|
+
coalesceMs: options.coalesceMs,
|
|
325
|
+
files,
|
|
326
|
+
});
|
|
327
|
+
forceReloadSdkSession?.();
|
|
328
|
+
}
|
|
329
|
+
const durable = allMatched;
|
|
330
|
+
// A z-only reorder already applied its inline styles to the live iframe
|
|
331
|
+
// DOM (and the store) synchronously, so remounting the iframe here only
|
|
332
|
+
// produces a visible blink. Skip the reload when the caller asked for it
|
|
333
|
+
// AND the persist is provably in sync: style-only ops, every target
|
|
334
|
+
// matched. Any unmatched patch means the live DOM now shows state disk
|
|
335
|
+
// doesn't hold — reload so the preview reconverges. (The SSE/file-watcher
|
|
336
|
+
// reload is independently suppressed by domEditSaveTimestampRef above.)
|
|
337
|
+
const skipSafe =
|
|
338
|
+
options.skipReload === true && batchesAreInlineStyleOnly(batches) && durable;
|
|
339
|
+
if (!durable || (changed && !skipSafe)) reloadPreview();
|
|
340
|
+
return { durable, allMatched, changed };
|
|
341
|
+
},
|
|
342
|
+
).catch((error) => {
|
|
343
|
+
if (error instanceof AtomicElementPatchConvergenceError) reloadPreview();
|
|
427
344
|
const alreadyToasted =
|
|
428
345
|
(error instanceof StudioSaveHttpError ||
|
|
429
346
|
error instanceof DomEditPersistUnsafeValueError) &&
|
|
@@ -458,6 +375,7 @@ export function useDomEditCommits({
|
|
|
458
375
|
handleDomAttributeCommit,
|
|
459
376
|
handleDomAttributeLiveCommit,
|
|
460
377
|
handleDomHtmlAttributeCommit,
|
|
378
|
+
handleDomAttributesCommit,
|
|
461
379
|
handleDomTextCommit,
|
|
462
380
|
commitDomTextFields,
|
|
463
381
|
handleDomTextFieldStyleCommit,
|
|
@@ -520,6 +438,7 @@ export function useDomEditCommits({
|
|
|
520
438
|
handleDomAttributeCommit,
|
|
521
439
|
handleDomAttributeLiveCommit,
|
|
522
440
|
handleDomHtmlAttributeCommit,
|
|
441
|
+
handleDomAttributesCommit,
|
|
523
442
|
handleDomTextCommit,
|
|
524
443
|
commitDomTextFields,
|
|
525
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
|
+
}
|
|
@@ -231,7 +231,9 @@ describe("onReorderShadow source filter", () => {
|
|
|
231
231
|
setRightPanelTab: vi.fn(),
|
|
232
232
|
showToast: vi.fn(),
|
|
233
233
|
refreshPreviewDocumentVersion: vi.fn(),
|
|
234
|
-
queueDomEditSave: vi.fn(async (save: () => Promise<
|
|
234
|
+
queueDomEditSave: vi.fn(async <T,>(save: () => Promise<T>) => save()) as <T>(
|
|
235
|
+
save: () => Promise<T>,
|
|
236
|
+
) => Promise<T>,
|
|
235
237
|
readProjectFile,
|
|
236
238
|
writeProjectFile: vi.fn(async () => {}),
|
|
237
239
|
updateEditingFileContent: vi.fn(),
|
|
@@ -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";
|
|
@@ -43,7 +43,7 @@ export interface UseDomEditSessionParams {
|
|
|
43
43
|
setRightPanelTab: (tab: RightPanelTab) => void;
|
|
44
44
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
45
45
|
refreshPreviewDocumentVersion: () => void;
|
|
46
|
-
queueDomEditSave: (save: () => Promise<
|
|
46
|
+
queueDomEditSave: <T>(save: () => Promise<T>) => Promise<T>;
|
|
47
47
|
readProjectFile: (path: string) => Promise<string>;
|
|
48
48
|
writeProjectFile: (path: string, content: string) => Promise<void>;
|
|
49
49
|
updateEditingFileContent: (path: string, content: string) => void;
|
|
@@ -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,
|
|
@@ -145,15 +145,19 @@ export function useDomEditTextCommits({
|
|
|
145
145
|
const domTextCommitVersionRef = useRef(0);
|
|
146
146
|
const domStyleCommitVersionRef = useRef(new Map<string, number>());
|
|
147
147
|
|
|
148
|
-
const {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
const {
|
|
149
|
+
handleDomAttributeCommit,
|
|
150
|
+
handleDomAttributeLiveCommit,
|
|
151
|
+
handleDomHtmlAttributeCommit,
|
|
152
|
+
handleDomAttributesCommit,
|
|
153
|
+
} = useDomEditAttributeCommits({
|
|
154
|
+
activeCompPath,
|
|
155
|
+
previewIframeRef,
|
|
156
|
+
showToast,
|
|
157
|
+
domEditSelection,
|
|
158
|
+
refreshDomEditSelectionFromPreview,
|
|
159
|
+
persistDomEditOperations,
|
|
160
|
+
});
|
|
157
161
|
|
|
158
162
|
const handleDomStyleCommit = useCallback(
|
|
159
163
|
async (property: string, value: string) => {
|
|
@@ -471,6 +475,7 @@ export function useDomEditTextCommits({
|
|
|
471
475
|
handleDomAttributeCommit,
|
|
472
476
|
handleDomAttributeLiveCommit,
|
|
473
477
|
handleDomHtmlAttributeCommit,
|
|
478
|
+
handleDomAttributesCommit,
|
|
474
479
|
handleDomTextCommit,
|
|
475
480
|
commitDomTextFields,
|
|
476
481
|
handleDomTextFieldStyleCommit,
|