feat: status property — Notion-style dropdown with color chips (#97)
* fix: resolve search panel freeze by making search_vault async The search_vault Tauri command was synchronous (fn, not async fn), blocking the main thread for 30+ seconds during hybrid/semantic search on large vaults (9200+ files). This caused the macOS beachball. Changes: - Make search_vault async with tokio::spawn_blocking (runs qmd off main thread) - Cache collection name per vault path (avoid repeated qmd collection list calls) - Cancel inflight searches and debounce timers when panel closes - Add regression test for stale result cancellation on panel close Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: add status property dropdown wireframes Three frames: closed pill state, open dropdown with suggested/vault statuses, and custom status creation flow. Also bump flaky NoteList 9000-entry test timeout from 15s to 30s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace status text edit with Notion-style dropdown - Extract STATUS_STYLES to shared statusStyles.ts utility - New StatusDropdown component: filterable popover with colored pills - StatusPill reusable component for consistent status chip rendering - Vault-wide status aggregation from entries prop - Dropdown shows "From vault" and "Suggested" sections - Custom status creation via type-and-Enter - Escape/backdrop click cancels without saving - Keyboard navigation (ArrowUp/Down + Enter) - 22 new tests covering dropdown behavior + integration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -368,4 +368,42 @@ describe('SearchPanel', () => {
|
||||
// Keyword results remain
|
||||
expect(screen.getByText('Keyword Only')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('cancels inflight searches when panel closes', async () => {
|
||||
const resolvers: ((v: unknown) => void)[] = []
|
||||
mockInvokeFn.mockImplementation(
|
||||
() => new Promise(resolve => { resolvers.push(resolve) }),
|
||||
)
|
||||
|
||||
const { rerender } = render(
|
||||
<SearchPanel open={true} vaultPath="/vault" entries={MOCK_ENTRIES} onSelectNote={vi.fn()} onClose={vi.fn()} />,
|
||||
)
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText('Search in all notes...'), { target: { value: 'slow query' } })
|
||||
|
||||
// Wait for keyword search to start
|
||||
await waitFor(() => {
|
||||
expect(resolvers).toHaveLength(1)
|
||||
})
|
||||
|
||||
// Close the panel while search is inflight
|
||||
rerender(
|
||||
<SearchPanel open={false} vaultPath="/vault" entries={MOCK_ENTRIES} onSelectNote={vi.fn()} onClose={vi.fn()} />,
|
||||
)
|
||||
|
||||
// Resolve the inflight keyword search — should be discarded (stale generation)
|
||||
resolvers[0]({
|
||||
results: [{ title: 'Stale Result', path: '/vault/essay/ai-apis.md', snippet: '', score: 0.9, note_type: null }],
|
||||
elapsed_ms: 30,
|
||||
})
|
||||
|
||||
// Reopen panel
|
||||
rerender(
|
||||
<SearchPanel open={true} vaultPath="/vault" entries={MOCK_ENTRIES} onSelectNote={vi.fn()} onClose={vi.fn()} />,
|
||||
)
|
||||
|
||||
// Should NOT show the stale result — panel was reset
|
||||
expect(screen.queryByText('Stale Result')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Search across all note contents')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user