Slice 3 of search-core. Search results now show WHY they matched: a ts_headline
body fragment with matched terms highlighted. The API returns U+E000/U+E001
sentinels around matches (NOT HTML); the client tokenises them into escaped
<mark> React elements, so untrusted email content can never inject markup (no
dangerouslySetInnerHTML). Subtle accent tint. Only when there's a free-text query
(browse unchanged); projection branched (no DB function in a ternary) for
unambiguous EF translation.
Verified: build + all 40 tests; ts_headline output shape in raw Postgres; and the
ranked+headline EF query proven to translate + execute against a live Postgres via
a throwaway test (removed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 2 of search-core: the generated SearchVector now applies setweight 'A' to
the subject and 'B' to the body, so ts_rank_cd (added last slice) ranks a subject
match above a body-only mention -- what users expect. Adds an EF migration that
drops/recreates the stored generated column + GIN index (Npgsql emits this
correctly and transactionally).
Verified: the migration DDL was dry-run against the live staging Postgres
(schema + real data) inside a rolled-back transaction -- applies cleanly; and the
weighted ranking (subject 1.0 vs body 0.4 for the same term) was confirmed
directly in Postgres. Build + all 40 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>