feat(search): hybrid pre-warm + command-bar debounce + preview_text overlay#320
Conversation
📝 WalkthroughWalkthroughThis PR refactors Quick Capture search to use debounced submission with injected, testable search functions, updates result mapping to use ChangesQuick Capture Search Debouncing and Result Mapping
Hybrid Search Warmup Implementation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@greptileai review |
|
@coderabbitai review |
|
@codex review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff57cf4a01
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| guard mode == .search else { | ||
| _pendingSearchTask?.cancel() | ||
| return | ||
| } | ||
| scheduleDebouncedSearch() | ||
| } |
There was a problem hiding this comment.
Run immediate search before applying stale selection
Switching handleInputChange to debounce-only search leaves prior results and selectedResultIndex in place while the new query is pending. In that window, pressing Return uses the old non-empty results path in submit() and copies/applies a stale row instead of searching the current input, so users can get unrelated content when they type and submit quickly. This is a behavioral regression from immediate-submit expectations; clear stale results/selection on input change or force Return to run a fresh search when a debounced search is pending.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in cc5d919. Query changes now invalidate stale rows before the debounced search completes; explicit Return then executes the current query because results are empty. Covered by testInputChangeClearsStaleResultsBeforeDebouncedSearchCompletes and full swift test passed locally.
ff57cf4 to
cc5d919
Compare
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit cc5d919. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc5d9194fd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let results = candidates.map { candidate in | ||
| [ | ||
| "chunk_id": candidate.id, | ||
| "content": candidate.previewText, |
There was a problem hiding this comment.
Preserve full chunk content for search-result copy actions
Mapping "content" to candidate.previewText makes the command-bar copy paths (copyResultToClipboard and applySelectedSearchResult) copy preview text instead of the stored chunk body, because rows are built from this field. Since preview_text is intentionally shortened/derived, users copying a long or summarized memory now get truncated/altered text rather than the original content, which is a correctness regression from the previous db.search payload.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in e84b12a. Candidate results fetch the full chunk content by id and expose it separately as full_content, while content remains the preview_text used by the overlay.
| } | ||
|
|
||
| let results = try db.search(query: trimmed, limit: limit) | ||
| let candidates = try db.searchCandidates(query: trimmed, limit: limit) |
There was a problem hiding this comment.
Keep robust fallback search paths in quick-capture queries
Replacing db.search with db.searchCandidates drops the fallback behavior that BrainDatabase.search provides (exact chunk-id matching, expanded query variants, and trigram FTS fallback), so quick-capture queries now lose matches that previously resolved through those paths. This is a retrieval-quality regression in the command bar for typoed/partial/chunk-id lookups even when relevant chunks exist.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in e84b12a. The controller keeps searchCandidates as the first path and appends deduped db.search fallback rows up to the requested limit, with test coverage for exact chunk-ID fallback.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/brainlayer/brainbar_hybrid_helper.py`:
- Around line 50-54: The warmup call to store.hybrid_search in warm()/warmup is
unguarded and can fail startup on transient SQLITE_BUSY; wrap the
store.hybrid_search(...) call in a bounded retry loop that catches
sqlite3.OperationalError (or inspect exception for "database is
locked"/SQLITE_BUSY), apply exponential backoff with jitter (e.g., max 5
attempts, short initial delay like 0.1s), and on repeated failure log a warning
and continue (do not re-raise) so serve_forever() still starts; reference the
store.hybrid_search invocation and the warm()/warmup function when adding the
retry/backoff and use your module logger (e.g., processLogger or logger) to
record retries and final failure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 96b31bb1-fcd7-4b95-8818-d789643d9a0f
📒 Files selected for processing (7)
brain-bar/Sources/BrainBar/QuickCaptureController.swiftbrain-bar/Sources/BrainBar/QuickCapturePanel.swiftbrain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swiftbrain-bar/Tests/BrainBarTests/QuickCaptureTests.swiftbrain-bar/Tests/BrainBarTests/StabilityFixTests.swiftsrc/brainlayer/brainbar_hybrid_helper.pytests/test_brainbar_hybrid_helper.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (3.13)
- GitHub Check: test (3.12)
- GitHub Check: test (3.11)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Flag risky DB or concurrency changes explicitly and do not hand-wave lock behavior
Enforce one-write-at-a-time concurrency constraint; reads are safe but brain_digest is write-heavy and must not run in parallel with other MCP work
Run pytest before claiming behavior changed safely; current test suite has 929 tests
**/*.py: Usepaths.py:get_db_path()for all database path resolution; all scripts and CLI must use this function rather than hardcoding paths
When performing bulk database operations: stop enrichment workers first, checkpoint WAL before and after, drop FTS triggers before bulk deletes, batch deletes in 5-10K chunks, and checkpoint every 3 batches
Files:
src/brainlayer/brainbar_hybrid_helper.pytests/test_brainbar_hybrid_helper.py
src/brainlayer/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/brainlayer/**/*.py: Use retry logic onSQLITE_BUSYerrors; each worker must use its own database connection to handle concurrency safely
Classification must preserveai_code,stack_trace, anduser_messageverbatim; skipnoiseentries entirely and summarizebuild_loganddir_listingentries (structure only)
Use AST-aware chunking via tree-sitter; never split stack traces; mask large tool output
For enrichment backend selection: use Groq as primary backend (cloud, configured in launchd plist), Gemini as fallback viaenrichment_controller.py, and Ollama as offline last-resort; allow override viaBRAINLAYER_ENRICH_BACKENDenv var
Configure enrichment rate viaBRAINLAYER_ENRICH_RATEenvironment variable (default 0.2 = 12 RPM)
Implement chunk lifecycle columns:superseded_by,aggregated_into,archived_aton chunks table; exclude lifecycle-managed chunks from default search; allowinclude_archived=Trueto show history
Implementbrain_supersedewith safety gate for personal data (journals, notes, health/finance); use soft-delete forbrain_archivewith timestamp
Addsupersedesparameter tobrain_storefor atomic store-and-replace operations
Run linting and formatting with:ruff check src/ && ruff format src/
Run tests withpytest
UsePRAGMA wal_checkpoint(FULL)before and after bulk database operations to prevent WAL bloat
Files:
src/brainlayer/brainbar_hybrid_helper.py
🧠 Learnings (2)
📚 Learning: 2026-03-18T00:12:08.774Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In Swift files under brain-bar/Sources/BrainBar, enforce that when a critical dependency like the database is nil due to startup ordering (socket before DB), any tool handler that accesses the database must throw an explicit error (e.g., ToolError.noDatabase) instead of returning a default/empty value. Do not allow silent defaults (e.g., guard let db else { return ... }). Flag patterns that silently return defaults when db is nil, as this masks startup timing issues. This guidance applies broadly to similar Swift files in the BrainBar module, not just this one location.
Applied to files:
brain-bar/Sources/BrainBar/QuickCaptureController.swiftbrain-bar/Sources/BrainBar/QuickCapturePanel.swift
📚 Learning: 2026-03-29T18:45:40.988Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 133
File: brain-bar/Sources/BrainBar/BrainDatabase.swift:0-0
Timestamp: 2026-03-29T18:45:40.988Z
Learning: In the BrainBar module’s Swift database layer (notably BrainDatabase.swift), ensure that the `search()` function’s `unreadOnly=true` path orders results by the delivery frontier cursor so the watermark `maxRowID` stays contiguous. Specifically, when `unreadOnly` is enabled, the query must include `ORDER BY c.rowid ASC` (e.g., via `let orderByClause = unreadOnly ? "c.rowid ASC" : "f.rank"`). Do not replace the unread-only ordering with relevance-based sorting (e.g., `f.rank`) unconditionally or for the unread-only path, as it can introduce gaps in the watermark and incorrectly mark unseen rows as delivered. Flag any future change to the `ORDER BY` clause in this function that makes relevance sorting apply to the unread-only case.
Applied to files:
brain-bar/Sources/BrainBar/QuickCaptureController.swiftbrain-bar/Sources/BrainBar/QuickCapturePanel.swift
🔇 Additional comments (20)
tests/test_brainbar_hybrid_helper.py (1)
7-37: LGTM!brain-bar/Sources/BrainBar/QuickCaptureController.swift (1)
63-79: LGTM!brain-bar/Sources/BrainBar/QuickCapturePanel.swift (8)
8-9: LGTM!
108-108: LGTM!Also applies to: 111-112, 119-136
138-143: LGTM!
209-209: LGTM!
262-274: LGTM!
329-329: LGTM!
372-381: LGTM!
383-397: LGTM!brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift (7)
83-114: LGTM!
116-147: LGTM!
149-186: LGTM!
428-429: LGTM!
462-463: LGTM!
677-678: LGTM!
703-704: LGTM!brain-bar/Tests/BrainBarTests/QuickCaptureTests.swift (2)
7-7: LGTM!Also applies to: 82-110
306-328: LGTM!brain-bar/Tests/BrainBarTests/StabilityFixTests.swift (1)
130-130: LGTM!
cc5d919 to
e84b12a
Compare
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e84b12a35b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| private func submitSearch(query: String? = nil) { | ||
| let query = query ?? inputText | ||
| do { | ||
| let searchResult = try QuickCaptureController.search( | ||
| db: db, | ||
| query: inputText, | ||
| limit: 8 | ||
| ) | ||
| let searchResult = try search(query, 8) |
There was a problem hiding this comment.
Cancel pending debounced search before immediate submit
When submitSearch is invoked explicitly (e.g., user presses Return while a debounce is pending), the previously scheduled _pendingSearchTask is not cancelled, so it still fires later and re-runs the same query. In that 250ms window, users can change selection, but the delayed rerun resets selectedResultIndex to 0, causing the next Return/copy action to apply the wrong row. Cancelling any pending search task at the start of immediate submitSearch avoids this stale follow-up overwrite.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in d8ce586. Explicit submit now cancels any pending debounce task before running search, while the debounced task path avoids self-cancel. Added testExplicitSubmitCancelsPendingDebouncedSearch.
| if results.count < limit { | ||
| let fallbackRows = try db.search(query: trimmed, limit: limit) |
There was a problem hiding this comment.
Preserve fallback search paths even when candidates hit limit
The new fallback call to db.search(...) only runs when results.count < limit, which means exact chunk-id/trigram/expanded-query recovery paths are skipped whenever searchCandidates already fills the limit. That creates a retrieval regression for queries where lexical candidates are plentiful but the exact-ID or fallback match is not in the top candidate set; previously db.search always evaluated those paths. Fresh evidence is the new if results.count < limit gate added here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in d8ce586. QuickCaptureController.search now always runs the db.search fallback and merges fallback ordering with candidate preview rows, so exact chunk-ID and other fallback paths are preserved even when searchCandidates fills the requested limit. Added testSearchKeepsExactChunkIDFallbackWhenCandidatesFillLimit.
e84b12a to
d8ce586
Compare
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
💡 Codex Review
brainlayer/brain-bar/Sources/BrainBar/QuickCapturePanel.swift
Lines 227 to 229 in d8ce586
When submit(forceCapture: true) is used from search mode (e.g., Command+Return), it calls submitCapture and returns without cancelling _pendingSearchTask. Because handleInputChange now schedules debounced searches, a pending task can still fire during a slower store, repopulating results for the old query and mutating selection/feedback after the user initiated capture. This creates stale post-capture search state and can cause the next Return/copy action to apply an unintended row.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Summary
Test Plan
Note
Medium Risk
Search ordering and copy semantics changed for the command bar; hybrid warmup adds concurrent SQLite access at startup with retry-only handling for lock errors.
Overview
Improves BrainBar quick-capture search UX and startup latency by changing what gets queried, when searches run, and what gets copied.
Command bar search no longer fires on every keystroke:
QuickCaptureViewModeldebounces input by 250ms (default), cancels pending tasks on dismiss/mode switch, and clears result rows immediately when the query changes so stale hits are not selectable. Explicit submit still runs search right away and cancels any pending debounced task. Search is injectable for tests.Search payload shape changes in
QuickCaptureController.search: it builds results fromsearchCandidates(preview_text–leaning FTS path) merged with legacydb.searchrows (deduped bychunk_id, with exact-ID fallback preserved). Displaycontentis the preview;full_contentholds the stored chunk text. Candidate rows enrich viagetChunk, which now also returnstags. The overlay list title stays short; rowcontentused for copy/Enter isfull_content, not the preview.Python hybrid helper warmup (
HybridSearchHelper.warm) now embeds a dummy query and runs a realhybrid_searchto prime the vector index, with short retries when SQLite reports database is locked.Tests cover debounce timing, coalescing, stale-result clearing, preview vs full copy, exact chunk ID fallback, and warmup/retry behavior.
Reviewed by Cursor Bugbot for commit d8ce586. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add hybrid search pre-warming, debounced command-bar search, and preview_text overlay
HybridSearchHelper.warmin brainbar_hybrid_helper.py now pre-runs a hybrid search on startup, with retry logic (two attempts) for transient SQLitedatabase is lockederrors.QuickCaptureViewModelin QuickCapturePanel.swift debounces live search by 250ms (configurable), coalescing rapid keystrokes and clearing stale results immediately on input change.content(preview text) andfull_contentfield; the command bar displays the preview while downstream copy/actions use full content.QuickCaptureController.searchin QuickCaptureController.swift merges candidate-based matches with fallback rows, deduplicates by chunk ID, preserves candidate order, and includestagsin all result maps.dismissanddeinitto prevent stale updates.Macroscope summarized d8ce586.
Summary by CodeRabbit
New Features
Improvements