fix: reorder welcome onboarding actions

This commit is contained in:
lucaronin
2026-04-20 11:04:29 +02:00
parent 9536efee0e
commit b90d2f8612
5 changed files with 48 additions and 39 deletions

View File

@@ -41,16 +41,18 @@ describe('WelcomeScreen', () => {
expect(brandIcon).toHaveAttribute('src', tolariaIcon)
})
it('shows all three option buttons', () => {
it('shows the onboarding actions in the guided-first order', () => {
render(<WelcomeScreen {...defaultProps} />)
expect(screen.getByTestId('welcome-create-new')).toHaveTextContent('Create empty vault')
expect(screen.getByTestId('welcome-open-folder')).toHaveTextContent('Open existing vault')
expect(screen.getByTestId('welcome-create-vault')).toHaveTextContent('Get started with a template')
const optionButtons = screen.getAllByRole('button')
expect(optionButtons[0]).toBe(screen.getByTestId('welcome-create-vault'))
expect(optionButtons[1]).toBe(screen.getByTestId('welcome-create-new'))
expect(optionButtons[2]).toBe(screen.getByTestId('welcome-open-folder'))
})
it('focuses the first action for keyboard users', () => {
render(<WelcomeScreen {...defaultProps} />)
expect(screen.getByTestId('welcome-create-new')).toHaveFocus()
expect(screen.getByTestId('welcome-create-vault')).toHaveFocus()
})
it('shows the simplified template option description', () => {
@@ -109,13 +111,13 @@ describe('WelcomeScreen', () => {
})
it('cycles onboarding actions with Tab and activates the selected action with Enter', () => {
const onOpenFolder = vi.fn()
render(<WelcomeScreen {...defaultProps} onOpenFolder={onOpenFolder} />)
const onCreateEmptyVault = vi.fn()
render(<WelcomeScreen {...defaultProps} onCreateEmptyVault={onCreateEmptyVault} />)
fireEvent.keyDown(window, { key: 'Tab' })
fireEvent.keyDown(window, { key: 'Enter' })
expect(onOpenFolder).toHaveBeenCalledOnce()
expect(onCreateEmptyVault).toHaveBeenCalledOnce()
})
it('disables all buttons while creating', () => {

View File

@@ -310,18 +310,18 @@ function useWelcomeActionButtons({
> & {
busy: boolean
}) {
const primaryActionRef = useRef<HTMLButtonElement>(null)
const openFolderActionRef = useRef<HTMLButtonElement>(null)
const templateActionRef = useRef<HTMLButtonElement>(null)
const createEmptyActionRef = useRef<HTMLButtonElement>(null)
const openFolderActionRef = useRef<HTMLButtonElement>(null)
const actionButtonRefs = useMemo(
() => [primaryActionRef, openFolderActionRef, templateActionRef],
() => [templateActionRef, createEmptyActionRef, openFolderActionRef],
[],
)
const actions = useMemo<WelcomeAction[]>(
() => ([
{ disabled: isOffline, run: onCreateVault },
{ disabled: false, run: onCreateEmptyVault },
{ disabled: false, run: onOpenFolder },
{ disabled: isOffline, run: onCreateVault },
]),
[isOffline, onCreateEmptyVault, onCreateVault, onOpenFolder],
)
@@ -330,7 +330,7 @@ function useWelcomeActionButtons({
if (busy) return
// WKWebView can ignore `autoFocus`; move focus explicitly so keyboard-only
// onboarding always starts on "Create empty vault".
// onboarding always starts on the guided template flow.
focusWelcomeAction(actionButtonRefs, 0)
}, [actionButtonRefs, busy, mode])
@@ -362,9 +362,9 @@ function useWelcomeActionButtons({
}, [actionButtonRefs, actions, busy])
return {
primaryActionRef,
openFolderActionRef,
templateActionRef,
createEmptyActionRef,
openFolderActionRef,
}
}
@@ -382,7 +382,7 @@ export function WelcomeScreen({
}: WelcomeScreenProps) {
const busy = creatingAction !== null
const presentation = getWelcomeScreenPresentation(mode, defaultVaultPath, isOffline)
const { primaryActionRef, openFolderActionRef, templateActionRef } = useWelcomeActionButtons({
const { templateActionRef, createEmptyActionRef, openFolderActionRef } = useWelcomeActionButtons({
mode,
busy,
isOffline,
@@ -417,6 +417,21 @@ export function WelcomeScreen({
<div style={DIVIDER_STYLE} />
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 10 }}>
<OptionButton
icon={<Rocket size={18} style={{ color: 'var(--accent-purple)' }} />}
iconBg="var(--accent-purple-light, #F3E8FF)"
label="Get started with a template"
description={presentation.templateDescription}
loadingLabel="Downloading template…"
loadingDescription="Cloning the Getting Started vault template"
onClick={onCreateVault}
disabled={busy || isOffline}
loading={creatingAction === 'template'}
testId="welcome-create-vault"
autoFocus
buttonRef={templateActionRef}
/>
<OptionButton
icon={<Plus size={18} style={{ color: 'var(--accent-blue)' }} />}
iconBg="var(--accent-blue-light, #EBF4FF)"
@@ -428,8 +443,7 @@ export function WelcomeScreen({
disabled={busy}
loading={creatingAction === 'empty'}
testId="welcome-create-new"
autoFocus
buttonRef={primaryActionRef}
buttonRef={createEmptyActionRef}
/>
<OptionButton
@@ -442,20 +456,6 @@ export function WelcomeScreen({
testId="welcome-open-folder"
buttonRef={openFolderActionRef}
/>
<OptionButton
icon={<Rocket size={18} style={{ color: 'var(--accent-purple)' }} />}
iconBg="var(--accent-purple-light, #F3E8FF)"
label="Get started with a template"
description={presentation.templateDescription}
loadingLabel="Downloading template…"
loadingDescription="Cloning the Getting Started vault template"
onClick={onCreateVault}
disabled={busy || isOffline}
loading={creatingAction === 'template'}
testId="welcome-create-vault"
buttonRef={templateActionRef}
/>
</div>
{creatingAction === 'template' && (

View File

@@ -181,8 +181,10 @@ test('keyboard onboarding can create an empty vault and the first note', async (
await expect(page.getByTestId('welcome-screen')).toBeVisible()
await expect(page.getByTestId('welcome-create-new')).toContainText('Create empty vault')
await expect(page.getByTestId('welcome-create-new')).toBeFocused()
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 })

View File

@@ -59,7 +59,7 @@ test('accepting telemetry consent on a fresh start opens the vault choice wizard
await expect(page.getByTestId('welcome-screen')).toBeVisible()
await expect(page.getByTestId('welcome-open-folder')).toBeVisible()
await expect(page.getByTestId('welcome-create-new')).toBeFocused()
await expect(page.getByTestId('welcome-create-vault')).toBeFocused()
})
test('telemetry consent still leaves the welcome wizard fully keyboard navigable @smoke', async ({ page }) => {
@@ -76,6 +76,9 @@ test('telemetry consent still leaves the welcome wizard fully keyboard navigable
await page.keyboard.press('Enter')
await expect(page.getByTestId('welcome-screen')).toBeVisible()
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('Tab')
@@ -92,10 +95,10 @@ test('telemetry consent still leaves the welcome wizard fully keyboard navigable
await expect(page.getByTestId('welcome-screen')).toBeVisible()
await page.keyboard.press('Tab')
await expect(page.getByTestId('welcome-create-vault')).toBeFocused()
await page.keyboard.press('Shift+Tab')
await expect(page.getByTestId('welcome-open-folder')).toBeFocused()
await expect(page.getByTestId('welcome-create-new')).toBeFocused()
await page.keyboard.press('Shift+Tab')
await expect(page.getByTestId('welcome-create-vault')).toBeFocused()
})
for (const action of ['accept', 'decline'] as const) {

View File

@@ -3,6 +3,7 @@ import { test, expect, type Page } from '@playwright/test'
async function installOnboardingMocks(page: Page, offline: boolean) {
await page.addInitScript((isOffline: boolean) => {
localStorage.clear()
let createdVaultPath: string | null = null
let ref: Record<string, unknown> | null = null
@@ -16,11 +17,12 @@ async function installOnboardingMocks(page: Page, offline: boolean) {
hidden_defaults: [],
})
ref.get_default_vault_path = () => '/Users/mock/Documents/Getting Started'
ref.check_vault_exists = () => false
ref.check_vault_exists = (args: { path?: string }) => args.path === createdVaultPath
ref.create_getting_started_vault = (args: { targetPath?: string | null }) => {
if (args.targetPath !== '/Users/mock/Documents/Getting Started') {
throw new Error(`Unexpected Getting Started target: ${args.targetPath}`)
}
createdVaultPath = args.targetPath
return args.targetPath
}
},
@@ -65,6 +67,6 @@ test('status bar keeps a Getting Started clone entry available after onboarding'
await expect(page.getByTestId('claude-onboarding-screen')).toBeVisible()
await page.getByTestId('claude-onboarding-continue').click()
await expect(page.locator('[data-testid="note-list-container"]')).toBeVisible()
await page.getByTitle('Switch vault').click()
await page.getByTestId('status-vault-trigger').click()
await expect(page.getByTestId('vault-menu-clone-getting-started')).toBeVisible()
})