From 0981093c2d1f297eed889c7ec9ad197f2595eb74 Mon Sep 17 00:00:00 2001 From: hassnalalshaikh Date: Thu, 10 Sep 2026 21:56:49 +0300 Subject: [PATCH] fix(studio): keep file-change subscription stable across redraws --- .../studio/src/hooks/useEditorSave.test.tsx | 27 +++++++++++++++++++ packages/studio/src/hooks/useEditorSave.ts | 4 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/studio/src/hooks/useEditorSave.test.tsx b/packages/studio/src/hooks/useEditorSave.test.tsx index f122ef04845..d9c9a8ae98f 100644 --- a/packages/studio/src/hooks/useEditorSave.test.tsx +++ b/packages/studio/src/hooks/useEditorSave.test.tsx @@ -41,6 +41,11 @@ async function mountEditorSave(writeProjectFile: WriteProjectFile) { return { handle: captured.handle, showToast, + rerender: async () => { + await act(async () => root.render()); + if (!captured.handle) throw new Error("Editor save handle was not mounted"); + return captured.handle; + }, unmount: () => act(async () => root.unmount()), }; } @@ -60,6 +65,28 @@ describe("useEditorSave pending work", () => { vi.unstubAllGlobals(); }); + it("keeps the pending getter stable across redraws while reading current edits", async () => { + const mounted = await mountEditorSave(vi.fn(async () => undefined)); + const getPendingCandidate = mounted.handle.getPendingCandidate; + try { + expect(getPendingCandidate()).toBeNull(); + for (let redraw = 0; redraw < 30; redraw++) { + const current = await mounted.rerender(); + expect(current.getPendingCandidate).toBe(getPendingCandidate); + } + act(() => mounted.handle.handleContentChange("latest pending source")); + expect(getPendingCandidate()).toEqual({ + projectId: "project-a", + path: "index.html", + content: "latest pending source", + }); + act(() => mounted.handle.discardPendingSave()); + expect(getPendingCandidate()).toBeNull(); + } finally { + await mounted.unmount(); + } + }); + it("exposes and flushes the latest rAF-buffered source candidate", async () => { const writeProjectFile = vi.fn(async () => undefined); const mounted = await mountEditorSave(writeProjectFile); diff --git a/packages/studio/src/hooks/useEditorSave.ts b/packages/studio/src/hooks/useEditorSave.ts index 7bb9486038d..676b0e6d816 100644 --- a/packages/studio/src/hooks/useEditorSave.ts +++ b/packages/studio/src/hooks/useEditorSave.ts @@ -185,10 +185,12 @@ export function useEditorSave({ pendingCandidateRef.current = null; }, []); + const getPendingCandidate = useCallback(() => pendingCandidateRef.current, []); + return { saveRafRef, handleContentChange, - getPendingCandidate: () => pendingCandidateRef.current, + getPendingCandidate, flushPendingSave, discardPendingSave, };