feat: exact-match-first ranking in search and wikilink autocomplete

Add searchRank/bestSearchRank utilities that compute a tier (0=exact,
1=prefix, 2=fuzzy-only). Both useNoteSearch and WikilinkChatInput now
sort by rank tier first, then by fuzzy score, ensuring notes with exact
title or alias matches always surface above partial/fuzzy matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-08 21:00:58 +01:00
parent dd7f0f978a
commit 4ce9167e09
5 changed files with 110 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useCallback, useEffect } from 'react'
import type { VaultEntry } from '../types'
import { fuzzyMatch } from '../utils/fuzzyMatch'
import { fuzzyMatch, bestSearchRank } from '../utils/fuzzyMatch'
import { getTypeColor, getTypeLightColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeIcon } from '../components/NoteItem'
import type { NoteSearchResultItem } from '../components/NoteSearchList'
@@ -45,9 +45,13 @@ export function useNoteSearch(entries: VaultEntry[], query: string, maxResults =
.map(mapResult)
}
return searchableEntries
.map((e) => ({ entry: e, ...fuzzyMatch(query, e.title) }))
.map((e) => ({
entry: e,
...fuzzyMatch(query, e.title),
rank: bestSearchRank(query, e.title, e.aliases),
}))
.filter((r) => r.match)
.sort((a, b) => b.score - a.score)
.sort((a, b) => a.rank - b.rank || b.score - a.score)
.slice(0, maxResults)
.map((r) => mapResult(r.entry))
}, [searchableEntries, query, maxResults, typeEntryMap])