From 567c084092d9a6c9f41364a24226ca6ae8e16118 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:05:48 -0700 Subject: [PATCH] Fix: prevent remote edits from being silently deleted on concurrent updates Remove the redundant `setFileContents` call in `handleContentChange` and the duplicate `onFileChanged`/`tryParseAndNotify` calls in `updateFileContent`. Both were overwriting the true merged Automerge document state with raw editor content, causing concurrent remote edits to be dropped by subsequent `updateText` diffs. --- hub-client/src/App.tsx | 12 +++++++----- ts-packages/quarto-sync-client/src/client.ts | 8 +++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/hub-client/src/App.tsx b/hub-client/src/App.tsx index 24a1e88b..1dec1e74 100644 --- a/hub-client/src/App.tsx +++ b/hub-client/src/App.tsx @@ -373,12 +373,14 @@ function App() { }, [navigateToProjectSelector]); const handleContentChange = useCallback((path: string, content: string) => { + // updateFileContent fires handle.change(), which synchronously triggers the + // 'change' event on the DocHandle. The registered changeHandler calls + // callbacks.onFileChanged with the true merged Automerge document state, + // which propagates to setFileContents via onFileContent. Setting fileContents + // directly here with the raw editor content would overwrite that merged state, + // causing concurrent remote edits to be silently deleted by subsequent + // updateText calls that diff against the stale Monaco content. updateFileContent(path, content); - setFileContents((prev) => { - const next = new Map(prev); - next.set(path, content); - return next; - }); }, []); const handleProjectCreated = useCallback(async ( diff --git a/ts-packages/quarto-sync-client/src/client.ts b/ts-packages/quarto-sync-client/src/client.ts index 392023b2..b27761f3 100644 --- a/ts-packages/quarto-sync-client/src/client.ts +++ b/ts-packages/quarto-sync-client/src/client.ts @@ -461,9 +461,11 @@ export function createSyncClient(callbacks: SyncClientCallbacks, astOptions?: AS updateText(doc, ['text'], content); }); - // Notify callback (local change) - callbacks.onFileChanged(path, content, []); - tryParseAndNotify(path, content); + // onFileChanged and tryParseAndNotify are called by the 'change' event handler + // registered in subscribeToFile/subscribeToFileInternal, which passes the true + // merged Automerge document state. Calling them again here with the raw editor + // content would overwrite the merged state in fileContents, causing concurrent + // remote edits to be silently deleted on the next updateFileContent call. } /**