Replace library search with a BM25-ranked in-memory index
The search box now queries an inverted index built at startup over
three fields per recording — title, blurb, and the full plain text of
its editorial note (extracted from the parsed Markdown at load time,
so "Search titles and notes" is finally literally true). Ranking is
standard BM25 (k1=1.2, b=0.75) with per-field weights (title 3x, blurb
2x, body 1x), light plural folding and apostrophe folding instead of a
stemmer, all-terms-required semantics with a graceful partial-match
fallback so long queries never dead-end, and an exact-phrase bonus so
today's phrase searches still win.
Results order by relevance by default while a search is active — the
sort control gains a "Most relevant" default option — and any explicit
sort still wins. No dependencies, no JavaScript: the index is a few
maps over the embedded catalog and every query is a microsecond scan.
Tests cover tokenization, field weighting, phrase-bonus ordering,
partial-match fallback, body-only matches (q=Pringles reaches a note
body), plural folding (q=bear finds the Bears title), and
punctuation-only queries; the full-title search fixture now correctly
finds both parts of the two-part talk with the exact match ranked
first.