fix: clear missing active vault paths
This commit is contained in:
@@ -94,9 +94,9 @@ describe('WelcomeScreen', () => {
|
||||
expect(screen.getByText(/could not be found on disk/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows the missing vault path in a badge', () => {
|
||||
it('does not show the missing vault path in a badge', () => {
|
||||
render(<WelcomeScreen {...missingProps} />)
|
||||
expect(screen.getByText('~/Laputa')).toBeInTheDocument()
|
||||
expect(screen.queryByText('~/Laputa')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows "Choose a different folder" instead of "Open existing vault"', () => {
|
||||
|
||||
@@ -103,14 +103,6 @@ const OPTION_DESC_STYLE: React.CSSProperties = {
|
||||
marginTop: 2,
|
||||
}
|
||||
|
||||
const PATH_BADGE_STYLE: React.CSSProperties = {
|
||||
width: '100%',
|
||||
background: 'var(--sidebar)',
|
||||
borderRadius: 6,
|
||||
padding: '8px 12px',
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const ERROR_STYLE: React.CSSProperties = {
|
||||
fontSize: 13,
|
||||
color: 'var(--destructive, #e03e3e)',
|
||||
@@ -155,7 +147,7 @@ function OptionButton({ icon, iconBg, label, description, onClick, disabled, loa
|
||||
)
|
||||
}
|
||||
|
||||
export function WelcomeScreen({ mode, missingPath, defaultVaultPath, onCreateVault, onCreateNewVault, onOpenFolder, creating, error }: WelcomeScreenProps) {
|
||||
export function WelcomeScreen({ mode, defaultVaultPath, onCreateVault, onCreateNewVault, onOpenFolder, creating, error }: WelcomeScreenProps) {
|
||||
const isWelcome = mode === 'welcome'
|
||||
|
||||
return (
|
||||
@@ -185,14 +177,6 @@ export function WelcomeScreen({ mode, missingPath, defaultVaultPath, onCreateVau
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isWelcome && missingPath && (
|
||||
<div style={PATH_BADGE_STYLE}>
|
||||
<code style={{ fontSize: 12, color: 'var(--muted-foreground)', fontFamily: 'var(--font-mono, monospace)' }}>
|
||||
{missingPath}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={DIVIDER_STYLE} />
|
||||
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
|
||||
@@ -87,6 +87,38 @@ describe('useOnboarding', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('clears the persisted active vault when the saved path no longer exists', async () => {
|
||||
localStorage.setItem('laputa_welcome_dismissed', '1')
|
||||
|
||||
mockInvokeFn.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'get_default_vault_path') return '/mock/Documents/Getting Started'
|
||||
if (cmd === 'check_vault_exists') return false
|
||||
if (cmd === 'load_vault_list') {
|
||||
return {
|
||||
vaults: [{ label: 'Old Vault', path: '/vault/deleted' }],
|
||||
active_vault: '/vault/deleted',
|
||||
hidden_defaults: [],
|
||||
}
|
||||
}
|
||||
if (cmd === 'save_vault_list') return null
|
||||
return null
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useOnboarding('/vault/deleted'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.state.status).toBe('vault-missing')
|
||||
})
|
||||
|
||||
expect(mockInvokeFn).toHaveBeenCalledWith('save_vault_list', {
|
||||
list: {
|
||||
vaults: [{ label: 'Old Vault', path: '/vault/deleted' }],
|
||||
active_vault: null,
|
||||
hidden_defaults: [],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('handleCreateVault creates vault and transitions to ready', async () => {
|
||||
mockInvokeFn.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === 'get_default_vault_path') return '/mock/Documents/Getting Started'
|
||||
|
||||
@@ -15,6 +15,12 @@ function tauriCall<T>(command: string, args: Record<string, unknown>): Promise<T
|
||||
|
||||
const DISMISSED_KEY = 'laputa_welcome_dismissed'
|
||||
|
||||
interface PersistedVaultList {
|
||||
vaults?: Array<{ label: string; path: string }>
|
||||
active_vault?: string | null
|
||||
hidden_defaults?: string[]
|
||||
}
|
||||
|
||||
function wasDismissed(): boolean {
|
||||
try {
|
||||
return localStorage.getItem(DISMISSED_KEY) === '1'
|
||||
@@ -31,6 +37,22 @@ function markDismissed(): void {
|
||||
}
|
||||
}
|
||||
|
||||
async function clearMissingActiveVault(missingPath: string): Promise<void> {
|
||||
try {
|
||||
const list = await tauriCall<PersistedVaultList>('load_vault_list', {})
|
||||
if (!list || list.active_vault !== missingPath) return
|
||||
await tauriCall('save_vault_list', {
|
||||
list: {
|
||||
vaults: list.vaults ?? [],
|
||||
active_vault: null,
|
||||
hidden_defaults: list.hidden_defaults ?? [],
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Best effort only — onboarding should still proceed
|
||||
}
|
||||
}
|
||||
|
||||
export function useOnboarding(initialVaultPath: string) {
|
||||
const [state, setState] = useState<OnboardingState>({ status: 'loading' })
|
||||
const [creating, setCreating] = useState(false)
|
||||
@@ -48,7 +70,16 @@ export function useOnboarding(initialVaultPath: string) {
|
||||
|
||||
if (exists) {
|
||||
setState({ status: 'ready', vaultPath: initialVaultPath })
|
||||
} else if (wasDismissed()) {
|
||||
} else {
|
||||
await clearMissingActiveVault(initialVaultPath)
|
||||
if (cancelled) return
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
return
|
||||
}
|
||||
|
||||
if (wasDismissed()) {
|
||||
// User previously dismissed — show vault-missing instead of welcome
|
||||
setState({ status: 'vault-missing', vaultPath: initialVaultPath, defaultPath })
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user