Skip to content

feat: full-text search over past conversations - #618

Open
FelixWang47831 wants to merge 3 commits into
agegr:mainfrom
FelixWang47831:support-to-search-content-in-sessions
Open

feat: full-text search over past conversations#618
FelixWang47831 wants to merge 3 commits into
agegr:mainfrom
FelixWang47831:support-to-search-content-in-sessions

Conversation

@FelixWang47831

@FelixWang47831 FelixWang47831 commented Aug 26, 2026

Copy link
Copy Markdown

PR description

The sidebar can browse sessions per project, but finding a conversation by what
was said in it means grepping ~/.pi/agent/sessions by hand. A magnifier next to
New / Refresh (or Ctrl/Cmd+Shift+F) replaces the session list
with a debounced full-text search over the stored session JSONL files: results
are grouped per session with highlighted snippets, scopable to one project and
optionally including tool output, and clicking one opens that session at that
message, scrolls to the first keyword occurrence and highlights every occurrence
in it. Adds GET /api/sessions/search and ?from=<entryId> on the context
route; i18n for en/zh-CN/zh-TW; no new dependencies.

image
  • No indexlib/session-search.ts streams the files on every keystroke,
    takes its candidates from listAllSessions() (so project scoping matches the
    sidebar and no request can escape the sessions directory), and is bounded by a
    file cap, an 8s budget and a per-session hit cap that it reports back so the UI
    can say "narrow the search".
  • Jumps stay contiguous with the leaf — the chat only pages upward, so
    ?from= returns everything from the target entry through the leaf, and refuses
    the jump (unknown entry, other branch, too far back) instead of shipping an
    isolated window.
  • The highlight uses ranges, not markup<mark> would split text nodes
    React owns, so lib/text-highlight.ts uses the CSS Custom Highlight API
    (Chrome/Edge 105+, Safari 17.2+, Firefox 140+; elsewhere it degrades to the row
    flash), and lib/text-match.ts is new so client and server share one matcher.

45 tests added, tsc --noEmit and npm run lint clean, and the remaining
npm test failures also fail on an unmodified checkout (Windows CRLF and PATH
assertions).

The sidebar could only browse sessions per project; finding an old
conversation by what was said in it required grepping
~/.pi/agent/sessions by hand.

Add a search toggle (Ctrl/Cmd+Shift+F) that replaces the session list
with a debounced full-text search over stored session JSONL files, with
highlighted snippets, per-session hit counts, project scoping, and an
opt-in for tool output. Clicking a result opens that session through the
normal select path, so results from another project move the effective
cwd like any other session click.

lib/session-search.ts streams the files instead of indexing them: there
is no index to keep in sync, candidate sessions come from
listAllSessions() so project scoping matches the sidebar and no request
can name a file outside the sessions directory, and the scan is bounded
by a file cap, a wall-clock budget, and a per-session hit cap that are
all reported back through stats.

Signed-off-by: Felix Wang <fei.wang_3@nxp.com>
Assisted-by: Pi:claude-opus-5
Conversation search could only open a session at its newest page, so a hit
in a long history still had to be found by scrolling.

Clicking a snippet now opens the session at that message: it is scrolled
to the middle of the viewport and flashed once (the result header still
opens at the first hit). Snippets became their own buttons inside the
result row instead of one button around everything.

Reaching an old entry has to respect the chat's paging model: loaded
messages are a contiguous run ending at the leaf, and only upward paging
exists. So `?from=<entryId>` returns everything from that entry, plus a
short lead-in, through the leaf — a superset of the normal tail page —
and refuses the jump when the entry is unknown, on another branch, or
farther back than MAX_JUMP_ENTRIES, in which case the session still opens
on its newest page.

The client completes the jump over up to three renders, each waiting on
the state the previous one changed: fetch, then the render window
catching up, then scroll and flash. Tool results and collapsed process
messages have no row of their own, so lib/chat-jump.ts resolves a target
to the nearest earlier row through messageRefs.

Signed-off-by: Felix Wang <fei.wang_3@nxp.com>
Assisted-by: Pi:claude-opus-5
Jumping to a hit still left the reader scanning a long message for the
keyword it matched on.

The jumped-to message now highlights every occurrence of the query and the
chat scrolls to the first one instead of to the message. The highlight
outlives the row flash — it is what makes a long message readable — and is
dropped on the next jump or when the chat unmounts.

It is painted through the CSS Custom Highlight API rather than by wrapping
text in <mark>: the message DOM belongs to React (markdown, code blocks,
tool output), and splitting its text nodes would invalidate the node
references React diffs against. Ranges live outside the DOM, so a
re-render can only drop the highlight, never corrupt the content. Where
the API is missing (Firefox < 140) the row flash still works.

The ::highlight() rule is injected from lib/text-highlight.ts and kept
self-contained. The build CSS parser does not recognize the
pseudo-element, and highlight pseudo-elements resolve custom properties
through the highlight inheritance chain rather than from the originating
element — so a var(--theme-color) there can stay unresolved, and an
unresolved var drops just that declaration, which shows up as a keyword
that has the underline but no background. Literal colors plus an explicit
foreground keep a strong yellow legible in both themes, and an existing
style tag is rewritten rather than skipped so a tab that survives a rule
change stops painting the old colors.

Matching moved to lib/text-match.ts because lib/session-search.ts reaches
node:fs through session-reader and cannot be imported by the browser
bundle; the panel passes its own query, mode, and case sensitivity into
the jump target so the chat highlights exactly what the server matched.

Signed-off-by: Felix Wang <fei.wang_3@nxp.com>
Assisted-by: Pi:claude-opus-5
@FelixWang47831
FelixWang47831 marked this pull request as ready for review August 26, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant