fix: quick-search surfaces connector captures + restore target flash#86
Merged
fix: quick-search surfaces connector captures + restore target flash#86
Conversation
The home dropdown ignored connector captures entirely, so searching a term that only matched a Reddit/HN/etc. post returned "No quick matches yet" even though pressing Enter would find it in the full view. The snippet generator was also case-sensitive on its window-placement heuristic — so typing "dark fantasy" never found the "Dark Fantasy Realms" hit and just sliced from position 0, hiding the match. Clicking a dropdown suggestion had dropped the message-id plumbing, and the 2-second amber flash on the target message (PR #30) had been removed incidentally in PR #48. Changes: - spool:search-preview IPC merges searchSessionPreview + searchCaptures; sessions still get priority, captures fill remaining slots so search behaviour for session-only users is unchanged - Renderer types propagate SearchResult[] end-to-end; home dropdown renders kind-specific rows (dot + title + snippet/source line), capture rows link out to the URL - Platform colors now come from the connector manifest via connectors.list() instead of a hardcoded map that silently fell through to accent for unknown plugins - buildLikeSnippet lowercases both sides for indexOf and uses a case- insensitive regex for <mark> wrapping, preserving original casing - handleSelectSuggestion accepts messageId so home-dropdown clicks land on the exact matched message - Restore showTargetHighlight state + 2s timer removed in PR #48; expose data-highlighted attribute for stable e2e assertions Tests: - 4 unit tests for buildLikeSnippet (case-insensitive window, case- preserving highlight, empty input, regex-metachar safety) - 4 e2e tests covering capture-only query, session snippet highlight, case-insensitive search, and click-to-flash — flash assertion keyed on data-highlighted attribute (not Tailwind class) to stay non-flaky - Captures seeded via sqlite3 CLI to avoid node/electron ABI mismatch
PR #86 only covered the home dropdown click path. Add matching coverage for the two other entry points that flow through handleOpenSession: - Clicking a row directly from the All view - Clicking a row after applying a source filter (Claude Code tab) Both paths share the same handler, but the UI surface differs; catching a regression in either one previously required manual testing.
macOS GitHub runner ships sqlite3 without FTS5, so the captures_fts INSERT trigger failed when the CLI tried to write a seeded row. Install a globalThis.__spoolSeedCapture hook in main/index.ts when SPOOL_E2E_TEST=1 is set (launchApp always sets it). The hook reuses the app's already-loaded better-sqlite3 (which bundles its own SQLite with FTS5 regardless of the OS) and the live db handle, so there's no ABI mismatch and no second connection. Dynamic import and process.mainModule.require were both tried first but neither is available in Playwright's evaluate sandbox — a named globalThis hook is the only approach that works with stock Electron.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Four bugs surfaced while QA'ing the provenance fix (PR #85) with a fresh Reddit sync:
Quick-search preview never searched captures. The home dropdown ran
searchSessionPreviewonly, so typing a term that only matched a connector item (e.g. a Reddit post title) returned "No quick matches yet" even though pressing Enter would find it in the All view. This was a pre-v0.1 oversight — the preview predates connectors and was never updated when captures were added.Session snippet was case-sensitive for its window position.
buildLikeSnippetlocated the first hit withnormalizedText.indexOf(term), but the upstream SQL usesLIKEwhich is ASCII case-insensitive. A lower-case query ("dark fantasy") would match messages containing "Dark Fantasy Realms", but the snippet builder couldn't find the hit position in the text and silently fell back toposition = 0. Long messages ended up showing unrelated prefix text with the actual match truncated off. Highlighting viasplit(term)had the same blind spot.Clicking a dropdown suggestion no longer scrolled to the matched message.
handleSelectSuggestiononly received the session UUID — themessageIdfrom the fragment result was thrown away, soSessionDetail's scroll-to-target effect never fired.The 2-second amber flash on the target message was gone. PR feat: scroll to matching message when opening session from search #30 added it; PR Add inline session find and session-to-search handoff #48 ("Make session detail searchable") incidentally removed the
showHighlightstate during the find-bar refactor and didn't put it back.Changes
Preview merges captures with fragments
spool:search-previewnow calls bothsearchSessionPreviewandsearchCaptureswhen no session filter is active. Fragments still get priority — captures only fill the remaining slots up to the limit — so users who never set up connectors see identical behavior to before. A source-scoped preview (claude/codex/gemini) stays sessions-only by intent.The renderer pipeline propagates
SearchResult[]end-to-end: preload return type,App.tsxstate, andHomeViewprops are all unified.HomeViewrenders kind-specific rows:<strong>highlight)platform · You saved this · authorBoth row types now use the same
SuggestionDotcomponent, centered on the first-line text height (h-5matches text-sm's 20px line-height) so the dot no longer appears vertically floating between the two lines.Platform colors come from the connector manifest
PLATFORM_BADGE_COLORSwas a hardcoded record inFragmentResults.tsx(and I initially duplicated it inHomeView.tsx— thanks for catching that). The connector SDK already requires every connector to declarecolorin its package.jsonspoolmetadata, andConnectorStatusalready ships it to the renderer. NowApp.tsxbuilds aplatform → colormap fromwindow.spool.connectors.list()and passes it down as a prop. Unknown platforms fall through to the accent color instead of a silent default.Case-insensitive snippet
buildLikeSnippetlowercases both the text and the terms beforeindexOfto locate the first hit, then uses a case-insensitive regex (new RegExp(escapeRegex(term), 'gi')) to wrap matches in<mark>while preserving the original casing in the displayed text. Regex metacharacters in terms are escaped defensively (e.g.v1.2.3now highlights correctly).Dropdown click plumbs messageId
handleSelectSuggestionnow accepts(uuid, messageId?)and forwards both tosetTargetMessageId, matching what the All view'shandleOpenSessionhas always done.Target-message flash restored
SessionDetailre-addsshowTargetHighlightstate with the 2ssetTimeout, using the sametransition-colors duration-700 bg-accent/10class from PR #30. The target message also gains stabledata-testid="target-message"anddata-highlighted="1"attributes so e2e tests don't have to pattern-match Tailwind class strings.Tests
Unit —
packages/core/src/db/search-query.test.ts4 new tests for
buildLikeSnippet:<mark>preserves original casing when query and text differ in case1.2.3) are escaped and match literallyE2E —
packages/app/e2e/home-preview.spec.ts4 new tests:
ZZQCAPTURE_ONLY_UNIQUE, seeded via sqlite3 CLI) surfaces adata-kind="capture"row in the dropdown<strong>with the matched termdata-testid="target-message"withdata-highlighted="1", which disappears within 5 seconds (the 2s fade timer plus 3s of slack)Non-flakiness notes
data-testid/data-*attributes, never on Tailwind class regexsqlite3CLI, avoiding the node/electron ABI mismatch that would happen if the test process tried to load the app'sbetter-sqlite3(which gets rebuilt for electron in thetest:e2escript)toHaveAttribute/not.toHaveAttributeuse Playwright's auto-retry, so the 2s timer doesn't need an explicit sleepfast-search.spec.tstests still passTest plan
pnpm --filter @spool/core test— 151 passed (was 147, +4 new)npx playwright test e2e/home-preview.spec.ts— 4/4 passed (5.2s total, flash test 2.9s)npx playwright test e2e/fast-search.spec.ts— 12/12 passed, no regression@spool/core,@spool/app,@spool/clidark fantasyquery; clicking session result jumps with amber flash