Skip to content

Commit dc32ca8

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

14 files changed

Lines changed: 348 additions & 336 deletions

packages/studio/src/App.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export function StudioApp() {
190190
uploadProjectFiles: fileManager.uploadProjectFiles,
191191
isRecordingRef: isGestureRecordingRef,
192192
sdkSession,
193+
forceReloadSdkSession,
193194
});
194195
const {
195196
activeBlockParams,
@@ -263,14 +264,10 @@ export function StudioApp() {
263264
? () => handleToggleRecordingRef.current()
264265
: undefined,
265266
});
266-
const selectSidebarTabStable = useCallback(
267-
(tab: SidebarTab) => leftSidebarRef.current?.selectTab(tab),
268-
[],
269-
);
270-
const getSidebarTabStable = useCallback(
271-
() => leftSidebarRef.current?.getTab() ?? "compositions",
272-
[],
273-
);
267+
const sidebarTabRef = useRef({
268+
select: (t: SidebarTab) => leftSidebarRef.current?.selectTab(t),
269+
get: () => leftSidebarRef.current?.getTab() ?? "compositions",
270+
});
274271
const domEditSession = useDomEditSession({
275272
projectId,
276273
activeCompPath,
@@ -303,9 +300,10 @@ export function StudioApp() {
303300
reloadPreview,
304301
setRefreshKey,
305302
openSourceForSelection: fileManager.openSourceForSelection,
306-
selectSidebarTab: selectSidebarTabStable,
307-
getSidebarTab: getSidebarTabStable,
303+
selectSidebarTab: sidebarTabRef.current.select,
304+
getSidebarTab: sidebarTabRef.current.get,
308305
sdkSession,
306+
forceReloadSdkSession,
309307
});
310308
domEditSelectionBridgeRef.current = domEditSession.domEditSelection;
311309
clearDomSelectionRef.current = domEditSession.clearDomSelection;

packages/studio/src/hooks/gsapScriptCommitTypes.ts

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

packages/studio/src/hooks/useDomEditCommits.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,17 @@ export interface UseDomEditCommitsParams {
7676
target: HTMLElement,
7777
options?: { preferClipAncestor?: boolean },
7878
) => Promise<DomEditSelection | null>;
79-
/** Stage 7 Step 3b: called after a successful server-side element patch. */
80-
onDomEditPersisted?: (selection: DomEditSelection, operations: PatchOperation[]) => void;
79+
/** Resync the in-memory SDK session after a SERVER-side write (NOT the SDK
80+
* path, whose session is already current) so a later SDK edit doesn't
81+
* serialize the pre-write doc and revert the server's change. */
82+
forceReloadSdkSession?: () => void;
8183
/** Stage 7 Step 3c: called before the server-side patch path; returns true if SDK handled it. */
8284
onTrySdkPersist?: (
8385
selection: DomEditSelection,
8486
operations: PatchOperation[],
8587
originalContent: string,
8688
targetPath: string,
89+
options?: { label?: string; coalesceKey?: string; skipRefresh?: boolean },
8790
) => Promise<boolean>;
8891
/** Stage 7 §3.1: called before the server-side delete path; returns true if SDK handled it. */
8992
onTrySdkDelete?: (hfId: string, originalContent: string, targetPath: string) => Promise<boolean>;
@@ -107,7 +110,7 @@ export function useDomEditCommits({
107110
clearDomSelection,
108111
refreshDomEditSelectionFromPreview,
109112
buildDomSelectionFromTarget,
110-
onDomEditPersisted,
113+
forceReloadSdkSession,
111114
onTrySdkPersist,
112115
onTrySdkDelete,
113116
}: UseDomEditCommitsParams) {
@@ -157,11 +160,20 @@ export function useDomEditCommits({
157160
throw new Error(`Missing file contents for ${targetPath}`);
158161
}
159162
if (options?.shouldSave && !options.shouldSave()) return;
163+
// Skip the SDK path when prepareContent is set (e.g. @font-face injection
164+
// for a custom font): sdkCutoverPersist serializes only the patched DOM
165+
// and would drop the injected content. Let the server path run prepareContent.
160166
if (
161167
onTrySdkPersist &&
162-
(await onTrySdkPersist(selection, operations, originalContent, targetPath))
168+
!options?.prepareContent &&
169+
(await onTrySdkPersist(selection, operations, originalContent, targetPath, {
170+
label: options?.label,
171+
coalesceKey: options?.coalesceKey,
172+
skipRefresh: options?.skipRefresh,
173+
}))
163174
) {
164-
onDomEditPersisted?.(selection, operations);
175+
// SDK handled it — its in-memory doc is already current, so do NOT
176+
// forceReload (that would echo-reload the session we just wrote).
165177
return;
166178
}
167179
const patchTarget = buildDomEditPatchTarget(selection);
@@ -234,7 +246,7 @@ export function useDomEditCommits({
234246
coalesceKey: options?.coalesceKey,
235247
files: { [targetPath]: { before: originalContent, after: finalContent } },
236248
});
237-
onDomEditPersisted?.(selection, operations);
249+
forceReloadSdkSession?.();
238250

239251
if (!options?.skipRefresh) {
240252
reloadPreview();
@@ -248,7 +260,7 @@ export function useDomEditCommits({
248260
domEditSaveTimestampRef,
249261
reloadPreview,
250262
showToast,
251-
onDomEditPersisted,
263+
forceReloadSdkSession,
252264
onTrySdkPersist,
253265
],
254266
);
@@ -310,6 +322,7 @@ export function useDomEditCommits({
310322
reloadPreview,
311323
clearDomSelection,
312324
onTrySdkDelete,
325+
forceReloadSdkSession,
313326
commitPositionPatchToHtml,
314327
});
315328

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 ──
@@ -233,14 +236,24 @@ export function useDomEditSession({
233236
clearDomSelection,
234237
refreshDomEditSelectionFromPreview,
235238
buildDomSelectionFromTarget,
239+
forceReloadSdkSession,
236240
onTrySdkPersist: sdkSession
237-
? (selection, operations, originalContent, targetPath) =>
238-
sdkCutoverPersist(selection, operations, originalContent, targetPath, sdkSession, {
239-
editHistory,
240-
writeProjectFile,
241-
reloadPreview,
242-
domEditSaveTimestampRef,
243-
})
241+
? (selection, operations, originalContent, targetPath, options) =>
242+
sdkCutoverPersist(
243+
selection,
244+
operations,
245+
originalContent,
246+
targetPath,
247+
sdkSession,
248+
{
249+
editHistory,
250+
writeProjectFile,
251+
reloadPreview,
252+
domEditSaveTimestampRef,
253+
compositionPath: activeCompPath,
254+
},
255+
options,
256+
)
244257
: undefined,
245258
onTrySdkDelete: sdkSession
246259
? (hfId, originalContent, targetPath) =>
@@ -249,6 +262,7 @@ export function useDomEditSession({
249262
writeProjectFile,
250263
reloadPreview,
251264
domEditSaveTimestampRef,
265+
compositionPath: activeCompPath,
252266
})
253267
: undefined,
254268
});

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[],
@@ -45,6 +47,7 @@ export function useElementLifecycleOps({
4547
reloadPreview,
4648
clearDomSelection,
4749
onTrySdkDelete,
50+
forceReloadSdkSession,
4851
commitPositionPatchToHtml,
4952
}: UseElementLifecycleOpsParams) {
5053
// fallow-ignore-next-line complexity
@@ -103,6 +106,12 @@ export function useElementLifecycleOps({
103106
const removeData = (await removeResponse.json()) as { changed?: boolean; content?: string };
104107
const patchedContent =
105108
typeof removeData.content === "string" ? removeData.content : originalContent;
109+
// ponytail: the server remove-element route (removeElementFromHtml) strips
110+
// only the element node — it does NOT cascade-remove GSAP tweens targeting
111+
// it, unlike the SDK path (removeElement → cascadeRemoveAnimations). This
112+
// fallback runs only when the element isn't in the SDK doc (e.g. runtime-
113+
// generated / unaddressable), where targeting tweens are unlikely. Upgrade
114+
// path: cascade in removeElementFromHtml by selector/hf-id to fully match.
106115
await saveProjectFilesWithHistory({
107116
projectId: pid,
108117
label: "Delete element",
@@ -115,6 +124,9 @@ export function useElementLifecycleOps({
115124

116125
clearDomSelection();
117126
usePlayerStore.getState().setSelectedElementId(null);
127+
// Server wrote the file; resync the stale in-memory SDK doc so a later
128+
// SDK edit doesn't resurrect the deleted element.
129+
forceReloadSdkSession?.();
118130
reloadPreview();
119131
showToast(`Deleted ${label}. Use Undo to restore it.`, "info");
120132
} catch (error) {
@@ -128,6 +140,7 @@ export function useElementLifecycleOps({
128140
domEditSaveTimestampRef,
129141
editHistory.recordEdit,
130142
onTrySdkDelete,
143+
forceReloadSdkSession,
131144
projectIdRef,
132145
reloadPreview,
133146
showToast,

0 commit comments

Comments
 (0)