# All files / src/hooks useVaultLoader.ts

84.15% Statements 85/101
73.07% Branches 19/26
86.48% Functions 32/37
85.5% Lines 59/69

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128

    66x

    30x
    30x
    30x
    30x
    30x

    1x
    1x
    1x
    1x

    104x

    104x
    5x

    104x

    104x

    40x
    36x
    36x
    8x
    3x
    1x

    104x
    104x
    104x
    104x

    104x

    30x
    30x
    30x

    104x
    32x
    32x

    104x

    104x
    5x
    5x
    4x

    5x
    5x

    104x
    1x

    104x
    1x

    104x
    2x
    2x

    104x

    104x
    2x
    1x

    104x
    1x

    104x
    1x

    104x
    33x

    104x
    1x

    104x

     import { useCallback, useEffect, useState } from 'react'
    import { invoke } from '@tauri-apps/api/core'
    import { isTauri, mockInvoke } from '../mock-tauri'
    import type { VaultEntry, GitCommit, ModifiedFile, NoteStatus } from '../types'

    function tauriCall<T>(command: string, tauriArgs: Record<string, unknown>, mockArgs?: Record<string, unknown>): Promise<T> {
      return isTauri() ? invoke<T>(command, tauriArgs) : mockInvoke<T>(command, mockArgs ?? tauriArgs)
    }

    async function loadVaultData(vaultPath: string) {
      Eif (!isTauri()) console.info('[mock] Using mock Tauri data for browser testing')
      const entries = await tauriCall<VaultEntry[]>('list_vault', { path: vaultPath })
      console.log(`Vault scan complete: ${entries.length} entries found`)
      const allContent = isTauri() ? {} : await mockInvoke<Record<string, string>>('get_all_content', { path: vaultPath })
      return { entries, allContent }
    }

    async function commitWithPush(vaultPath: string, message: string): Promise<string> {
      Eif (!isTauri()) {
        await mockInvoke<string>('git_commit', { message })
        await mockInvoke<string>('git_push', {})
        return 'Committed and pushed'
      }
      await invoke<string>('git_commit', { vaultPath, message })
      try {
        await invoke<string>('git_push', { vaultPath })
        return 'Committed and pushed'
      } catch {
        return 'Committed (push failed)'
      }
    }

    function useNewNoteTracker() {
      const [newPaths, setNewPaths] = useState<Set<string>>(new Set())

      const trackNew = useCallback((path: string) => {
        setNewPaths((prev) => new Set(prev).add(path))
      }, [])

      const clear = useCallback(() => setNewPaths(new Set()), [])

      return { newPaths, trackNew, clear }
    }

    export function resolveNoteStatus(path: string, newPaths: Set<string>, modifiedFiles: ModifiedFile[]): NoteStatus {
      if (newPaths.has(path)) return 'new'
      const gitEntry = modifiedFiles.find((f) => f.path === path)
      if (!gitEntry) return 'clean'
      if (gitEntry.status === 'untracked' || gitEntry.status === 'added') return 'new'
      if (gitEntry.status === 'modified') return 'modified'
      return 'clean'
    }

    export function useVaultLoader(vaultPath: string) {
      const [entries, setEntries] = useState<VaultEntry[]>([])
      const [allContent, setAllContent] = useState<Record<string, string>>({})
      const [modifiedFiles, setModifiedFiles] = useState<ModifiedFile[]>([])
      const tracker = useNewNoteTracker()

      useEffect(() => {
        // eslint-disable-next-line react-hooks/set-state-in-effect -- clear stale data then load new vault
        setEntries([]); setAllContent({}); setModifiedFiles([]); tracker.clear()
        loadVaultData(vaultPath)
          .then(({ entries: e, allContent: c }) => { setEntries(e); setAllContent(c) })
          .catch((err) => console.warn('Vault scan failed:', err))
      }, [vaultPath]) // eslint-disable-line react-hooks/exhaustive-deps -- tracker.clear is stable

      const loadModifiedFiles = useCallback(async () => {
        try {
          setModifiedFiles(await tauriCall<ModifiedFile[]>('get_modified_files', { vaultPath }, {}))
        } catch (err) {
          console.warn('Failed to load modified files:', err)
          setModifiedFiles([])
        }
      }, [vaultPath])

      useEffect(() => { loadModifiedFiles() }, [loadModifiedFiles]) // eslint-disable-line react-hooks/set-state-in-effect -- trigger initial load

      const addEntry = useCallback((entry: VaultEntry, content: string) => {
        setEntries((prev) => {
          if (prev.some(e => e.path === entry.path)) return prev
          return [entry, ...prev]
        })
        setAllContent((prev) => ({ ...prev, [entry.path]: content }))
        tracker.trackNew(entry.path)
      }, [tracker])

      const updateContent = useCallback((path: string, content: string) =>
        setAllContent((prev) => ({ ...prev, [path]: content })), [])

      const updateEntry = useCallback((path: string, patch: Partial<VaultEntry>) =>
        setEntries((prev) => prev.map((e) => e.path === path ? { ...e, ...patch } : e)), [])

      const removeEntry = useCallback((path: string) => {
        setEntries((prev) => prev.filter((e) => e.path !== path))
        setAllContent((prev) => { const next = { ...prev }; delete next[path]; return next })
      }, [])

      const replaceEntry = useCallback((oldPath: string, patch: Partial<VaultEntry> & { path: string }, newContent: string) => {
        setEntries((prev) => prev.map((e) => e.path === oldPath ? { ...e, ...patch } : e))
        setAllContent((prev) => { const next = { ...prev }; delete next[oldPath]; next[patch.path] = newContent; return next })
      }, [])

      const loadGitHistory = useCallback(async (path: string): Promise<GitCommit[]> => {
        try { return await tauriCall<GitCommit[]>('get_file_history', { vaultPath, path }, { path }) }
        catch (err) { console.warn('Failed to load git history:', err); return [] }
      }, [vaultPath])

      const loadDiffAtCommit = useCallback((path: string, commitHash: string): Promise<string> =>
        tauriCall<string>('get_file_diff_at_commit', { vaultPath, path, commitHash }, { path, commitHash }), [vaultPath])

      const loadDiff = useCallback((path: string): Promise<string> =>
        tauriCall<string>('get_file_diff', { vaultPath, path }, { path }), [vaultPath])

      const getNoteStatus = useCallback((path: string): NoteStatus =>
        resolveNoteStatus(path, tracker.newPaths, modifiedFiles), [tracker.newPaths, modifiedFiles])

      const commitAndPush = useCallback((message: string): Promise<string> =>
        commitWithPush(vaultPath, message), [vaultPath])

      return {
        entries, allContent, modifiedFiles,
        addEntry, updateEntry, removeEntry, replaceEntry, updateContent,
        loadModifiedFiles, loadGitHistory, loadDiff, loadDiffAtCommit,
        getNoteStatus, commitAndPush,
      }
    }