feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)

* feat: add search backend (qmd CLI) and design file

- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
  and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
  add mock in test setup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add SearchPanel UI with Cmd+Shift+F shortcut

- SearchPanel component: full-text search overlay with keyword/semantic
  mode toggle, debounced search (200ms), arrow key navigation, result
  count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: correct SearchPanel imports for Tauri/browser dual-mode

Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make test_detect_collection_fallback robust when qmd not installed

* fix: reset localStorage between App tests to prevent view mode state leak

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Rossi
2026-02-25 17:09:50 +01:00
committed by GitHub
parent b5f69fa58c
commit 2fe473ebbd
14 changed files with 890 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ import type { ViewMode } from './useViewMode'
interface KeyboardActions {
onQuickOpen: () => void
onCommandPalette: () => void
onSearch: () => void
onCreateNote: () => void
onSave: () => void
onOpenSettings: () => void
@@ -46,7 +47,7 @@ function handleCmdKey(e: KeyboardEvent, keyMap: Record<string, ShortcutHandler>)
}
export function useAppKeyboard({
onQuickOpen, onCommandPalette, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote,
onQuickOpen, onCommandPalette, onSearch, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote,
onSetViewMode, activeTabPathRef, handleCloseTabRef,
}: KeyboardActions) {
useEffect(() => {
@@ -68,11 +69,17 @@ export function useAppKeyboard({
}
const handleKeyDown = (e: KeyboardEvent) => {
// Cmd+Shift+F: full-text search (distinct from Cmd+F browser find)
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'f') {
e.preventDefault()
onSearch()
return
}
if (!handleViewModeKey(e, onSetViewMode)) {
handleCmdKey(e, cmdKeyMap)
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [onQuickOpen, onCommandPalette, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote, activeTabPathRef, handleCloseTabRef, onSetViewMode])
}, [onQuickOpen, onCommandPalette, onSearch, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote, activeTabPathRef, handleCloseTabRef, onSetViewMode])
}