fix: exclude trashed notes from search results and autocomplete

Trashed notes were appearing in search (Cmd+F), Quick Open (Ctrl+P),
wikilink autocomplete ([[), and person mention autocomplete (@).

Rust: add is_file_trashed() to check frontmatter, filter search_vault results.
Frontend: filter trashed entries from useNoteSearch, baseItems in both
editor views, and mock search_vault handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-09 12:42:56 +01:00
parent 46f289d229
commit 73060f3d00
9 changed files with 229 additions and 7 deletions

View File

@@ -52,7 +52,7 @@ export function RawEditorView({ content, path, entries, onContentChange, onSave,
const typeEntryMap = useMemo(() => buildTypeEntryMap(entries), [entries])
const baseItems = useMemo(
() => deduplicateByPath(entries.map(entry => ({
() => deduplicateByPath(entries.filter(e => !e.trashed).map(entry => ({
title: entry.title,
aliases: [...new Set([entry.filename.replace(/\.md$/, ''), ...entry.aliases])],
group: entry.isA || 'Note',

View File

@@ -62,7 +62,7 @@ export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange
const typeEntryMap = useMemo(() => buildTypeEntryMap(entries), [entries])
const baseItems = useMemo(
() => deduplicateByPath(entries.map(entry => ({
() => deduplicateByPath(entries.filter(e => !e.trashed).map(entry => ({
title: entry.title,
aliases: [...new Set([entry.filename.replace(/\.md$/, ''), ...entry.aliases])],
group: entry.isA || 'Note',

View File

@@ -220,6 +220,35 @@ describe('useNoteSearch', () => {
expect(result.current.results[0].title).toBe('Refactoring Notes')
})
it('excludes trashed notes from results', () => {
const withTrashed: VaultEntry[] = [
makeEntry({ path: '/vault/a.md', title: 'Active Note', modifiedAt: 1700000003 }),
makeEntry({ path: '/vault/t.md', title: 'Trashed Note', trashed: true, trashedAt: 1700000000, modifiedAt: 1700000002 }),
makeEntry({ path: '/vault/b.md', title: 'Another Active', modifiedAt: 1700000001 }),
]
const { result } = renderHook(() => useNoteSearch(withTrashed, ''))
expect(result.current.results.map(r => r.title)).toEqual(['Active Note', 'Another Active'])
})
it('excludes trashed notes from search results by query', () => {
const withTrashed: VaultEntry[] = [
makeEntry({ path: '/vault/a.md', title: 'Meeting Notes', modifiedAt: 1700000002 }),
makeEntry({ path: '/vault/t.md', title: 'Meeting Draft', trashed: true, modifiedAt: 1700000001 }),
]
const { result } = renderHook(() => useNoteSearch(withTrashed, 'Meeting'))
expect(result.current.results).toHaveLength(1)
expect(result.current.results[0].title).toBe('Meeting Notes')
})
it('does not exclude archived notes from results', () => {
const withArchived: VaultEntry[] = [
makeEntry({ path: '/vault/a.md', title: 'Active Note', modifiedAt: 1700000002 }),
makeEntry({ path: '/vault/ar.md', title: 'Archived Note', archived: true, modifiedAt: 1700000001 }),
]
const { result } = renderHook(() => useNoteSearch(withArchived, ''))
expect(result.current.results).toHaveLength(2)
})
it('resolves custom type color from Type entries', () => {
const withTypes: VaultEntry[] = [
makeEntry({ path: '/vault/t/recipe.md', title: 'Recipe', isA: 'Type', color: 'orange', icon: 'cooking-pot' }),

View File

@@ -32,7 +32,7 @@ export function useNoteSearch(entries: VaultEntry[], query: string, maxResults =
const typeEntryMap = useMemo(() => buildTypeEntryMap(entries), [entries])
const searchableEntries = useMemo(
() => entries.filter((e) => !SEARCH_EXCLUDED_TYPES.has(e.isA ?? '')),
() => entries.filter((e) => !e.trashed && !SEARCH_EXCLUDED_TYPES.has(e.isA ?? '')),
[entries],
)

View File

@@ -269,6 +269,7 @@ export const mockHandlers: Record<string, (args: any) => any> = {
if (!q) return { results: [], elapsed_ms: 0, query: q, mode: args.mode }
const matches = MOCK_ENTRIES
.filter(e => {
if (e.trashed) return false
const content = MOCK_CONTENT[e.path] ?? ''
return e.title.toLowerCase().includes(q) || content.toLowerCase().includes(q)
})