From ce04a49bae92d98cb6ba7830688e28b7dbde6005 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Fri, 15 May 2026 11:18:51 +0200 Subject: [PATCH] fix: keep default workspace views mounted --- src/components/StatusBar.test.tsx | 6 ++--- src/components/status-bar/VaultMenu.tsx | 11 ++++++--- src/hooks/useWorkspaceIdentityActions.ts | 3 ++- src/hooks/vaultLoaderCommands.ts | 10 ++++---- src/hooks/vaultLoaderCommands.views.test.ts | 26 +++++++++++++++++++++ 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/components/StatusBar.test.tsx b/src/components/StatusBar.test.tsx index 018a07ff..caef6321 100644 --- a/src/components/StatusBar.test.tsx +++ b/src/components/StatusBar.test.tsx @@ -245,7 +245,7 @@ describe('StatusBar', () => { expect(screen.getByText('Work Vault')).toBeInTheDocument() }) - it('shows the active workspace as checked because it is always included', () => { + it('shows the active workspace real mount state so stale unmounted defaults can be repaired', () => { render( { fireEvent.click(screen.getByRole('button', { name: 'Switch vault' })) const activeCheckbox = screen.getByRole('checkbox', { name: 'Include Main Vault in the unified graph' }) - expect(activeCheckbox).toBeChecked() - expect(activeCheckbox).toBeDisabled() + expect(activeCheckbox).not.toBeChecked() + expect(activeCheckbox).not.toBeDisabled() }) it('uses the expanded multi-workspace vault picker layout', () => { diff --git a/src/components/status-bar/VaultMenu.tsx b/src/components/status-bar/VaultMenu.tsx index 799de544..14bfc79a 100644 --- a/src/components/status-bar/VaultMenu.tsx +++ b/src/components/status-bar/VaultMenu.tsx @@ -95,6 +95,7 @@ interface MountToggleRequest { canSetDefaultWorkspace: boolean defaultPath: string includedVaultCount: number + isMounted: boolean path: string } @@ -304,9 +305,12 @@ function shouldDisableMountToggle({ canSetDefaultWorkspace, defaultPath, includedVaultCount, + isMounted, path, }: MountToggleRequest): boolean { - return path === defaultPath && (includedVaultCount <= 1 || !canSetDefaultWorkspace) + return path === defaultPath + && isMounted + && (includedVaultCount <= 1 || !canSetDefaultWorkspace) } function selectVaultPath({ @@ -351,9 +355,10 @@ function useVaultMenuInteractions({ canSetDefaultWorkspace: !!onSetDefaultWorkspace, defaultPath, includedVaultCount: includedVaults.length, + isMounted: includedVaults.find((vault) => vault.path === path)?.mounted !== false, path, }) - ), [defaultPath, includedVaults.length, onSetDefaultWorkspace]) + ), [defaultPath, includedVaults, onSetDefaultWorkspace]) const handleSelectVault = useCallback((path: string) => { selectVaultPath({ @@ -435,7 +440,7 @@ function VaultMenuItem({ > {multiWorkspaceEnabled && ( { setDefaultWorkspacePath(path) - }, [setDefaultWorkspacePath]) + setExtraVaults(updateVaultOptionInList(path, { mounted: true })) + }, [setDefaultWorkspacePath, setExtraVaults]) return { setDefaultWorkspace, updateWorkspaceIdentity } } diff --git a/src/hooks/vaultLoaderCommands.ts b/src/hooks/vaultLoaderCommands.ts index 6792969e..1f5e10b5 100644 --- a/src/hooks/vaultLoaderCommands.ts +++ b/src/hooks/vaultLoaderCommands.ts @@ -69,8 +69,9 @@ function loadVaultEntriesWithCommand({ vaultPath, command }: VaultPathOptions & .then((entries) => normalizeVaultEntries(entries, vaultPath)) } -function shouldSkipMountedVault(vault: VaultOption): boolean { - return vault.available === false || vault.mounted === false || !vault.path.trim() +function shouldIncludeVault(vault: VaultOption, primaryPaths: Set): boolean { + if (!vault.path.trim() || vault.available === false) return false + return vault.mounted !== false || primaryPaths.has(vault.path) } function shouldReloadEmptyWorkspaceResult(entries: VaultEntry[], options: WorkspaceEntryLoadOptions): boolean { @@ -109,10 +110,11 @@ export function loadWorkspaceEntries( : entries) } -function uniqueMountedVaults({ vaultPath, vaults = [], includeFallbackVault = true }: MountedVaultEntriesOptions): VaultOption[] { +function uniqueMountedVaults({ defaultWorkspacePath, vaultPath, vaults = [], includeFallbackVault = true }: MountedVaultEntriesOptions): VaultOption[] { const byPath = new Map() + const primaryPaths = new Set([vaultPath, defaultWorkspacePath ?? ''].filter((path) => path.trim())) for (const vault of vaults) { - if (shouldSkipMountedVault(vault)) continue + if (!shouldIncludeVault(vault, primaryPaths)) continue byPath.set(vault.path, vault) } if (shouldIncludeFallbackVault(byPath, vaultPath, includeFallbackVault)) { diff --git a/src/hooks/vaultLoaderCommands.views.test.ts b/src/hooks/vaultLoaderCommands.views.test.ts index 907056c5..5a0e6ae6 100644 --- a/src/hooks/vaultLoaderCommands.views.test.ts +++ b/src/hooks/vaultLoaderCommands.views.test.ts @@ -60,4 +60,30 @@ describe('loadMountedVaultViews', () => { { filename: 'focus.yml', name: 'Team Focus', rootPath: '/team', workspacePath: '/team' }, ]) }) + + it('loads saved views from the default workspace even when stale state marks it unmounted', async () => { + mockInvoke.mockImplementation((command: string, args?: Record) => { + if (command !== 'list_views') return Promise.resolve([]) + + if (args?.vaultPath === '/default') { + return Promise.resolve([{ filename: 'default.yml', definition: viewDefinition('Default View') }]) + } + if (args?.vaultPath === '/mounted') { + return Promise.resolve([{ filename: 'mounted.yml', definition: viewDefinition('Mounted View') }]) + } + throw new Error(`Unexpected vaultPath ${String(args?.vaultPath)}`) + }) + + const views = await loadMountedVaultViews({ + defaultWorkspacePath: '/default', + vaultPath: '/default', + vaults: [ + { label: 'Default', path: '/default', alias: 'default', mounted: false, available: true }, + { label: 'Mounted', path: '/mounted', alias: 'mounted', mounted: true, available: true }, + { label: 'Hidden', path: '/hidden', alias: 'hidden', mounted: false, available: true }, + ], + }) + + expect(views.map((view) => view.definition.name)).toEqual(['Default View', 'Mounted View']) + }) })