fix: hide stale getting started vault entries

This commit is contained in:
lucaronin
2026-04-20 01:02:08 +02:00
parent 8b97ef711f
commit 74e50c64bb
3 changed files with 269 additions and 1 deletions

View File

@@ -195,6 +195,55 @@ describe('useVaultSwitcher', () => {
expect(result.current.isGettingStartedHidden).toBe(false)
})
it('drops stale canonical Getting Started entries when the starter path is missing', async () => {
mockVaultListStore = {
vaults: [
{ label: 'Getting Started', path: expectedDefaultVaultPath },
{ label: 'Work', path: '/work/vault' },
],
active_vault: '/work/vault',
hidden_defaults: [],
}
setMockInvokeBehavior({
checkVaultExists: ({ path }) => path === '/work/vault',
})
const { result } = await renderLoadedVaultSwitcher()
expect(result.current.allVaults).toEqual([{ label: 'Work', path: '/work/vault', available: true }])
expect(result.current.vaultPath).toBe('/work/vault')
await waitFor(() => {
expect(mockVaultListStore).toEqual({
vaults: [{ label: 'Work', path: '/work/vault' }],
active_vault: '/work/vault',
hidden_defaults: [],
})
})
})
it('clears a stale canonical Getting Started selection when the starter path is missing', async () => {
mockVaultListStore = {
vaults: [{ label: 'Getting Started', path: expectedDefaultVaultPath }],
active_vault: expectedDefaultVaultPath,
hidden_defaults: [],
}
setMockInvokeBehavior({ checkVaultExists: false })
const { result } = await renderLoadedVaultSwitcher()
expect(result.current.allVaults).toEqual([])
expect(result.current.selectedVaultPath).toBeNull()
await waitFor(() => {
expect(mockVaultListStore).toEqual({
vaults: [],
active_vault: null,
hidden_defaults: [],
})
})
})
it('handles load error gracefully', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
mockInvokeFn.mockImplementation((cmd: string) => {

View File

@@ -137,6 +137,18 @@ function syncDefaultVaultExport(path: string) {
DEFAULT_VAULTS[0] = { label: GETTING_STARTED_LABEL, path }
}
function isCanonicalGettingStartedPath(path: string, resolvedDefaultPath: string): boolean {
return path === resolvedDefaultPath
}
function isUnavailableGettingStartedVault(vault: VaultOption): boolean {
return vault.label === GETTING_STARTED_LABEL && vault.available === false
}
function shouldDropPersistedGettingStartedVault(vault: VaultOption, resolvedDefaultPath: string): boolean {
return isCanonicalGettingStartedPath(vault.path, resolvedDefaultPath) || isUnavailableGettingStartedVault(vault)
}
async function checkVaultAvailability(path: string): Promise<boolean> {
if (!path) {
return false
@@ -166,7 +178,79 @@ async function loadInitialVaultState() {
console.warn('Failed to load vault list:', vaultListResult.reason)
}
return { activeVault, defaultAvailable, hiddenDefaults, resolvedDefaultPath, vaults }
return sanitizeCanonicalGettingStartedState({
activeVault,
defaultAvailable,
hiddenDefaults,
resolvedDefaultPath,
vaults,
})
}
function sanitizeCanonicalGettingStartedState({
activeVault,
defaultAvailable,
hiddenDefaults,
resolvedDefaultPath,
vaults,
}: {
activeVault: string | null
defaultAvailable: boolean
hiddenDefaults: string[]
resolvedDefaultPath: string
vaults: VaultOption[]
}) {
if (!resolvedDefaultPath) {
return { activeVault, defaultAvailable, hiddenDefaults, resolvedDefaultPath, vaults }
}
const filteredVaults = vaults.filter(
(vault) => !shouldDropPersistedGettingStartedVault(vault, resolvedDefaultPath),
)
const removedStarterPaths = new Set(
vaults
.filter((vault) => shouldDropPersistedGettingStartedVault(vault, resolvedDefaultPath))
.map((vault) => vault.path),
)
const sanitizedActiveVault = resolveSanitizedGettingStartedSelection({
activeVault,
defaultAvailable,
filteredVaults,
removedStarterPaths,
resolvedDefaultPath,
})
return {
activeVault: sanitizedActiveVault,
defaultAvailable,
hiddenDefaults,
resolvedDefaultPath,
vaults: filteredVaults,
}
}
function resolveSanitizedGettingStartedSelection({
activeVault,
defaultAvailable,
filteredVaults,
removedStarterPaths,
resolvedDefaultPath,
}: {
activeVault: string | null
defaultAvailable: boolean
filteredVaults: VaultOption[]
removedStarterPaths: Set<string>
resolvedDefaultPath: string
}): string | null {
if (!activeVault || !removedStarterPaths.has(activeVault)) {
return activeVault
}
if (isCanonicalGettingStartedPath(activeVault, resolvedDefaultPath) && defaultAvailable) {
return activeVault
}
return filteredVaults[0]?.path ?? null
}
function buildDefaultVaults({

View File

@@ -0,0 +1,135 @@
import { test, expect, type Page } from '@playwright/test'
interface StaleStarterPaths {
gettingStartedPath: string
personalVaultPath: string
workVaultPath: string
}
function buildPaths(): StaleStarterPaths {
return {
gettingStartedPath: '/Users/mock/Documents/Getting Started',
personalVaultPath: '/Users/mock/Personal',
workVaultPath: '/Users/mock/Work',
}
}
async function installStaleStarterMocks(page: Page) {
const paths = buildPaths()
await page.addInitScript((data: StaleStarterPaths) => {
localStorage.clear()
localStorage.setItem('tolaria:claude-code-onboarding-dismissed', '1')
let ref: Record<string, unknown> | null = null
Object.defineProperty(window, '__mockHandlers', {
configurable: true,
set(value) {
ref = value as Record<string, unknown>
ref.load_vault_list = () => ({
vaults: [
{ label: 'Getting Started', path: data.gettingStartedPath },
{ label: 'Work Vault', path: data.workVaultPath },
{ label: 'Personal Vault', path: data.personalVaultPath },
],
active_vault: data.workVaultPath,
hidden_defaults: [],
})
ref.get_default_vault_path = () => data.gettingStartedPath
ref.check_vault_exists = (args: { path?: string }) =>
args?.path === data.workVaultPath || args?.path === data.personalVaultPath
ref.list_vault = (args: { path?: string }) => {
if (args?.path === data.workVaultPath) {
return [{
path: `${data.workVaultPath}/work-home.md`,
filename: 'work-home.md',
title: 'Work Home',
isA: 'Note',
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
archived: false,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 256,
snippet: 'Work Home snippet',
wordCount: 12,
relationships: {},
outgoingLinks: [],
properties: {},
template: null,
sort: null,
}]
}
if (args?.path === data.personalVaultPath) {
return [{
path: `${data.personalVaultPath}/personal-home.md`,
filename: 'personal-home.md',
title: 'Personal Home',
isA: 'Note',
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
archived: false,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 256,
snippet: 'Personal Home snippet',
wordCount: 12,
relationships: {},
outgoingLinks: [],
properties: {},
template: null,
sort: null,
}]
}
return []
}
ref.list_vault_folders = () => []
ref.list_views = () => []
ref.get_all_content = () => ({
[`${data.workVaultPath}/work-home.md`]: '# Work Home\n\nWork Home snippet',
[`${data.personalVaultPath}/personal-home.md`]: '# Personal Home\n\nPersonal Home snippet',
})
ref.get_note_content = (args: { path?: string }) => {
if (args?.path === `${data.workVaultPath}/work-home.md`) {
return '# Work Home\n\nWork Home snippet'
}
if (args?.path === `${data.personalVaultPath}/personal-home.md`) {
return '# Personal Home\n\nPersonal Home snippet'
}
return ''
}
ref.get_modified_files = () => []
ref.get_file_history = () => []
},
get() {
return ref
},
})
}, paths)
}
test('stale persisted Getting Started entries stay hidden when the starter path is missing @smoke', async ({ page }) => {
await installStaleStarterMocks(page)
await page.goto('/', { waitUntil: 'domcontentloaded' })
const trigger = page.getByTestId('status-vault-trigger')
await expect(trigger).toContainText('Work Vault')
await trigger.focus()
await expect(trigger).toBeFocused()
await page.keyboard.press('Enter')
await expect(page.getByTestId('vault-menu-item-Getting Started')).toHaveCount(0)
await expect(page.getByTestId('vault-menu-item-Work Vault')).toBeVisible()
await expect(page.getByTestId('vault-menu-item-Personal Vault')).toBeVisible()
})