Compare commits
2 Commits
v0.2026030
...
v0.2026030
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a92b4694c | ||
|
|
6b9ff9a4a2 |
@@ -1 +1 @@
|
||||
21351
|
||||
3852
|
||||
|
||||
@@ -274,6 +274,16 @@ fn save_settings(settings: Settings) -> Result<(), String> {
|
||||
settings::save_settings(settings)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_last_vault_path() -> Option<String> {
|
||||
settings::get_last_vault()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_last_vault_path(path: String) -> Result<(), String> {
|
||||
settings::set_last_vault(&path)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn github_list_repos(token: String) -> Result<Vec<GithubRepo>, String> {
|
||||
github::github_list_repos(&token).await
|
||||
@@ -539,6 +549,8 @@ pub fn run() {
|
||||
get_settings,
|
||||
update_menu_state,
|
||||
save_settings,
|
||||
get_last_vault_path,
|
||||
set_last_vault_path,
|
||||
github_list_repos,
|
||||
github_create_repo,
|
||||
clone_repo,
|
||||
|
||||
@@ -71,6 +71,36 @@ pub fn save_settings(settings: Settings) -> Result<(), String> {
|
||||
save_settings_at(&settings_path()?, settings)
|
||||
}
|
||||
|
||||
fn last_vault_file() -> Result<PathBuf, String> {
|
||||
dirs::config_dir()
|
||||
.map(|d| d.join("com.laputa.app").join("last-vault.txt"))
|
||||
.ok_or_else(|| "Could not determine config directory".to_string())
|
||||
}
|
||||
|
||||
fn get_last_vault_at(path: &PathBuf) -> Option<String> {
|
||||
fs::read_to_string(path)
|
||||
.ok()
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
fn set_last_vault_at(path: &PathBuf, vault_path: &str) -> Result<(), String> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.map_err(|e| format!("Failed to create config directory: {}", e))?;
|
||||
}
|
||||
fs::write(path, vault_path.trim())
|
||||
.map_err(|e| format!("Failed to write last vault path: {}", e))
|
||||
}
|
||||
|
||||
pub fn get_last_vault() -> Option<String> {
|
||||
last_vault_file().ok().and_then(|p| get_last_vault_at(&p))
|
||||
}
|
||||
|
||||
pub fn set_last_vault(vault_path: &str) -> Result<(), String> {
|
||||
set_last_vault_at(&last_vault_file()?, vault_path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -199,4 +229,65 @@ mod tests {
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().to_str().unwrap().contains("com.laputa.app"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_last_vault_returns_none_for_missing_file() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("last-vault.txt");
|
||||
assert!(get_last_vault_at(&path).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_and_get_last_vault_roundtrip() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("last-vault.txt");
|
||||
set_last_vault_at(&path, "/Users/test/MyVault").unwrap();
|
||||
assert_eq!(
|
||||
get_last_vault_at(&path).as_deref(),
|
||||
Some("/Users/test/MyVault")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_last_vault_trims_whitespace() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("last-vault.txt");
|
||||
set_last_vault_at(&path, " /Users/test/Vault ").unwrap();
|
||||
assert_eq!(
|
||||
get_last_vault_at(&path).as_deref(),
|
||||
Some("/Users/test/Vault")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_last_vault_returns_none_for_empty_file() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("last-vault.txt");
|
||||
fs::write(&path, " \n ").unwrap();
|
||||
assert!(get_last_vault_at(&path).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_last_vault_creates_parent_directories() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("nested").join("dir").join("last-vault.txt");
|
||||
set_last_vault_at(&path, "/Users/test/Vault").unwrap();
|
||||
assert!(path.exists());
|
||||
assert_eq!(
|
||||
get_last_vault_at(&path).as_deref(),
|
||||
Some("/Users/test/Vault")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_last_vault_overwrites_previous() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("last-vault.txt");
|
||||
set_last_vault_at(&path, "/Users/test/OldVault").unwrap();
|
||||
set_last_vault_at(&path, "/Users/test/NewVault").unwrap();
|
||||
assert_eq!(
|
||||
get_last_vault_at(&path).as_deref(),
|
||||
Some("/Users/test/NewVault")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ function App() {
|
||||
if (entry) notes.handleSelectNote(entry)
|
||||
},
|
||||
onOpenVault: vaultSwitcher.handleOpenLocalFolder,
|
||||
onCreateType: dialogs.openCreateType,
|
||||
onToggleAIChat: dialogs.toggleAIChat,
|
||||
onCheckForUpdates: handleCheckForUpdates,
|
||||
isUpdating: updateStatus.state === 'downloading' || updateStatus.state === 'ready',
|
||||
|
||||
@@ -53,6 +53,7 @@ interface AppCommandsConfig {
|
||||
onCreateTheme?: () => void
|
||||
onOpenTheme?: (themeId: string) => void
|
||||
onOpenVault?: () => void
|
||||
onCreateType?: () => void
|
||||
onToggleAIChat?: () => void
|
||||
onCheckForUpdates?: () => void
|
||||
isUpdating?: boolean
|
||||
@@ -152,6 +153,7 @@ export function useAppCommands(config: AppCommandsConfig): CommandAction[] {
|
||||
onCreateTheme: config.onCreateTheme,
|
||||
onOpenTheme: config.onOpenTheme,
|
||||
onOpenVault: config.onOpenVault,
|
||||
onCreateType: config.onCreateType,
|
||||
onToggleAIChat: config.onToggleAIChat,
|
||||
onCheckForUpdates: config.onCheckForUpdates,
|
||||
isUpdating: config.isUpdating,
|
||||
|
||||
@@ -316,6 +316,41 @@ describe('useCommandRegistry', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('create-type command', () => {
|
||||
it('has create-type command in Note group when onCreateType is provided', () => {
|
||||
const onCreateType = vi.fn()
|
||||
const { result } = renderHook(() => useCommandRegistry(makeConfig({ onCreateType })))
|
||||
const cmd = result.current.find(c => c.id === 'create-type')
|
||||
expect(cmd).toBeDefined()
|
||||
expect(cmd!.label).toBe('New Type')
|
||||
expect(cmd!.group).toBe('Note')
|
||||
expect(cmd!.enabled).toBe(true)
|
||||
})
|
||||
|
||||
it('is disabled when onCreateType is not provided', () => {
|
||||
const { result } = renderHook(() => useCommandRegistry(makeConfig()))
|
||||
const cmd = result.current.find(c => c.id === 'create-type')
|
||||
expect(cmd).toBeDefined()
|
||||
expect(cmd!.enabled).toBe(false)
|
||||
})
|
||||
|
||||
it('calls onCreateType when executed', () => {
|
||||
const onCreateType = vi.fn()
|
||||
const { result } = renderHook(() => useCommandRegistry(makeConfig({ onCreateType })))
|
||||
result.current.find(c => c.id === 'create-type')!.execute()
|
||||
expect(onCreateType).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('has relevant keywords for discoverability', () => {
|
||||
const onCreateType = vi.fn()
|
||||
const { result } = renderHook(() => useCommandRegistry(makeConfig({ onCreateType })))
|
||||
const cmd = result.current.find(c => c.id === 'create-type')
|
||||
expect(cmd!.keywords).toContain('new')
|
||||
expect(cmd!.keywords).toContain('create')
|
||||
expect(cmd!.keywords).toContain('type')
|
||||
})
|
||||
})
|
||||
|
||||
describe('type-aware commands', () => {
|
||||
it('generates "New [Type]" commands from vault entries', () => {
|
||||
const entries = [
|
||||
|
||||
@@ -25,6 +25,7 @@ interface CommandRegistryConfig {
|
||||
onSave: () => void
|
||||
onOpenSettings: () => void
|
||||
onOpenVault?: () => void
|
||||
onCreateType?: () => void
|
||||
onTrashNote: (path: string) => void
|
||||
onRestoreNote: (path: string) => void
|
||||
onArchiveNote: (path: string) => void
|
||||
@@ -178,6 +179,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
|
||||
onGoBack, onGoForward, canGoBack, canGoForward,
|
||||
themes, activeThemeId, onSwitchTheme, onCreateTheme, onOpenTheme,
|
||||
onCheckForUpdates, isUpdating,
|
||||
onCreateType,
|
||||
} = config
|
||||
|
||||
const hasActiveNote = activeTabPath !== null
|
||||
@@ -205,6 +207,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
|
||||
|
||||
// Note actions (contextual)
|
||||
{ id: 'create-note', label: 'Create New Note', group: 'Note', shortcut: '⌘N', keywords: ['new', 'add'], enabled: true, execute: onCreateNote },
|
||||
{ id: 'create-type', label: 'New Type', group: 'Note', keywords: ['new', 'create', 'type', 'template'], enabled: !!onCreateType, execute: () => onCreateType?.() },
|
||||
{ id: 'open-daily-note', label: "Open Today's Note", group: 'Note', shortcut: '⌘J', keywords: ['daily', 'journal', 'today'], enabled: true, execute: onOpenDailyNote },
|
||||
{ id: 'save-note', label: 'Save Note', group: 'Note', shortcut: '⌘S', keywords: ['write'], enabled: hasActiveNote, execute: onSave },
|
||||
{ id: 'close-tab', label: 'Close Tab', group: 'Note', shortcut: '⌘W', keywords: [], enabled: hasActiveNote, execute: () => { if (activeTabPath) onCloseTab(activeTabPath) } },
|
||||
@@ -241,7 +244,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
|
||||
return cmds
|
||||
}, [
|
||||
hasActiveNote, activeTabPath, isArchived, isTrashed, modifiedCount,
|
||||
onQuickOpen, onCreateNote, onCreateNoteOfType, onSave, onOpenSettings,
|
||||
onQuickOpen, onCreateNote, onCreateNoteOfType, onCreateType, onSave, onOpenSettings,
|
||||
onTrashNote, onRestoreNote, onArchiveNote, onUnarchiveNote,
|
||||
onCommitPush, onSetViewMode, onToggleInspector, onToggleRawEditor, onToggleAIChat, onOpenVault,
|
||||
onCheckForUpdates, isUpdating,
|
||||
|
||||
@@ -20,12 +20,17 @@ vi.mock('../mock-tauri', () => ({
|
||||
mockInvoke: (...args: unknown[]) => mockInvokeFn(...args),
|
||||
}))
|
||||
|
||||
vi.mock('./useVaultSwitcher', () => ({
|
||||
persistLastVault: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../utils/vault-dialog', () => ({
|
||||
pickFolder: vi.fn(),
|
||||
}))
|
||||
|
||||
import { useOnboarding } from './useOnboarding'
|
||||
import { pickFolder } from '../utils/vault-dialog'
|
||||
import { persistLastVault } from './useVaultSwitcher'
|
||||
|
||||
describe('useOnboarding', () => {
|
||||
beforeEach(() => {
|
||||
@@ -104,6 +109,7 @@ describe('useOnboarding', () => {
|
||||
|
||||
expect(result.current.state).toEqual({ status: 'ready', vaultPath: '/mock/Documents/Getting Started' })
|
||||
expect(localStorage.getItem('laputa_welcome_dismissed')).toBe('1')
|
||||
expect(persistLastVault).toHaveBeenCalledWith('/mock/Documents/Getting Started')
|
||||
})
|
||||
|
||||
it('handleCreateVault sets error on failure', async () => {
|
||||
@@ -148,6 +154,7 @@ describe('useOnboarding', () => {
|
||||
|
||||
expect(result.current.state).toEqual({ status: 'ready', vaultPath: '/selected/folder' })
|
||||
expect(localStorage.getItem('laputa_welcome_dismissed')).toBe('1')
|
||||
expect(persistLastVault).toHaveBeenCalledWith('/selected/folder')
|
||||
})
|
||||
|
||||
it('handleOpenFolder does nothing when picker is cancelled', async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { isTauri, mockInvoke } from '../mock-tauri'
|
||||
import { pickFolder } from '../utils/vault-dialog'
|
||||
import { persistLastVault } from './useVaultSwitcher'
|
||||
|
||||
type OnboardingState =
|
||||
| { status: 'loading' }
|
||||
@@ -70,6 +71,7 @@ export function useOnboarding(initialVaultPath: string) {
|
||||
try {
|
||||
const vaultPath = await tauriCall<string>('create_getting_started_vault', { targetPath: null })
|
||||
markDismissed()
|
||||
persistLastVault(vaultPath)
|
||||
setState({ status: 'ready', vaultPath })
|
||||
} catch (err) {
|
||||
setError(typeof err === 'string' ? err : `Failed to create vault: ${err}`)
|
||||
@@ -83,6 +85,7 @@ export function useOnboarding(initialVaultPath: string) {
|
||||
const path = await pickFolder('Open vault folder')
|
||||
if (!path) return
|
||||
markDismissed()
|
||||
persistLastVault(path)
|
||||
setState({ status: 'ready', vaultPath: path })
|
||||
} catch (err) {
|
||||
setError(`Failed to open folder: ${err}`)
|
||||
|
||||
133
src/hooks/useVaultSwitcher.test.ts
Normal file
133
src/hooks/useVaultSwitcher.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { renderHook, waitFor, act } from '@testing-library/react'
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
const mockInvokeFn = vi.fn()
|
||||
|
||||
vi.mock('../mock-tauri', () => ({
|
||||
isTauri: () => false,
|
||||
mockInvoke: (...args: unknown[]) => mockInvokeFn(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../utils/vault-dialog', () => ({
|
||||
pickFolder: vi.fn(),
|
||||
}))
|
||||
|
||||
import { useVaultSwitcher, DEFAULT_VAULTS, persistLastVault } from './useVaultSwitcher'
|
||||
import { pickFolder } from '../utils/vault-dialog'
|
||||
|
||||
describe('useVaultSwitcher', () => {
|
||||
const onSwitch = vi.fn()
|
||||
const onToast = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockInvokeFn.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'get_last_vault_path') return null
|
||||
if (cmd === 'set_last_vault_path') return null
|
||||
return null
|
||||
})
|
||||
})
|
||||
|
||||
it('starts with the default vault path', () => {
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
expect(result.current.vaultPath).toBe(DEFAULT_VAULTS[0].path)
|
||||
})
|
||||
|
||||
it('loads last vault path from backend on mount', async () => {
|
||||
mockInvokeFn.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'get_last_vault_path') return '/Users/test/MyVault'
|
||||
if (cmd === 'set_last_vault_path') return null
|
||||
return null
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.vaultPath).toBe('/Users/test/MyVault')
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to default when no last vault is stored', async () => {
|
||||
mockInvokeFn.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'get_last_vault_path') return null
|
||||
return null
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
|
||||
// Wait for the async load to complete
|
||||
await waitFor(() => {
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('get_last_vault_path', {})
|
||||
})
|
||||
|
||||
expect(result.current.vaultPath).toBe(DEFAULT_VAULTS[0].path)
|
||||
})
|
||||
|
||||
it('falls back to default when get_last_vault_path fails', async () => {
|
||||
mockInvokeFn.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'get_last_vault_path') throw new Error('read failed')
|
||||
return null
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
|
||||
// Wait for the async load to complete
|
||||
await waitFor(() => {
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('get_last_vault_path', {})
|
||||
})
|
||||
|
||||
expect(result.current.vaultPath).toBe(DEFAULT_VAULTS[0].path)
|
||||
})
|
||||
|
||||
it('persists vault path when switching', async () => {
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
|
||||
act(() => {
|
||||
result.current.switchVault('/Users/test/NewVault')
|
||||
})
|
||||
|
||||
expect(result.current.vaultPath).toBe('/Users/test/NewVault')
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('set_last_vault_path', { path: '/Users/test/NewVault' })
|
||||
})
|
||||
|
||||
it('persists vault path on handleOpenLocalFolder', async () => {
|
||||
vi.mocked(pickFolder).mockResolvedValue('/Users/test/OpenedVault')
|
||||
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleOpenLocalFolder()
|
||||
})
|
||||
|
||||
expect(result.current.vaultPath).toBe('/Users/test/OpenedVault')
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('set_last_vault_path', { path: '/Users/test/OpenedVault' })
|
||||
})
|
||||
|
||||
it('persists vault path on handleVaultCloned', async () => {
|
||||
const { result } = renderHook(() => useVaultSwitcher({ onSwitch, onToast }))
|
||||
|
||||
act(() => {
|
||||
result.current.handleVaultCloned('/Users/test/ClonedVault', 'ClonedVault')
|
||||
})
|
||||
|
||||
expect(result.current.vaultPath).toBe('/Users/test/ClonedVault')
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('set_last_vault_path', { path: '/Users/test/ClonedVault' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('persistLastVault', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockInvokeFn.mockResolvedValue(null)
|
||||
})
|
||||
|
||||
it('calls set_last_vault_path with the given path', () => {
|
||||
persistLastVault('/Users/test/Vault')
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('set_last_vault_path', { path: '/Users/test/Vault' })
|
||||
})
|
||||
|
||||
it('does not throw when the backend call fails', () => {
|
||||
mockInvokeFn.mockRejectedValue(new Error('write failed'))
|
||||
expect(() => persistLastVault('/Users/test/Vault')).not.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { isTauri, mockInvoke } from '../mock-tauri'
|
||||
import { pickFolder } from '../utils/vault-dialog'
|
||||
import type { VaultOption } from '../components/StatusBar'
|
||||
|
||||
@@ -11,12 +13,27 @@ interface UseVaultSwitcherOptions {
|
||||
onToast: (msg: string) => void
|
||||
}
|
||||
|
||||
function tauriCall<T>(command: string, args: Record<string, unknown>): Promise<T> {
|
||||
return isTauri() ? invoke<T>(command, args) : mockInvoke<T>(command, args)
|
||||
}
|
||||
|
||||
export function persistLastVault(path: string): void {
|
||||
tauriCall('set_last_vault_path', { path }).catch(() => {})
|
||||
}
|
||||
|
||||
/** Manages vault path, extra vaults, switching, cloning, and local folder opening. */
|
||||
export function useVaultSwitcher({ onSwitch, onToast }: UseVaultSwitcherOptions) {
|
||||
const [vaultPath, setVaultPath] = useState(DEFAULT_VAULTS[0].path)
|
||||
const [extraVaults, setExtraVaults] = useState<VaultOption[]>([])
|
||||
const allVaults = useMemo(() => [...DEFAULT_VAULTS, ...extraVaults], [extraVaults])
|
||||
|
||||
// On mount, load the last vault path from persistent storage
|
||||
useEffect(() => {
|
||||
tauriCall<string | null>('get_last_vault_path', {}).then((saved) => {
|
||||
if (saved) setVaultPath(saved)
|
||||
}).catch(() => {})
|
||||
}, [])
|
||||
|
||||
// Refs ensure stable callbacks that always invoke the latest closures,
|
||||
// breaking the circular dependency between useVaultSwitcher and downstream hooks.
|
||||
const onSwitchRef = useRef(onSwitch)
|
||||
@@ -29,6 +46,7 @@ export function useVaultSwitcher({ onSwitch, onToast }: UseVaultSwitcherOptions)
|
||||
|
||||
const switchVault = useCallback((path: string) => {
|
||||
setVaultPath(path)
|
||||
persistLastVault(path)
|
||||
onSwitchRef.current()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ let mockSettings: Settings = {
|
||||
auto_pull_interval_minutes: 5,
|
||||
}
|
||||
|
||||
let mockLastVaultPath: string | null = null
|
||||
|
||||
let mockVaultSettings: VaultSettings = { theme: null }
|
||||
|
||||
const mockThemes: ThemeFile[] = [
|
||||
@@ -245,6 +247,8 @@ export const mockHandlers: Record<string, (args: any) => any> = {
|
||||
}))
|
||||
return { results: matches, elapsed_ms: 42, query: q, mode: args.mode }
|
||||
},
|
||||
get_last_vault_path: () => mockLastVaultPath,
|
||||
set_last_vault_path: (args: { path: string }) => { mockLastVaultPath = args.path; return null },
|
||||
get_default_vault_path: () => '/Users/mock/Documents/Getting Started',
|
||||
check_vault_exists: (args: { path: string }) => {
|
||||
// In mock mode, the demo-vault-v2 path always "exists"
|
||||
|
||||
Reference in New Issue
Block a user