diff --git a/src/App.tsx b/src/App.tsx index bb0d3fc1..77025b77 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -74,6 +74,7 @@ import { import { createViewFilename } from './utils/viewFilename' import { nextViewOrder } from './utils/viewOrdering' import { viewMatchesSelection, viewVaultPath } from './utils/viewIdentity' +import { viewCreationVaultPath } from './utils/viewTargetVault' import { ConflictResolverModal } from './components/ConflictResolverModal' import { ConfirmDeleteDialog } from './components/ConfirmDeleteDialog' import { DeleteProgressNotice } from './components/DeleteProgressNotice' @@ -1168,8 +1169,13 @@ function App() { const handleCreateOrUpdateView = useCallback(async (definition: ViewDefinition) => { const editing = dialogs.editingView - const activeVaultViews = viewsForVault(vault.views, resolvedPath) - const targetVaultPath = editing?.rootPath ?? resolvedPath + const targetVaultPath = viewCreationVaultPath({ + editingRootPath: editing?.rootPath, + fallbackVaultPath: resolvedPath, + graphDefaultWorkspacePath, + multiWorkspaceEnabled, + }) + const activeVaultViews = viewsForVault(vault.views, targetVaultPath) const filename = savedViewFilename(definition, editing, activeVaultViews) const nextDefinition = savedViewDefinition(definition, editing, activeVaultViews) const target = isTauri() ? invoke : mockInvoke @@ -1190,7 +1196,7 @@ function App() { setToastMessage(`Could not save view: ${message}`) return false } - }, [resolvedPath, vault, handleSetSelection, dialogs.editingView, setToastMessage]) + }, [graphDefaultWorkspacePath, multiWorkspaceEnabled, resolvedPath, vault, handleSetSelection, dialogs.editingView, setToastMessage]) const handleUpdateViewDefinition = useCallback(async (filename: string, patch: Partial, rootPath?: string) => { const existing = vault.views.find((view) => viewMatchesSelection(view, viewSelection(filename, rootPath))) diff --git a/src/utils/viewTargetVault.test.ts b/src/utils/viewTargetVault.test.ts new file mode 100644 index 00000000..cd772af1 --- /dev/null +++ b/src/utils/viewTargetVault.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' +import { viewCreationVaultPath } from './viewTargetVault' + +describe('viewCreationVaultPath', () => { + it('creates new views in the default workspace when multiple workspaces are mounted', () => { + expect(viewCreationVaultPath({ + fallbackVaultPath: '/portent', + graphDefaultWorkspacePath: '/laputa', + multiWorkspaceEnabled: true, + })).toBe('/laputa') + }) + + it('keeps edited views in their owning vault', () => { + expect(viewCreationVaultPath({ + editingRootPath: '/refactoring', + fallbackVaultPath: '/portent', + graphDefaultWorkspacePath: '/laputa', + multiWorkspaceEnabled: true, + })).toBe('/refactoring') + }) + + it('uses the active vault when workspace mounting is disabled', () => { + expect(viewCreationVaultPath({ + fallbackVaultPath: '/portent', + graphDefaultWorkspacePath: '/laputa', + multiWorkspaceEnabled: false, + })).toBe('/portent') + }) +}) diff --git a/src/utils/viewTargetVault.ts b/src/utils/viewTargetVault.ts new file mode 100644 index 00000000..ee788f37 --- /dev/null +++ b/src/utils/viewTargetVault.ts @@ -0,0 +1,22 @@ +interface ViewCreationVaultPathOptions { + editingRootPath?: string + fallbackVaultPath: string + graphDefaultWorkspacePath: string + multiWorkspaceEnabled: boolean +} + +function usablePath(path: string | null | undefined): string | null { + const trimmed = path?.trim() + return trimmed ? trimmed : null +} + +export function viewCreationVaultPath({ + editingRootPath, + fallbackVaultPath, + graphDefaultWorkspacePath, + multiWorkspaceEnabled, +}: ViewCreationVaultPathOptions): string { + return usablePath(editingRootPath) + ?? (multiWorkspaceEnabled ? usablePath(graphDefaultWorkspacePath) : null) + ?? fallbackVaultPath +}