diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx index 826623ac..f34b3e60 100644 --- a/packages/ui/src/components/views/FilesView.tsx +++ b/packages/ui/src/components/views/FilesView.tsx @@ -549,6 +549,8 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { const [draftContent, setDraftContent] = React.useState(''); const [isSaving, setIsSaving] = React.useState(false); + const autoSaveTimerRef = React.useRef | null>(null); + const [autoSaveStatus, setAutoSaveStatus] = React.useState<'idle' | 'saved'>('idle'); const [confirmDiscardOpen, setConfirmDiscardOpen] = React.useState(false); const pendingSelectFileRef = React.useRef(null); @@ -1150,6 +1152,35 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { }; }, [isDirty, setMainTabGuard]); + // Auto-save: debounce 1.5s after user stops typing + const AUTO_SAVE_DELAY = 1500; + + React.useEffect(() => { + const canWrite = Boolean(selectedFile && files.writeFile); + if (!isDirty || !canWrite || isSaving) { + return; + } + + autoSaveTimerRef.current = setTimeout(() => { + void saveDraft().then(() => { + setAutoSaveStatus('saved'); + setTimeout(() => setAutoSaveStatus('idle'), 2000); + }); + }, AUTO_SAVE_DELAY); + + return () => { + if (autoSaveTimerRef.current) { + clearTimeout(autoSaveTimerRef.current); + autoSaveTimerRef.current = null; + } + }; + }, [draftContent, isDirty, selectedFile, files.writeFile, isSaving, saveDraft]); + + // Reset auto-save status when switching files + React.useEffect(() => { + setAutoSaveStatus('idle'); + }, [selectedFile?.path]); + React.useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!hasModifier(e)) { @@ -1158,8 +1189,16 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { if (e.key.toLowerCase() === 's') { e.preventDefault(); + // Cancel pending auto-save; user wants immediate save + if (autoSaveTimerRef.current) { + clearTimeout(autoSaveTimerRef.current); + autoSaveTimerRef.current = null; + } if (!isSaving) { - void saveDraft(); + void saveDraft().then(() => { + setAutoSaveStatus('saved'); + setTimeout(() => setAutoSaveStatus('idle'), 2000); + }); } } else if (e.key.toLowerCase() === 'f') { e.preventDefault(); @@ -2253,21 +2292,28 @@ export const FilesView: React.FC = ({ mode = 'full' }) => { {selectedFile && (
{canEdit && textViewMode === 'edit' && ( - + isSaving ? ( + + + Saving… + + ) : autoSaveStatus === 'saved' && !isDirty ? ( + + + Saved + + ) : isDirty ? ( + + ) : null )} @@ -2714,21 +2760,28 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
{canEdit && textViewMode === 'edit' && ( - + + ) : null )}