Skip to content

feat(search): hybrid pre-warm + command-bar debounce + preview_text overlay#320

Merged
EtanHey merged 1 commit into
mainfrom
feat/search-perf-p0
May 24, 2026
Merged

feat(search): hybrid pre-warm + command-bar debounce + preview_text overlay#320
EtanHey merged 1 commit into
mainfrom
feat/search-perf-p0

Conversation

@EtanHey
Copy link
Copy Markdown
Owner

@EtanHey EtanHey commented May 24, 2026

Summary

  • Extend BrainBar hybrid helper warmup to run a dummy hybrid search after embedding warmup so the binary vector index is preloaded.
  • Add a 250ms debounce to command-bar search input while keeping explicit submit immediate.
  • Route command-bar overlay search through searchCandidates and preview_text instead of full content.

Test Plan

  • uv run pytest tests/test_brainbar_hybrid_helper.py -q
  • swift test --filter 'QuickCapturePanelTests/testHandleInputChangeDebouncesSearchByAtLeast200Milliseconds|QuickCapturePanelTests/testRapidInputChangesOnlyRunLatestDebouncedSearch|QuickCaptureTests/testSearchOverlayResultsUsePreviewTextNotFullContent'\n- uv run ruff check src/ tests/\n- uv run ruff format --check src/ tests/\n- uv run pytest tests/ -v --tb=short -m "not integration and not live" -x --ignore=tests/test_eval_framework.py --ignore=tests/test_follow_up_rewrite.py --ignore=tests/test_prompt_classification.py\n- uv run pytest tests/test_eval_framework.py tests/test_follow_up_rewrite.py tests/test_prompt_classification.py -v --tb=short -x\n- uv run pytest tests/test_think_recall_integration.py::TestMCPToolCount -v --tb=short\n\nNo squash merge; use rebase merge per Phase 2.4-A mandate.

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: QuickCaptureViewModel debounces 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 from searchCandidates (preview_text–leaning FTS path) merged with legacy db.search rows (deduped by chunk_id, with exact-ID fallback preserved). Display content is the preview; full_content holds the stored chunk text. Candidate rows enrich via getChunk, which now also returns tags. The overlay list title stays short; row content used for copy/Enter is full_content, not the preview.

Python hybrid helper warmup (HybridSearchHelper.warm) now embeds a dummy query and runs a real hybrid_search to 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.warm in brainbar_hybrid_helper.py now pre-runs a hybrid search on startup, with retry logic (two attempts) for transient SQLite database is locked errors.
  • QuickCaptureViewModel in QuickCapturePanel.swift debounces live search by 250ms (configurable), coalescing rapid keystrokes and clearing stale results immediately on input change.
  • Search results now carry a separate content (preview text) and full_content field; the command bar displays the preview while downstream copy/actions use full content.
  • QuickCaptureController.search in QuickCaptureController.swift merges candidate-based matches with fallback rows, deduplicates by chunk ID, preserves candidate order, and includes tags in all result maps.
  • Behavioral Change: explicit search submissions now cancel any pending debounced search by default; pending tasks are also cancelled on dismiss and deinit to prevent stale updates.

Macroscope summarized d8ce586.

Summary by CodeRabbit

  • New Features

    • Added debounced search with configurable delay—search executes after the user stops typing (250ms default).
    • Search results now display preview text instead of full content for better readability.
  • Improvements

    • Enhanced search task management with proper cancellation and cleanup.
    • Improved hybrid search warmup performance.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This PR refactors Quick Capture search to use debounced submission with injected, testable search functions, updates result mapping to use searchCandidates, adds task cancellation on mode/dismiss transitions, includes test coverage for debounce timing and preview-text display, and enhances hybrid search warmup to explicitly invoke hybrid_search.

Changes

Quick Capture Search Debouncing and Result Mapping

Layer / File(s) Summary
Search result mapping in QuickCaptureController
brain-bar/Sources/BrainBar/QuickCaptureController.swift
search method now calls db.searchCandidates(...) and transforms each candidate into a [String: Any] dictionary with explicit fields (chunk_id, content, created_at, project, importance, score).
Debounced search infrastructure and state management
brain-bar/Sources/BrainBar/QuickCapturePanel.swift (lines 8–9, 108–144, 209, 329)
QuickCaptureViewModel adds injectable QuickCaptureSearch closure, searchDebounceDelay timing parameter, and _pendingSearchTask state; initialization wires the injected search or defaults to QuickCaptureController.search; deinit, setMode, and dismiss all explicitly cancel any pending task.
Debounced input handling and search submission
brain-bar/Sources/BrainBar/QuickCapturePanel.swift (lines 262–274, 372–386)
handleInputChange clears stale results/selection and schedules a debounced search task instead of immediate submission; new scheduleDebouncedSearch() waits for the debounce interval; refactored submitSearch(query:) invokes the injected search closure with limit 8 and updates results/feedback.
Test coverage for debounce behavior and preview text
brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift (lines 83–185), brain-bar/Tests/BrainBarTests/QuickCaptureTests.swift (lines 7, 82–111, 305–328)
New async tests verify debounce timing (no immediate search call), coalescing of rapid keystrokes, and clearing of stale results on input change; new SQLite3-backed test verifies search results use stored preview_text instead of full content; helper function updatePreviewText(...) updates chunk preview text via direct SQLite C-API calls.
Updated test patterns to exercise new submit behavior
brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift (lines 428–429, 462–463, 677–678, 703–704), brain-bar/Tests/BrainBarTests/StabilityFixTests.swift (line 130)
Existing tests for dismiss-overlay, returning to search mode, copy-to-clipboard, and stability checks are updated to drive search state by setting model.inputText and calling model.submit() instead of handleInputChange(...).

