/** * Hook for canvas graph state and persistence. Wraps useGraphStateWithHistory * with initial graph from recollection storage (or example). Save is explicit via save(). */ import { useCallback, useMemo, useRef, useState } from 'react' import { useGraphStateWithHistory } from '@/hooks/useGraphStateWithHistory' import { getInitialGraph } from '@/app/canvas/canvasGraphUtils' import { saveGraphToStorage, RECOLLECTION_VERSION } from '@/app/recollections/recollectionGraphStorage' import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes' export type SaveStatus = 'saved' | 'unsaved' | 'saving' export type UseCanvasGraphResult = ReturnType & { /** Persist current nodes/edges to storage. No-op when recollectionId is missing. */ save: () => void /** For menubar: show "Unsaved changes" | "Saving…" | "All changes saved". */ saveStatus: SaveStatus } export function useCanvasGraph(recollectionId: string | undefined): UseCanvasGraphResult { const initialGraph = useMemo(() => getInitialGraph(recollectionId), [recollectionId]) const result = useGraphStateWithHistory(initialGraph.nodes, initialGraph.edges) const { nodes, edges } = result const nodesRef = useRef(nodes) const edgesRef = useRef(edges) nodesRef.current = nodes edgesRef.current = edges const [isSaving, setIsSaving] = useState(false) const [lastSavedSerialized, setLastSavedSerialized] = useState(() => JSON.stringify({ nodes: initialGraph.nodes, edges: initialGraph.edges }) ) const currentSerialized = useMemo( () => JSON.stringify({ nodes, edges }), [nodes, edges] ) const isDirty = currentSerialized !== lastSavedSerialized const saveStatus: SaveStatus = isSaving ? 'saving' : isDirty ? 'unsaved' : 'saved' const save = useCallback(() => { if (!recollectionId) return const snapshot = JSON.stringify({ nodes: nodesRef.current, edges: edgesRef.current, }) setIsSaving(true) saveGraphToStorage(recollectionId, { version: RECOLLECTION_VERSION, nodes: nodesRef.current, edges: edgesRef.current, }) const SAVING_DISPLAY_MS = 360 setTimeout(() => { setLastSavedSerialized(snapshot) setIsSaving(false) }, SAVING_DISPLAY_MS) }, [recollectionId]) return { ...result, save, saveStatus } }