feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter (#158)
* feat: keyboard-first navigation — menu bar shortcuts, note list nav, inspector Tab/Enter - Add missing menu bar items: Archive Note (⌘E), Trash Note (⌘⌫), Find in Vault (⌘⇧F), Go Back (⌘[), Go Forward (⌘]) - Wire new menu events through useMenuEvents → useAppCommands - Note list: ↑↓ moves highlight, Enter opens selected note (useNoteListKeyboard hook) - Inspector properties: Tab between rows, Enter to start editing - Settings panel: auto-focus first input on open - Extract StatusDot, StateBadge, noteItemStyle from NoteItem (reduces complexity) - Extract dispatchActiveTabEvent/dispatchOptionalEvent from useMenuEvents - 21 new tests (useNoteListKeyboard + useMenuEvents for new events) - All 1275 frontend tests pass, 319 Rust tests pass - CodeScene quality gates: passed (NoteItem improved from 22→18 complexity) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: keyboard-first-nav wireframes (note list highlight, menu shortcuts, inspector tab nav) * test: add search.rs coverage for fallback branches (qmd_uri_to_vault_path, extract_clean_snippet) * chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup code) --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,11 @@ function makeHandlers(): MenuEventHandlers {
|
||||
onZoomIn: vi.fn(),
|
||||
onZoomOut: vi.fn(),
|
||||
onZoomReset: vi.fn(),
|
||||
onArchiveNote: vi.fn(),
|
||||
onTrashNote: vi.fn(),
|
||||
onSearch: vi.fn(),
|
||||
onGoBack: vi.fn(),
|
||||
onGoForward: vi.fn(),
|
||||
activeTabPathRef: { current: '/vault/test.md' } as React.MutableRefObject<string | null>,
|
||||
handleCloseTabRef: { current: vi.fn() } as React.MutableRefObject<(path: string) => void>,
|
||||
activeTabPath: '/vault/test.md',
|
||||
@@ -105,6 +110,50 @@ describe('dispatchMenuEvent', () => {
|
||||
expect(h.onZoomReset).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('note-archive triggers archive on active tab', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('note-archive', h)
|
||||
expect(h.onArchiveNote).toHaveBeenCalledWith('/vault/test.md')
|
||||
})
|
||||
|
||||
it('note-archive does nothing when no active tab', () => {
|
||||
const h = makeHandlers()
|
||||
h.activeTabPathRef = { current: null }
|
||||
dispatchMenuEvent('note-archive', h)
|
||||
expect(h.onArchiveNote).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('note-trash triggers trash on active tab', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('note-trash', h)
|
||||
expect(h.onTrashNote).toHaveBeenCalledWith('/vault/test.md')
|
||||
})
|
||||
|
||||
it('note-trash does nothing when no active tab', () => {
|
||||
const h = makeHandlers()
|
||||
h.activeTabPathRef = { current: null }
|
||||
dispatchMenuEvent('note-trash', h)
|
||||
expect(h.onTrashNote).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('edit-find-in-vault triggers search', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('edit-find-in-vault', h)
|
||||
expect(h.onSearch).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('view-go-back triggers go back', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('view-go-back', h)
|
||||
expect(h.onGoBack).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('view-go-forward triggers go forward', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('view-go-forward', h)
|
||||
expect(h.onGoForward).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('unknown event ID does nothing', () => {
|
||||
const h = makeHandlers()
|
||||
dispatchMenuEvent('unknown-event', h)
|
||||
|
||||
Reference in New Issue
Block a user