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.
This commit is contained in:
ruinivist
2026-05-31 01:21:08 +00:00
parent 1b0024d78f
commit 0d0d4b8a85
+42 -8
View File
@@ -108,6 +108,8 @@ export function App() {
const loadVersionRef = useRef(0);
const themeSyncFrameRef = useRef<number | null>(null);
const toastTimeoutRef = useRef<number | null>(null);
const loadedUpdatedAtRef = useRef<string | null>(null);
const inFlightTitleSaveRef = useRef<Promise<void> | 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;