feat: in-session search with Cmd+F, keyboard nav, and resync perf fix#157
feat: in-session search with Cmd+F, keyboard nav, and resync perf fix#157
Conversation
roborev: Combined Review (
|
|
This is a pretty solid review with some good feedback for me to iterate on. Let me work on some updates. |
roborev: Combined Review (
|
|
My latest commits that I've pushed should address most of the constructive feedback from roborev's initial review. The biggest change is that I implemented some backend changes as well, so that it leverages sqlite for the searching vs. bringing the session in memory as before. There are also some minor UI improvements on finding matches in collapsable blocks (tool inputs/outputs, thinking blocksnow automatically expand when they contain a search match) and highlighting formatted text. Specific items that were addressed:
Because these new updates added more scope, I added tests for the new BE endpoint and FE updates:
... and as I typed all this, saw roborevs review come back in. Will check that out too and see if anything needs adjusting. |
|
Last push to address some of those feedback items, and then I'll probably wait for others to chime in maybe. I appreciate these automated reviews and hope it eases the load here 🙂
Open to any feedback as usual! |
roborev: Combined Review (
|
|
Removing the vestigial markdown parsing logic after refactoring the search to happen on the backend instead of the original approach that was frontend only. I think this is at a good state with known limitations on edge cases that I think are lower priority. |
roborev: Combined Review (
|
|
Hey @wesm, I'm thinking this PR is in a good spot for a human review or discussion. What are your thoughts on adding a feature like this? |
|
I will review in the next couple of days! Treading water trying to keep up with the flow of PRs |
Press / or click the search icon in the breadcrumb to open a search bar that searches within the currently-displayed session. Matches are highlighted inline with prev/next navigation and a live match counter. Press Escape to close. Closes wesm#156 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix 2: preserve current match ordinal when messages reload or loadOlder fires. Only reset to first match when the query itself changes. Prevents background reloads from yanking the user back to the start of their search. - Fix 3: match against stripMarkdown(content) instead of raw markdown. Queries no longer spuriously match URLs inside [text](url), HTML attributes, or bare markdown syntax characters that are invisible in the rendered output. Adds stripMarkdown() to markdown.ts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract applyHighlight Svelte action to shared highlight.ts utility - Pass highlightQuery/isCurrentHighlight props to CodeBlock, ToolBlock, ThinkingBlock from MessageContent - CodeBlock: apply highlight to pre>code element - ThinkingBlock: apply highlight to content div; auto-expand when a match exists in the block - ToolBlock: apply highlight to all pre content areas (input, task prompt, fallback, output); auto-expand input/output sections when they contain a match Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add GET /api/v1/sessions/{id}/search?q=... which queries all messages
for the session using LIKE substring matching (consistent with browser
Cmd+F semantics), returning matching ordinals in document order.
The in-session search store now calls this endpoint instead of scanning
the in-memory message window, making find-in-session work across the
entire session regardless of how many messages are loaded. Navigation
uses ensureOrdinalLoaded() to fetch pages on demand when jumping to
matches outside the current viewport.
Changes:
- db.SearchSession(): LIKE query scoped to session_id, ordered by ordinal
- GET /api/v1/sessions/{id}/search handler
- api/client.ts: searchSession() function
- inSessionSearch store: debounced API fetch, proper abort on new query,
loading state, ensureOrdinalLoaded before scroll
- SessionFindBar: show '...' counter while loading, suppress 'No results'
during pending fetch
- Tests: TestSearchSession (db layer, 12 cases) and
TestHandleSearchSession (HTTP handler, 7 cases)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- stripMarkdown: 23 cases covering links, images, bold/italic, headings, blockquotes, inline/fenced code, HTML tags, mixed content - applyHighlight: 14 cases covering single/multiple matches, case-insensitivity, empty query, current-class toggling, nested elements, and update/clear lifecycle Closes wesm#156 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix 1 (streaming text node freeze): applyHighlight splits text nodes to
insert <mark> elements. When Svelte uses fine-grained text node updates
({content}), it retains the original node reference — which becomes
detached after splitting, causing visible content to freeze mid-stream.
Fix: add escapeHTML() to highlight.ts and switch all block components
(CodeBlock, ThinkingBlock, ToolBlock) to {@html escapeHTML(content)},
forcing Svelte to replace innerHTML on update rather than patch a
retained text node. applyHighlight.update() then re-marks the fresh DOM.
Fix 2 (stale matches on session switch): the store only refetched when
query changed, not when sessionId changed. Switching sessions with the
same query active left stale ordinals from the previous session in the
match list. Fix: track prevSessionId alongside prevQuery and trigger
re-fetch when either changes.
Add escapeHTML unit tests (7 cases) and a streaming-simulation test
that verifies applyHighlight survives an innerHTML reset.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The backend SearchSession query now LEFT JOINs tool_calls so that a match in result_content surfaces the parent message ordinal. DISTINCT collapses multiple tool calls on the same message into a single result. Previously a query that matched only inside tool output would return zero results from the API, even though the frontend would auto-expand and highlight it once the message was loaded. Added three new test cases: - match in tool result_content only (message content has no match) - tool result match scoped to correct session (no cross-session bleed) - message not double-counted when both content and result match Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
stripMarkdown was written for an in-memory matching approach that was replaced by the backend SQLite search endpoint. Removes the function and its 23 unit tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Refetch search results when messageCount changes so streamed content updates the match list while a query is active - Separate search-driven expansion from user-controlled collapse state in ThinkingBlock and ToolBlock so closing/changing the search query restores the original collapsed state - Fix $derived(() => ...) to $derived.by(() => ...) in SessionFindBar so the counter text reactively updates on next/previous navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LinkSubagentSessions performs a correlated subquery joining sessions to tool_calls on subagent_session_id. Without an index, this is O(sessions × tool_calls) — 327 seconds on a 15K session / 575K message dataset. Adding a partial index drops resync from 9+ minutes to ~37 seconds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Content-only refreshes (messageCount change) preserve the current match position instead of resetting to the first hit and scrolling away from the user's location - Click handlers toggle userCollapsed/userOutputCollapsed independently so user expand/collapse choices are recorded correctly even when search-driven expansion is active Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Intercept Cmd+F to open in-session find when a session is active, preventing the browser's native find from opening - Add Cmd+G / Cmd+Shift+G for next/prev match navigation while the find bar is open - Bump counter min-width from 52px to 72px to prevent layout shift when "No results" text is displayed - Update shortcuts modal to show Cmd+F alongside / Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- When preservePosition is active and the previously selected match ordinal no longer exists in refreshed results, fall back to scrolling to the first match instead of silently pointing at index 0 without scrolling - Gate Cmd+F behind ui.activeModal check so it does not suppress the browser's native find when a modal is open - Gate Cmd+G behind modal and input-focus checks, allowing it only from the find input itself or when no input is focused Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cc78891 to
f0401a8
Compare
Gate both shortcuts on router.route === "sessions" so they only fire when the session view is active. Also apply the isFindInput() exception to Cmd+F so it does not suppress native browser find from unrelated inputs like the sidebar typeahead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
- Clear matches immediately on query/session change during debounce - Force-show hidden block types when search query is active - Thread search props through ToolCallGroup to child ToolBlocks - Add userOverride pattern so manual collapse takes effect during search - Filter system messages and search tool input_json in session search Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Only reset userOverride when highlightQuery changes, not on content updates during streaming - Include input_json in ToolBlock expand check so backend matches in tool parameters are reachable - Set loading=true immediately when scheduling debounce to avoid transient "No results" flash during keystroke debounce window Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend search only covers fields the frontend renders and highlights (message content and tool-call result_content). Searching raw input_json produced matches that expanded the tool block but had no visible highlighted text, confusing users. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
roborev: Combined Review (
|
|
"Here's my assessment of each finding:
I think this is good enough to merge. |
Addresses #156. Adds find-in-session with backend search, inline
highlighting, and keyboard navigation. Also fixes a resync
performance regression found during testing.
Search
/key, or the breadcrumb search iconordinals
blocks with a current-match indicator
Cmd+G/Cmd+Shift+G globally while the find bar is open, Esc to
close
matches; restores user collapse state when search ends
(streaming), preserving the user's current match position
Resync perf fix
tool_calls.subagent_session_id—LinkSubagentSessionswas doing a full table scan per sessionduring resync, taking 327s on a 15K session / 575K message
dataset. Now completes in under 1s. Total resync dropped from
9+ minutes to ~37 seconds.
Test plan
/, verify find bar appearsagentsview sync --fulland verify resync completes inunder a minute