Skip to content

Commit c3f70c9

Browse files
fix(studio): restore saved positions on page refresh (#801)
* fix(studio): restore saved positions on page refresh studio-manual-edits.json was correctly persisted to disk but never read back into memory on bootstrap. On every page refresh, studioManualEditManifestRef started empty, so handleLoad applied an empty manifest and all saved positions/sizes/rotations were silently discarded. applyStudioManualEditsToPreview now reads from disk whenever the in-memory manifest is empty. The existing readRevision guard prevents overwriting an in-flight optimistic edit if a position change races with the disk read. * fix(studio): close delete-all race and apply same bootstrap to motion manifest Two follow-up fixes from review: 1. Replace edits.length === 0 with an explicit manifestBootstrappedRef boolean. The old condition was true in two distinct states: never-bootstrapped AND user-deleted-all-edits. Because the delete-all disk write is async-queued, there was a window where applyStudioManualEditsToPreview could read stale disk content and resurrect just-deleted positions. The boolean flag is set on the first apply and reset on project switch, cleanly separating the two states. 2. applyStudioMotionToPreview had the identical bug: GSAP motion edits were also lost on page refresh. Applied the same motionBootstrappedRef pattern.
1 parent 246a191 commit c3f70c9

2 files changed

Lines changed: 16 additions & 2 deletions

File tree

‎.filesize-allowlist‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
packages/studio/src/player/hooks/useTimelinePlayer.ts
2+
packages/studio/src/hooks/useManifestPersistence.ts

‎packages/studio/src/hooks/useManifestPersistence.ts‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export function useManifestPersistence({
7777
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
7878
) => Promise<void>
7979
>(async () => {});
80+
const manifestBootstrappedRef = useRef(false);
81+
const motionBootstrappedRef = useRef(false);
8082
const studioManualEditProjectRef = useRef<string | null>(projectId);
8183

8284
// Keep a ref to the latest projectId so async save callbacks always read the
@@ -144,7 +146,13 @@ export function useManifestPersistence({
144146
iframe: HTMLIFrameElement | null = previewIframeRef.current,
145147
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
146148
) => {
147-
const readFromDiskFirst = Boolean(options?.forceFromDisk || options?.readFromDiskFirst);
149+
// Bootstrap from disk on first apply per session; explicit flag avoids
150+
// re-reading disk after the user deletes all edits (async write race).
151+
const needsBootstrap = !manifestBootstrappedRef.current;
152+
if (needsBootstrap) manifestBootstrappedRef.current = true;
153+
const readFromDiskFirst = Boolean(
154+
options?.forceFromDisk || options?.readFromDiskFirst || needsBootstrap,
155+
);
148156
if (!readFromDiskFirst) {
149157
applyCurrentStudioManualEditsToPreview(iframe);
150158
return;
@@ -210,7 +218,11 @@ export function useManifestPersistence({
210218
iframe: HTMLIFrameElement | null = previewIframeRef.current,
211219
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
212220
) => {
213-
const readFromDiskFirst = Boolean(options?.forceFromDisk || options?.readFromDiskFirst);
221+
const needsBootstrap = !motionBootstrappedRef.current;
222+
if (needsBootstrap) motionBootstrappedRef.current = true;
223+
const readFromDiskFirst = Boolean(
224+
options?.forceFromDisk || options?.readFromDiskFirst || needsBootstrap,
225+
);
214226
if (!readFromDiskFirst) {
215227
applyCurrentStudioMotionToPreview(iframe);
216228
return;
@@ -427,6 +439,7 @@ export function useManifestPersistence({
427439
studioMotionManifestRef.current = emptyStudioMotionManifest();
428440
studioMotionRevisionRef.current += 1;
429441
setStudioMotionRevision((revision) => revision + 1);
442+
manifestBootstrappedRef.current = motionBootstrappedRef.current = false;
430443
}, [projectId]);
431444

432445
// ── Listen for external file changes (HMR / SSE) ──

0 commit comments

Comments
 (0)