import { useCallback, useEffect, useRef, useState } from "react"; // "A few seconds" per the group session's live-editing requirement -- // short enough to feel live, long enough not to fire a save on every // keystroke. Deliberately well under board-context's 30s cross-device // poll interval, so a local edit always settles before the poll could // otherwise consider overwriting it. export const AUTOSAVE_DELAY_MS = 2500; /** * Debounced autosave for a single field's value. Call `onSave` a beat * after `value` stops changing, and again immediately via `flush()` (used * by a manual "Save" button and by flush-on-close) -- both paths funnel * through the same `runSave`, so there's no separate "save now" logic to * keep in sync with the debounced one. * * `onSave` must be one of the board-context mutators (e.g. `saveNote`, * `editTodo`), never a raw server action directly -- those mutators already * mark the write as in-flight (see `track()` in board-context.tsx) so the * app's cross-device poll doesn't clobber it mid-save. */ export function useAutosave( value: T, onSave: (value: T) => Promise, delayMs = AUTOSAVE_DELAY_MS ): { flush: () => Promise; isDirty: boolean; isSaving: boolean } { const [savedValue, setSavedValue] = useState(value); const [isSaving, setIsSaving] = useState(false); const onSaveRef = useRef(onSave); const savingPromiseRef = useRef | null>(null); useEffect(() => { onSaveRef.current = onSave; }, [onSave]); const runSave = useCallback((v: T) => { setIsSaving(true); const promise = (async () => { await onSaveRef.current(v); setSavedValue(v); setIsSaving(false); })(); savingPromiseRef.current = promise; return promise; }, []); useEffect(() => { if (value === savedValue) return; const timer = setTimeout(() => { void runSave(value); }, delayMs); return () => clearTimeout(timer); }, [value, savedValue, delayMs, runSave]); const flush = useCallback(async () => { if (value !== savedValue) { await runSave(value); } else if (savingPromiseRef.current) { await savingPromiseRef.current; } }, [value, savedValue, runSave]); return { flush, isDirty: value !== savedValue, isSaving }; }