From 0d0d4b8a85f16812138ae015108fa1946eaf6254 Mon Sep 17 00:00:00 2001 From: ruinivist <179396038+ruinivist@users.noreply.github.com> Date: Sun, 31 May 2026 01:21:08 +0000 Subject: [PATCH] fix: reload drawing when edited externally by MCP Adds an active polling and reload mechanism. It tracks the `updatedAt` timestamp of the current drawing and polls `refreshList` every 5 seconds. If the server's drawing is newer, it clears pending autosaves and reloads the drawing to reflect the external changes. Also tracks when a title save is in progress to avoid reloading when the current user renamed the drawing. --- src/App.tsx | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c4f2eef..3ec6f0b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -108,6 +108,8 @@ export function App() { const loadVersionRef = useRef(0); const themeSyncFrameRef = useRef(null); const toastTimeoutRef = useRef(null); + const loadedUpdatedAtRef = useRef(null); + const inFlightTitleSaveRef = useRef | null>(null); const activeDrawing = useMemo( () => drawings.find((drawing) => drawing.id === activeId) ?? null, @@ -141,6 +143,7 @@ export function App() { body: JSON.stringify(nextScene), }) .then((body) => { + loadedUpdatedAtRef.current = body.drawing.updatedAt; setDrawings((current) => replaceMeta(current, body.drawing)); if (isManual) { setToastMessage("Saved"); @@ -230,6 +233,7 @@ export function App() { currentIdRef.current = drawing.id; currentTitleRef.current = drawing.title; latestSceneRef.current = nextScene; + loadedUpdatedAtRef.current = drawing.updatedAt; setDrawings(sortDrawings(list)); setActiveId(drawing.id); @@ -323,18 +327,22 @@ export function App() { return; } - try { - const body = await requestJson<{ ok: true; drawing: DrawingMeta }>(`/api/drawings/${drawingId}`, { - method: "PUT", - body: JSON.stringify({ title: currentTitleRef.current }), - }); - + const promise = requestJson<{ ok: true; drawing: DrawingMeta }>(`/api/drawings/${drawingId}`, { + method: "PUT", + body: JSON.stringify({ title: currentTitleRef.current }), + }).then((body) => { currentTitleRef.current = body.drawing.title; + loadedUpdatedAtRef.current = body.drawing.updatedAt; setActiveTitle(body.drawing.title); setDrawings((current) => replaceMeta(current, body.drawing)); - } catch (renameError) { + }).catch((renameError) => { setError(renameError instanceof Error ? renameError.message : "Rename failed"); - } + }).finally(() => { + inFlightTitleSaveRef.current = null; + }); + + inFlightTitleSaveRef.current = promise; + await promise; }, []); const syncThemeTokens = useCallback(() => { @@ -411,6 +419,32 @@ export function App() { }; }, []); + useEffect(() => { + const interval = window.setInterval(() => { + void refreshList(); + }, 5000); + + return () => { + window.clearInterval(interval); + }; + }, [refreshList]); + + useEffect(() => { + if ( + activeDrawing && + loadedUpdatedAtRef.current && + activeDrawing.updatedAt > loadedUpdatedAtRef.current && + !inFlightSaveRef.current && + !inFlightTitleSaveRef.current + ) { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + void loadDrawing(activeDrawing.id); + } + }, [activeDrawing, loadDrawing]); + useEffect(() => { if (!scene || loading) { return;