Files
tolaria/src/hooks/useEditorSave.ts
lucaronin 0aa5d7ecc4 fix: commit & push now saves pending content and refreshes modified files
Root cause: Two problems caused "0 files changed":
1. loadModifiedFiles() was only called on mount, never refreshed after saves
2. Pending editor content wasn't flushed to disk before git commit

Fix:
- useEditorSave: add savePending() to flush unsaved content, onAfterSave
  callback to refresh git status after Cmd+S
- useCommitFlow: new hook managing save→commit→push flow with proper
  sequencing (save pending → refresh files → show dialog → commit)
- App.tsx: wire onAfterSave to loadModifiedFiles, use useCommitFlow
- git.rs: include stdout in error when stderr is empty (fixes "nothing
  to commit" message being swallowed)
- mock-tauri: track saved files so get_modified_files reflects edits

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:08:09 +01:00

74 lines
2.7 KiB
TypeScript

import { useCallback, useRef } from 'react'
import type { SetStateAction } from 'react'
import { useSaveNote } from './useSaveNote'
interface Tab {
entry: { path: string }
content: string
}
interface EditorSaveConfig {
updateVaultContent: (path: string, content: string) => void
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Tab types vary between layers
setTabs: (fn: SetStateAction<any[]>) => void
setToastMessage: (msg: string | null) => void
onAfterSave?: () => void
}
/**
* Hook that manages explicit save (Cmd+S) for editor content.
* Tracks pending (unsaved) content and provides save + pre-rename helpers.
*/
export function useEditorSave({ updateVaultContent, setTabs, setToastMessage, onAfterSave }: EditorSaveConfig) {
const pendingContentRef = useRef<{ path: string; content: string } | null>(null)
const updateTabAndContent = useCallback((path: string, content: string) => {
updateVaultContent(path, content)
setTabs((prev: Tab[]) =>
prev.map((t) => t.entry.path === path ? { ...t, content } : t)
)
}, [updateVaultContent, setTabs])
const { saveNote } = useSaveNote(updateTabAndContent)
/** Persist pending content matching an optional path filter; returns true if saved */
const flushPending = useCallback(async (pathFilter?: string): Promise<boolean> => {
const pending = pendingContentRef.current
if (!pending) return false
if (pathFilter && pending.path !== pathFilter) return false
await saveNote(pending.path, pending.content)
pendingContentRef.current = null
return true
}, [saveNote])
/** Called by Cmd+S — persists the current editor content to disk */
const handleSave = useCallback(async () => {
try {
const saved = await flushPending()
if (!saved) { setToastMessage('Nothing to save'); return }
setToastMessage('Saved')
onAfterSave?.()
} catch (err) {
console.error('Save failed:', err)
setToastMessage(`Save failed: ${err}`)
}
}, [flushPending, setToastMessage, onAfterSave])
/** Called by Editor onChange — buffers the latest content without saving */
const handleContentChange = useCallback((path: string, content: string) => {
pendingContentRef.current = { path, content }
}, [])
/** Save pending content for a specific path (used before rename) */
const savePendingForPath = useCallback(
(path: string): Promise<boolean> => flushPending(path),
[flushPending],
)
/** Flush any pending content to disk silently (used before git commit).
* Does NOT call onAfterSave — callers manage their own refresh. */
const savePending = useCallback((): Promise<boolean> => flushPending(), [flushPending])
return { handleSave, handleContentChange, savePendingForPath, savePending }
}