Files
amethyst/commons
Claude c9cd62a203 feat(search): run the search box's tokens as filters, locally and on relays
The token language shipped drawing chips that nothing acted on outside desktop's
relay path. `SearchBarViewModel` handed the raw box — chips and all — to both the
local scan and the NIP-50 `search` string, so `from:npub1… bitcoin` asked relays
for the literal text of its own tokens and matched nothing, and `since:`/`#t`
narrowed neither side.

Both paths now go through the one builder:

- `searchPostsByText` parses the text and builds its three kind-group REQs from
  `SearchFilterBuilder`, so `from:`/`to:` become `authors`/`#p`, dates become the
  window, and only the leftover terms travel as `search`.
- `LocalCache.filter` grows a predicate overload — the place for everything a wire
  Filter cannot say — and `CacheSearch.findNotesMatching` runs the same filters
  against the cache under it. Local and relay results stop disagreeing about what
  a query means.

The predicate carries the two things that are not filter fields: the NIP-50
`search`, via one `EventSearchMatcher` per filter reused across the scan, and
viewer policy — mute list, unsearchable kinds, encrypted content — which is the
reader's business and not a relay's.

Notably this means `FilterMatcher` never had to learn `search`, so the blast
radius the plan worried about (33 feed filters, FilterIndex, geode's MirrorWorker
all silently narrowing) never opens. Search opts in by composing a matcher; every
other caller is untouched. Plan step 7 is moot.

A full-text query still falls back to the old scan when the filter path finds
nothing, so no existing search gets worse while the two are compared in the wild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yKjw2WqwZpSzsqcYZMnkV
2026-09-08 00:28:56 +00:00
..