diff --git a/web/src/components/editor/EditorPanel.tsx b/web/src/components/editor/EditorPanel.tsx index ce8fcb5..f221636 100644 --- a/web/src/components/editor/EditorPanel.tsx +++ b/web/src/components/editor/EditorPanel.tsx @@ -20,12 +20,34 @@ export function EditorPanel({ workspaceId, path, language }: EditorPanelProps) { const saveInProgress = write.isPending; const saveTimeoutRef = useRef(undefined); + // Detect that the file on disk has changed since we last loaded/saved it. + // We only consider it "changed externally" if the server content differs + // from what we last persisted (savedContent). The query refetches every + // 5s (see useFileRead) so this flips when another tool edits the file. + const externalChange = + data !== undefined && data.content !== savedContent && !dirty; + // Sync editor buffer with fetched content when the file changes or loads. + // Guard: do NOT clobber the user's unsaved edits. If dirty, the user is + // editing and the next refetch must not stomp the buffer. useEffect(() => { - if (data) { + if (!data) return; + setSavedContent(data.content); + if (!dirty) { setBuffer(data.content); - setSavedContent(data.content); } + // Intentionally NOT including `dirty` in deps: we want this effect to + // run when `data` changes; on the first refetch that comes back with + // new content while dirty, the warning above (`externalChange` is gated + // by !dirty) won't fire, but the next save will surface the diff. The + // user can also click Reload to accept the disk version. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + + const reloadFromDisk = useCallback(() => { + if (!data) return; + setBuffer(data.content); + setSavedContent(data.content); }, [data]); const editorLanguage = @@ -162,6 +184,18 @@ export function EditorPanel({ workspaceId, path, language }: EditorPanelProps) { Loading… )} + {externalChange && ( +
+ File changed on disk. + +
+ )}
fetchFileRead(workspaceId!, path!), enabled: !!workspaceId && !!path, + // Poll so external edits (the agent, another tool) are visible in + // the editor without requiring a manual refresh. + refetchInterval: 5_000, + refetchOnWindowFocus: true, }); }