Hybrid Search Warmup Implementation

Layer / File(s) Summary
Hybrid search warmup with explicit search call
src/brainlayer/brainbar_hybrid_helper.py, tests/test_brainbar_hybrid_helper.py
HybridSearchHelper.warm() now captures vector store and embedding model, embeds the warmup query string, and invokes store.hybrid_search(...) with query_embedding, query_text, and n_results=5; new unit test monkeypatches embedding and search functions to verify the exact sequence and payloads.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • EtanHey/brainlayer#144: Both PRs modify QuickCapturePanel.swift's QuickCaptureViewModel input/search flow.
  • EtanHey/brainlayer#149: Both PRs update QuickCaptureViewModel search/debounced submission behavior and result construction in overlapping code paths.
  • EtanHey/brainlayer#225: The main PR's HybridSearchHelper.warm() now calls store.hybrid_search(...), which is affected by that PR's SearchMixin.hybrid_search parameter changes.

Poem

🐰 A rabbit hops through search results with glee,

Debouncing keystrokes so quick and so free,

Preview text shines where the full text once lay,

While warmth fills the hybrid search—hip hip hooray! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: hybrid pre-warm, command-bar debounce, and preview_text overlay—all primary features delivered in this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/search-perf-p0

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented May 24, 2026

@greptileai review

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented May 24, 2026

@coderabbitai review

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented May 24, 2026

@codex review

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented May 24, 2026

@cursor @BugBot review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 24, 2026

You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Comment thread brain-bar/Sources/BrainBar/QuickCapturePanel.swift
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +265 to 270
guard mode == .search else {
_pendingSearchTask?.cancel()
return
}
scheduleDebouncedSearch()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@EtanHey EtanHey force-pushed the feat/search-perf-p0 branch from ff57cf4 to cc5d919 Compare May 24, 2026 22:06
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread brain-bar/Sources/BrainBar/QuickCaptureController.swift Outdated
Comment thread brain-bar/Sources/BrainBar/QuickCaptureController.swift
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 88e99e6 and cc5d919.

📒 Files selected for processing (7)
  • brain-bar/Sources/BrainBar/QuickCaptureController.swift
  • brain-bar/Sources/BrainBar/QuickCapturePanel.swift
  • brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift
  • brain-bar/Tests/BrainBarTests/QuickCaptureTests.swift
  • brain-bar/Tests/BrainBarTests/StabilityFixTests.swift
  • src/brainlayer/brainbar_hybrid_helper.py
  • tests/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: Use paths.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.py
  • tests/test_brainbar_hybrid_helper.py
src/brainlayer/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/brainlayer/**/*.py: Use retry logic on SQLITE_BUSY errors; each worker must use its own database connection to handle concurrency safely
Classification must preserve ai_code, stack_trace, and user_message verbatim; skip noise entries entirely and summarize build_log and dir_listing entries (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 via enrichment_controller.py, and Ollama as offline last-resort; allow override via BRAINLAYER_ENRICH_BACKEND env var
Configure enrichment rate via BRAINLAYER_ENRICH_RATE environment variable (default 0.2 = 12 RPM)
Implement chunk lifecycle columns: superseded_by, aggregated_into, archived_at on chunks table; exclude lifecycle-managed chunks from default search; allow include_archived=True to show history
Implement brain_supersede with safety gate for personal data (journals, notes, health/finance); use soft-delete for brain_archive with timestamp
Add supersedes parameter to brain_store for atomic store-and-replace operations
Run linting and formatting with: ruff check src/ && ruff format src/
Run tests with pytest
Use PRAGMA 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.swift
  • brain-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.swift
  • brain-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!

Comment thread src/brainlayer/brainbar_hybrid_helper.py Outdated
@EtanHey EtanHey force-pushed the feat/search-perf-p0 branch from cc5d919 to e84b12a Compare May 24, 2026 22:22
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Comment thread brain-bar/Sources/BrainBar/QuickCaptureController.swift
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +384 to +387
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d8ce586. Explicit submit now cancels any pending debounce task before running search, while the debounced task path avoids self-cancel. Added testExplicitSubmitCancelsPendingDebouncedSearch.

Comment on lines +71 to +72
if results.count < limit {
let fallbackRows = try db.search(query: trimmed, limit: limit)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@EtanHey EtanHey force-pushed the feat/search-perf-p0 branch from e84b12a to d8ce586 Compare May 24, 2026 22:37
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if forceCapture {
submitCapture(preserveMode: true)
return

P2 Badge Cancel debounced search before force-capture submit

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".

@EtanHey EtanHey merged commit f94b082 into main May 24, 2026
7 checks passed
@EtanHey EtanHey deleted the feat/search-perf-p0 branch May 24, 2026 22:49
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