Supersedes #200.
## Summary
- **Session-grouped results**: FTS5 search returns one result per
session. The best-ranked matching message provides the snippet and
scroll target.
- **Relevance/Recency sort toggle**: two-button control above results.
Sort validated server-side before ORDER BY interpolation. Toggle visible
during loading and zero-results states.
- **Session name search**: matches `display_name` and `first_message`
via a UNION with LIKE/ILIKE. Name-only matches use `ordinal = -1`;
frontend navigates without scrolling.
- **is_system flag**: parsers tag system-injected user messages; search
excludes them via column check and prefix fallback.
- **Case-sensitive prefix matching**: `SystemPrefixSQL` uses `substr()`
instead of `LIKE` for consistent behavior across SQLite
(case-insensitive LIKE) and PostgreSQL.
- **PostgreSQL parity**: `pg serve` path updated with `DISTINCT ON`
grouping, `is_system` column, ILIKE name branch.
- **FTS5 dedup fix**: outer JOIN includes MATCH clause to prevent
segment-level row duplication.
- **Stable timestamp ordering**: `julianday()` in SQLite ORDER BY avoids
lexicographic misorderings from variable fractional-second precision.
## Test plan
- [ ] `make test` — Go unit tests pass
- [ ] `make test-postgres` — PG integration tests pass
- [ ] `cd frontend && npx vitest run` — frontend tests pass
- [ ] Manual: Cmd+K search, verify session-grouped results with
name/snippet/meta
- [ ] Manual: Relevance/Recency toggle reorders results
- [ ] Manual: search term only in session name, verify ordinal=-1
behavior
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Tom Maloney <tom@supermaloney.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Relates to #190.
Summary
ended_at DESC). Sort is validated server-side beforeORDER BYinterpolation. Toggle is visible during loading and zero-results states.project · Xh ago· 8-char short ID (click to copy). Consistent styling for both search results and recent-session rows.display_nameandfirst_messagein addition to message content, via aUNIONwith aLIKE/ILIKEsession-name branch. Name-only matches returnordinal = -1; the frontend navigates to the session without scrolling. Snippet correctly shows the matching field (display_name vs first_message).pg servepath updated withDISTINCT ON (session_id)grouping,is_systemcolumn added to the PG schema (SchemaVersion bumped to 2), ILIKE session-name branch in the UNION CTE, and an actionable error message when the schema needs migration (run 'agentsview pg push').JOIN messages_ftsincludesWHERE messages_fts MATCH ?to prevent segment-level row duplication.julianday()normalization in SQLite ORDER BY avoids lexicographic misorderings from variable RFC3339Nano fractional-second precision.Test plan
make test— all Go unit tests passmake test-postgres— PG integration tests pass (requires Docker)cd frontend && npx vitest run— all TypeScript/Svelte unit tests passmake dev+make frontend-dev, open Cmd+K, search for a 3+ char term, verify session-grouped results with name/snippet/meta layoutordinal=-1behavior (navigates to session without scrolling to a message)agentsview pg serveagainst a schema-v1 database, verify the error message tells you to runpg push🤖 Generated with Claude Code