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

@@ -2,12 +2,13 @@ import { describe, it, expect, vi, afterEach } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useAppKeyboard } from './useAppKeyboard'
function fireKey(key: string, mods: { altKey?: boolean; metaKey?: boolean; ctrlKey?: boolean } = {}) {
function fireKey(key: string, mods: { altKey?: boolean; metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean } = {}) {
const event = new KeyboardEvent('keydown', {
key,
altKey: mods.altKey ?? false,
metaKey: mods.metaKey ?? false,
ctrlKey: mods.ctrlKey ?? false,
shiftKey: mods.shiftKey ?? false,
bubbles: true,
cancelable: true,
})
@@ -18,6 +19,7 @@ function makeActions() {
return {
onQuickOpen: vi.fn(),
onCommandPalette: vi.fn(),
onSearch: vi.fn(),
onCreateNote: vi.fn(),
onSave: vi.fn(),
onOpenSettings: vi.fn(),
@@ -94,4 +96,19 @@ describe('useAppKeyboard', () => {
fireKey('k', { metaKey: true })
expect(actions.onCommandPalette).toHaveBeenCalled()
})
it('Cmd+Shift+F triggers search', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('f', { metaKey: true, shiftKey: true })
expect(actions.onSearch).toHaveBeenCalled()
})
it('Cmd+Shift+F does not trigger other shortcuts', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
fireKey('f', { metaKey: true, shiftKey: true })
expect(actions.onQuickOpen).not.toHaveBeenCalled()
expect(actions.onCreateNote).not.toHaveBeenCalled()
})
})