mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
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.
This commit is contained in:
@@ -77,6 +77,8 @@ export function useManifestPersistence({
|
||||
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
|
||||
) => Promise<void>
|
||||
>(async () => {});
|
||||
const manifestBootstrappedRef = useRef(false);
|
||||
const motionBootstrappedRef = useRef(false);
|
||||
const studioManualEditProjectRef = useRef<string | null>(projectId);
|
||||
|
||||
// Keep a ref to the latest projectId so async save callbacks always read the
|
||||
@@ -144,7 +146,13 @@ export function useManifestPersistence({
|
||||
iframe: HTMLIFrameElement | null = previewIframeRef.current,
|
||||
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
|
||||
) => {
|
||||
const readFromDiskFirst = Boolean(options?.forceFromDisk || options?.readFromDiskFirst);
|
||||
// Bootstrap from disk on first apply per session; explicit flag avoids
|
||||
// re-reading disk after the user deletes all edits (async write race).
|
||||
const needsBootstrap = !manifestBootstrappedRef.current;
|
||||
if (needsBootstrap) manifestBootstrappedRef.current = true;
|
||||
const readFromDiskFirst = Boolean(
|
||||
options?.forceFromDisk || options?.readFromDiskFirst || needsBootstrap,
|
||||
);
|
||||
if (!readFromDiskFirst) {
|
||||
applyCurrentStudioManualEditsToPreview(iframe);
|
||||
return;
|
||||
@@ -210,7 +218,11 @@ export function useManifestPersistence({
|
||||
iframe: HTMLIFrameElement | null = previewIframeRef.current,
|
||||
options?: { forceFromDisk?: boolean; readFromDiskFirst?: boolean },
|
||||
) => {
|
||||
const readFromDiskFirst = Boolean(options?.forceFromDisk || options?.readFromDiskFirst);
|
||||
const needsBootstrap = !motionBootstrappedRef.current;
|
||||
if (needsBootstrap) motionBootstrappedRef.current = true;
|
||||
const readFromDiskFirst = Boolean(
|
||||
options?.forceFromDisk || options?.readFromDiskFirst || needsBootstrap,
|
||||
);
|
||||
if (!readFromDiskFirst) {
|
||||
applyCurrentStudioMotionToPreview(iframe);
|
||||
return;
|
||||
@@ -427,6 +439,7 @@ export function useManifestPersistence({
|
||||
studioMotionManifestRef.current = emptyStudioMotionManifest();
|
||||
studioMotionRevisionRef.current += 1;
|
||||
setStudioMotionRevision((revision) => revision + 1);
|
||||
manifestBootstrappedRef.current = motionBootstrappedRef.current = false;
|
||||
}, [projectId]);
|
||||
|
||||
// ── Listen for external file changes (HMR / SSE) ──
|
||||
|
||||
Reference in New Issue
Block a user