import { test, expect, type Page } from '@playwright/test' import { findCommand, openCommandPalette, sendShortcut } from './helpers' interface MockEntry { path: string filename: string title: string isA: string aliases: string[] belongsTo: string[] relatedTo: string[] status: string | null archived: boolean modifiedAt: number | null createdAt: number | null fileSize: number snippet: string wordCount: number relationships: Record outgoingLinks: string[] properties: Record template: null sort: null } interface VaultSeed { label: string path: string noteTitle?: string } interface EntryOptions { fileName?: string isA?: string snippet?: string } function untitledNoteRow(page: Page) { return page.getByText(/^Untitled Note(?: \d+)?$/i).first() } async function expectFreshVaultSeedEntries(page: Page) { await expect(page.getByText('AGENTS.md — Tolaria Vault', { exact: true })).toBeVisible() await expect(page.getByText('CLAUDE.md', { exact: true })).toBeVisible() await expect(page.getByText('Config', { exact: true })).toHaveCount(0) } async function installEmptyVaultMocks( page: Page, config: { createdVaultPath: string initialVaults: VaultSeed[] activeVault: string | null }, ) { await page.addInitScript((mockConfig) => { const gettingStartedPath = '/Users/mock/Documents/Getting Started' function buildEntry(vaultPath: string, title: string, options: EntryOptions = {}): MockEntry { const defaultStem = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') const filename = options.fileName ?? `${defaultStem}.md` return { path: `${vaultPath}/${filename}`, filename, title, isA: options.isA ?? 'Note', aliases: [], belongsTo: [], relatedTo: [], status: null, archived: false, modifiedAt: 1700000000, createdAt: null, fileSize: 256, snippet: options.snippet ?? `${title} snippet`, wordCount: 12, relationships: {}, outgoingLinks: [], properties: {}, template: null, sort: null, } } function buildFreshVaultSeedEntries(vaultPath: string): MockEntry[] { return [ buildEntry(vaultPath, 'AGENTS.md — Tolaria Vault', { fileName: 'AGENTS.md' }), buildEntry(vaultPath, 'CLAUDE.md', { fileName: 'CLAUDE.md' }), buildEntry(vaultPath, 'Type', { fileName: 'type.md', isA: 'Type' }), buildEntry(vaultPath, 'Note', { fileName: 'note.md', isA: 'Type' }), ] } function syncEntryContent(entry: MockEntry) { allContent[entry.path] = `# ${entry.title}\n\n${entry.snippet}` } let savedVaults = mockConfig.initialVaults.map(({ label, path }) => ({ label, path })) let activeVault = mockConfig.activeVault let hiddenDefaults: string[] = [] const entriesByVault = Object.fromEntries( mockConfig.initialVaults.map((vault) => [ vault.path, vault.noteTitle ? [buildEntry(vault.path, vault.noteTitle)] : [], ]), ) satisfies Record const allContent = Object.fromEntries( Object.values(entriesByVault) .flat() .map((entry) => [entry.path, `# ${entry.title}\n\n${entry.snippet}`]), ) localStorage.clear() localStorage.setItem('tolaria:claude-code-onboarding-dismissed', '1') Object.defineProperty(window, 'prompt', { configurable: true, value: () => mockConfig.createdVaultPath, }) let ref: Record | null = null Object.defineProperty(window, '__mockHandlers', { configurable: true, set(value) { ref = value as Record ref.load_vault_list = () => ({ vaults: [...savedVaults], active_vault: activeVault, hidden_defaults: [...hiddenDefaults], }) ref.save_vault_list = (args: { list: { vaults: Array<{ label: string; path: string }> active_vault: string | null hidden_defaults?: string[] } }) => { savedVaults = [...args.list.vaults] activeVault = args.list.active_vault hiddenDefaults = [...(args.list.hidden_defaults ?? [])] return null } ref.get_default_vault_path = () => gettingStartedPath ref.check_vault_exists = (args: { path?: string }) => savedVaults.some((vault) => vault.path === args.path) || args.path === mockConfig.createdVaultPath ref.create_empty_vault = (args: { targetPath?: string | null }) => { if (args.targetPath !== mockConfig.createdVaultPath) { throw new Error(`Unexpected empty vault target: ${args.targetPath}`) } entriesByVault[mockConfig.createdVaultPath] = buildFreshVaultSeedEntries(mockConfig.createdVaultPath) entriesByVault[mockConfig.createdVaultPath].forEach(syncEntryContent) return mockConfig.createdVaultPath } ref.list_vault = (args: { path?: string }) => entriesByVault[args.path ?? activeVault ?? ''] ?? [] ref.list_vault_folders = () => [] ref.list_views = () => [] ref.get_all_content = () => allContent ref.get_note_content = (args: { path?: string }) => allContent[args.path ?? ''] ?? '' ref.get_modified_files = () => [] ref.get_file_history = () => [] }, get() { return ref }, }) }, config) } test('keyboard onboarding can create an empty vault and the first note', async ({ page }) => { await installEmptyVaultMocks(page, { createdVaultPath: '/Users/mock/Documents/Fresh Vault', initialVaults: [], activeVault: null, }) await page.goto('/', { waitUntil: 'domcontentloaded' }) await expect(page.getByTestId('welcome-screen')).toBeVisible() await expect(page.getByTestId('welcome-create-new')).toContainText('Create empty vault') await expect(page.getByTestId('welcome-create-vault')).toBeFocused() await page.keyboard.press('Tab') await expect(page.getByTestId('welcome-create-new')).toBeFocused() await page.keyboard.press('Enter') await expect(page.getByTestId('note-list-container')).toBeVisible({ timeout: 5_000 }) await expectFreshVaultSeedEntries(page) await sendShortcut(page, 'n', ['Control']) await expect(untitledNoteRow(page)).toBeVisible({ timeout: 5_000 }) }) test('command palette and bottom bar expose empty-vault creation from the active app shell', async ({ page }) => { await installEmptyVaultMocks(page, { createdVaultPath: '/Users/mock/Documents/Client Vault', initialVaults: [{ label: 'Work Vault', path: '/Users/mock/Work', noteTitle: 'Work Home' }], activeVault: '/Users/mock/Work', }) await page.goto('/', { waitUntil: 'domcontentloaded' }) await expect(page.getByTestId('note-list-container')).toBeVisible({ timeout: 5_000 }) await expect(page.getByTestId('status-vault-trigger')).toContainText('Work Vault') await openCommandPalette(page) expect(await findCommand(page, 'Create Empty Vault')).toBe(true) await page.keyboard.press('Escape') const trigger = page.getByTestId('status-vault-trigger') await trigger.focus() await expect(trigger).toBeFocused() await page.keyboard.press('Enter') const createEmptyItem = page.getByTestId('vault-menu-create-empty') await createEmptyItem.focus() await expect(createEmptyItem).toBeFocused() await page.keyboard.press('Enter') await expect(trigger).toContainText('Client Vault') await expectFreshVaultSeedEntries(page) await sendShortcut(page, 'n', ['Control']) await expect(untitledNoteRow(page)).toBeVisible({ timeout: 5_000 }) })