Skip to content

Commit f07b2e7

Browse files
fix(studio,core): resolve SDK-cutover review findings
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
1 parent 0918300 commit f07b2e7

14 files changed

Lines changed: 355 additions & 352 deletions

packages/studio/src/App.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ export function StudioApp() {
187187
pendingTimelineEditPathRef,
188188
uploadProjectFiles: fileManager.uploadProjectFiles,
189189
isRecordingRef: isGestureRecordingRef,
190-
sdkSession: sdkHandle.session,
190+
sdkSession,
191+
forceReloadSdkSession,
191192
});
192193
const {
193194
activeBlockParams,
@@ -261,14 +262,10 @@ export function StudioApp() {
261262
? () => handleToggleRecordingRef.current()
262263
: undefined,
263264
});
264-
const selectSidebarTabStable = useCallback(
265-
(tab: SidebarTab) => leftSidebarRef.current?.selectTab(tab),
266-
[],
267-
);
268-
const getSidebarTabStable = useCallback(
269-
() => leftSidebarRef.current?.getTab() ?? "compositions",
270-
[],
271-
);
265+
const sidebarTabRef = useRef({
266+
select: (t: SidebarTab) => leftSidebarRef.current?.selectTab(t),
267+
get: () => leftSidebarRef.current?.getTab() ?? "compositions",
268+
});
272269
const domEditSession = useDomEditSession({
273270
projectId,
274271
activeCompPath,
@@ -301,9 +298,10 @@ export function StudioApp() {
301298
reloadPreview,
302299
setRefreshKey,
303300
openSourceForSelection: fileManager.openSourceForSelection,
304-
selectSidebarTab: selectSidebarTabStable,
305-
getSidebarTab: getSidebarTabStable,
301+
selectSidebarTab: sidebarTabRef.current.select,
302+
getSidebarTab: sidebarTabRef.current.get,
306303
sdkSession,
304+
forceReloadSdkSession,
307305
});
308306
domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
309307
clearDomSelectionRef.current = domEditSession.clearDomSelection;

packages/studio/src/hooks/gsapScriptCommitTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,6 @@ export interface GsapScriptCommitsParams {
7373
/** Stage 7 §3.5: SDK session for routing GSAP tween ops through addGsapTween/setGsapTween/removeGsapTween. */
7474
sdkSession?: Composition | null;
7575
writeProjectFile?: (path: string, content: string) => Promise<void>;
76+
/** Resync the in-memory SDK session after a server-authoritative write. */
77+
forceReloadSdkSession?: () => void;
7678
}

packages/studio/src/hooks/useDomEditCommits.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,17 @@ export interface UseDomEditCommitsParams {
7373
target: HTMLElement,
7474
options?: { preferClipAncestor?: boolean },
7575
) => Promise<DomEditSelection | null>;
76-
/** Stage 7 Step 3b: called after a successful server-side element patch. */
77-
onDomEditPersisted?: (selection: DomEditSelection, operations: PatchOperation[]) => void;
76+
/** Resync the in-memory SDK session after a SERVER-side write (NOT the SDK
77+
* path, whose session is already current) so a later SDK edit doesn't
78+
* serialize the pre-write doc and revert the server's change. */
79+
forceReloadSdkSession?: () => void;
7880
/** Stage 7 Step 3c: called before the server-side patch path; returns true if SDK handled it. */
7981
onTrySdkPersist?: (
8082
selection: DomEditSelection,
8183
operations: PatchOperation[],
8284
originalContent: string,
8385
targetPath: string,
86+
options?: { label?: string; coalesceKey?: string; skipRefresh?: boolean },
8487
) => Promise<boolean>;
8588
/** Stage 7 §3.1: called before the server-side delete path; returns true if SDK handled it. */
8689
onTrySdkDelete?: (hfId: string, originalContent: string, targetPath: string) => Promise<boolean>;
@@ -104,7 +107,7 @@ export function useDomEditCommits({
104107
clearDomSelection,
105108
refreshDomEditSelectionFromPreview,
106109
buildDomSelectionFromTarget,
107-
onDomEditPersisted,
110+
forceReloadSdkSession,
108111
onTrySdkPersist,
109112
onTrySdkDelete,
110113
}: UseDomEditCommitsParams) {
@@ -156,7 +159,22 @@ export function useDomEditCommits({
156159
}
157160

158161
if (options?.shouldSave && !options.shouldSave()) return;
159-
162+
// Skip the SDK path when prepareContent is set (e.g. @font-face injection
163+
// for a custom font): sdkCutoverPersist serializes only the patched DOM
164+
// and would drop the injected content. Let the server path run prepareContent.
165+
if (
166+
onTrySdkPersist &&
167+
!options?.prepareContent &&
168+
(await onTrySdkPersist(selection, operations, originalContent, targetPath, {
169+
label: options?.label,
170+
coalesceKey: options?.coalesceKey,
171+
skipRefresh: options?.skipRefresh,
172+
}))
173+
) {
174+
// SDK handled it — its in-memory doc is already current, so do NOT
175+
// forceReload (that would echo-reload the session we just wrote).
176+
return;
177+
}
160178
const patchTarget = buildDomEditPatchTarget(selection);
161179
const patchBody = { target: patchTarget, operations };
162180
const unsafeFields = findUnsafeDomPatchValues(patchBody);
@@ -228,7 +246,7 @@ export function useDomEditCommits({
228246
coalesceKey: options?.coalesceKey,
229247
files: { [targetPath]: { before: originalContent, after: finalContent } },
230248
});
231-
onDomEditPersisted?.(selection, operations);
249+
forceReloadSdkSession?.();
232250

233251
if (!options?.skipRefresh) {
234252
reloadPreview();
@@ -242,7 +260,8 @@ export function useDomEditCommits({
242260
domEditSaveTimestampRef,
243261
reloadPreview,
244262
showToast,
245-
onDomEditPersisted,
263+
forceReloadSdkSession,
264+
onTrySdkPersist,
246265
],
247266
);
248267

@@ -304,6 +323,7 @@ export function useDomEditCommits({
304323
reloadPreview,
305324
clearDomSelection,
306325
onTrySdkDelete,
326+
forceReloadSdkSession,
307327
commitPositionPatchToHtml,
308328
onElementDeleted,
309329
});

packages/studio/src/hooks/useDomEditSession.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface UseDomEditSessionParams {
6161
selectSidebarTab?: (tab: SidebarTab) => void;
6262
getSidebarTab?: () => SidebarTab;
6363
sdkSession?: Composition | null;
64+
forceReloadSdkSession?: () => void;
6465
}
6566

6667
// ── Hook ──
@@ -100,6 +101,7 @@ export function useDomEditSession({
100101
selectSidebarTab,
101102
getSidebarTab,
102103
sdkSession,
104+
forceReloadSdkSession,
103105
}: UseDomEditSessionParams) {
104106
void _setRefreshKey;
105107
void _readProjectFile;
@@ -195,6 +197,7 @@ export function useDomEditSession({
195197
showToast,
196198
sdkSession,
197199
writeProjectFile,
200+
forceReloadSdkSession,
198201
});
199202

200203
// ── DOM commit handlers ──
@@ -234,14 +237,24 @@ export function useDomEditSession({
234237
clearDomSelection,
235238
refreshDomEditSelectionFromPreview,
236239
buildDomSelectionFromTarget,
240+
forceReloadSdkSession,
237241
onTrySdkPersist: sdkSession
238-
? (selection, operations, originalContent, targetPath) =>
239-
sdkCutoverPersist(selection, operations, originalContent, targetPath, sdkSession, {
240-
editHistory,
241-
writeProjectFile,
242-
reloadPreview,
243-
domEditSaveTimestampRef,
244-
})
242+
? (selection, operations, originalContent, targetPath, options) =>
243+
sdkCutoverPersist(
244+
selection,
245+
operations,
246+
originalContent,
247+
targetPath,
248+
sdkSession,
249+
{
250+
editHistory,
251+
writeProjectFile,
252+
reloadPreview,
253+
domEditSaveTimestampRef,
254+
compositionPath: activeCompPath,
255+
},
256+
options,
257+
)
245258
: undefined,
246259
onTrySdkDelete: sdkSession
247260
? (hfId, originalContent, targetPath) =>
@@ -250,6 +263,7 @@ export function useDomEditSession({
250263
writeProjectFile,
251264
reloadPreview,
252265
domEditSaveTimestampRef,
266+
compositionPath: activeCompPath,
253267
})
254268
: undefined,
255269
});

packages/studio/src/hooks/useElementLifecycleOps.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ interface UseElementLifecycleOpsParams {
2828
clearDomSelection: () => void;
2929
/** Route delete through SDK when session resolves the hf-id; returns true if handled. */
3030
onTrySdkDelete?: (hfId: string, originalContent: string, targetPath: string) => Promise<boolean>;
31+
/** Resync the SDK session after a server-fallback delete. */
32+
forceReloadSdkSession?: () => void;
3133
commitPositionPatchToHtml: (
3234
selection: DomEditSelection,
3335
patches: PatchOperation[],
@@ -47,6 +49,7 @@ export function useElementLifecycleOps({
4749
reloadPreview,
4850
clearDomSelection,
4951
onTrySdkDelete,
52+
forceReloadSdkSession,
5053
commitPositionPatchToHtml,
5154
onElementDeleted,
5255
}: UseElementLifecycleOpsParams) {
@@ -106,6 +109,12 @@ export function useElementLifecycleOps({
106109
const removeData = (await removeResponse.json()) as { changed?: boolean; content?: string };
107110
const patchedContent =
108111
typeof removeData.content === "string" ? removeData.content : originalContent;
112+
// ponytail: the server remove-element route (removeElementFromHtml) strips
113+
// only the element node — it does NOT cascade-remove GSAP tweens targeting
114+
// it, unlike the SDK path (removeElement → cascadeRemoveAnimations). This
115+
// fallback runs only when the element isn't in the SDK doc (e.g. runtime-
116+
// generated / unaddressable), where targeting tweens are unlikely. Upgrade
117+
// path: cascade in removeElementFromHtml by selector/hf-id to fully match.
109118
await saveProjectFilesWithHistory({
110119
projectId: pid,
111120
label: "Delete element",
@@ -118,6 +127,9 @@ export function useElementLifecycleOps({
118127

119128
clearDomSelection();
120129
usePlayerStore.getState().setSelectedElementId(null);
130+
// Server wrote the file; resync the stale in-memory SDK doc so a later
131+
// SDK edit doesn't resurrect the deleted element.
132+
forceReloadSdkSession?.();
121133
reloadPreview();
122134
onElementDeleted?.(selection);
123135
showToast(`Deleted ${label}. Use Undo to restore it.`, "info");
@@ -132,6 +144,7 @@ export function useElementLifecycleOps({
132144
domEditSaveTimestampRef,
133145
editHistory.recordEdit,
134146
onTrySdkDelete,
147+
forceReloadSdkSession,
135148
projectIdRef,
136149
reloadPreview,
137150
showToast,

0 commit comments

Comments
 (0)