feat(#530): server-backed session search (FTS5) (#555)

* feat(#530): add SessionSearch models + searchSessions endpoint + contract tests

* feat(#530): wire server-backed search into Sessions screen (debounce + render)

* style(#530): ktlint import ordering on HermesApiService

* fix(#530): ktlint-clean overlap wrap + map search snippet to title

* fix(#530): map search snippet to title so results show + highlight

* fix(#530): render search hits as honest result cards instead of faking title

Backend SessionSearchResult carries no session name (only snippet + metadata),
so mapping snippet->title made the card show raw excerpt as the title. Instead,
render a dedicated SearchResultCard: 'Match' label + session id, highlighted
snippet as body, and source/model/played-at chips. Added SessionInfo.model and
a friendly relative-time formatter. ktlint clean, compileDebugKotlin + unit
tests pass.

* fix(#530): strip JSON from search snippet + tighten result card padding

* fix(#530): strip FTS highlight markers from search snippet on client

agy's backend change re-wraps matched terms in >>>term<<< markers inside the
clean snippet, but the mobile highlights by raw query term and never consumed
those markers — so literal '>>>'/'<<<' would render in result text. Strip the
markers before passing the snippet to highlightText so the query highlight
still works on clean text.

* fix(#530): integrate tree-flattening hierarchy with search modes in SessionsScreen

* fix(#530): fix test assertion and mock ApiClient in SessionsViewModelTest

* fix(#530): resolve compose PaddingValues compile error and fix slash-preservation test path

* fix(#530): clean search snippet from JSON payload and add unit test

---------

Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-14 12:47:59 +03:00
1 parent 71c1bdded9
commit c543eb6470
8 files changed
+914 -252

No files matched your search

+9
View File
@@ -482,6 +482,15 @@
<!-- Sessions Stats -->
<string name="sessions_search_placeholder">Search sessions…</string>
<string name="sessions_search_empty_title">No results</string>
<string name="sessions_search_empty_desc">No sessions match “%1$s”</string>
<string name="sessions_search_results_header">%1$d result(s) for “%2$s”</string>
<string name="sessions_search_played_at">Played %1$s</string>
<string name="sessions_search_match_label">Match</string>
<string name="sessions_search_model_label">Model</string>
<string name="sessions_search_unknown_model">Unknown model</string>
<string name="sessions_search_source_label">Source</string>
<string name="sessions_search_open_hint">Tap to open this conversation</string>
<string name="sessions_stat_total">Total</string>
<string name="sessions_stat_active">Active</string>
<string name="sessions_action_prune">Prune</string>