Skip to content

fix: repair quick capture search and keyboard ux#144

Merged
EtanHey merged 3 commits intomainfrom
feat/fix-quick-capture-bugs
Mar 30, 2026
Merged

fix: repair quick capture search and keyboard ux#144
EtanHey merged 3 commits intomainfrom
feat/fix-quick-capture-bugs

Conversation

@EtanHey
Copy link
Copy Markdown
Owner

@EtanHey EtanHey commented Mar 30, 2026

Summary

  • wire Quick Capture search to query while typing in Search mode
  • preserve the capture confirmation flash on Return and keep Tab focused on mode toggling
  • remove extra panel chrome/padding and keep non-input controls out of the focus loop

Test plan

  • swift test --package-path brain-bar
  • swift test --package-path brain-bar --filter QuickCapturePanelTests
  • pytest currently fails outside this Swift change set in tests/test_enrichment_controller.py

Note

Fix quick capture search live updates and keyboard focus traversal

  • Adds QuickCaptureViewModel.handleInputChange to trigger search immediately on every keystroke while in search mode, replacing deferred submission.
  • Fixes applySelectedSearchResult to switch to capture mode before populating the input field, ensuring prior search state is cleared correctly.
  • Removes non-interactive UI elements (hint card, mode buttons, results list) from tab focus traversal via focusable(false), keeping keyboard shortcuts intact.
  • Changes the panel window style from titled to borderless in QuickCapturePanelController.
  • Adds onTextChange callback to QuickCaptureInputField so text changes propagate to the view model via the controlTextDidChange delegate.

Macroscope summarized 2cac340.

Summary by CodeRabbit

  • New Features

    • Live search now triggers automatically as you type input.
  • Style

    • Updated panel appearance to borderless window style.
    • Refined layout spacing and improved focus behavior.
  • Tests

    • Added unit tests for input handling and capture workflows.

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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

The changes introduce live-search functionality by adding handleInputChange(_:) to automatically submit searches as text is edited. The UI is refined with a borderless window style, focus restrictions on interactive elements using .focusable(false), and adjusted layout padding. Control flow in result selection is reordered for correct state transitions. Two new tests verify search-mode behavior and capture-mode storage.

Changes

Cohort / File(s) Summary
Live-Search Text Input & Focus Management
brain-bar/Sources/BrainBar/QuickCapturePanel.swift
Added handleInputChange(_:) callback wired from QuickCaptureInputField.controlTextDidChange, enabling live-search submission on text edits in search mode. Reordered result selection to switch to capture mode before updating inputText. Changed window styleMask to .borderless. Added .focusable(false) across multiple SwiftUI subviews and containers; reduced padding values for layout adjustments.
Input-Handling Test Coverage
brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift
Added two unit tests: one verifying handleInputChange executes live search in search mode and updates results/selection state; another validating handleInputReturn stores content and triggers confirmation feedback in capture mode.

Sequence Diagram

sequenceDiagram
    actor User
    participant QCInputField as QuickCaptureInputField
    participant QCViewModel as QuickCaptureViewModel
    participant DB as Database

    User->>QCInputField: Edits text
    QCInputField->>QCInputField: controlTextDidChange
    QCInputField->>QCViewModel: onTextChange callback
    QCViewModel->>QCViewModel: handleInputChange(text)
    QCViewModel->>QCViewModel: Update inputText
    alt In Search Mode
        QCViewModel->>QCViewModel: submitSearch()
        QCViewModel->>DB: Query with inputText
        DB-->>QCViewModel: Return results
        QCViewModel->>QCViewModel: Update results & selectedResultID
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit's ode to live searches so quick,
Text flows, searches dance, results click by click,
No borders to hide this capture so neat,
Focus stays focused, the UI's complete!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main changes: fixing quick capture search to work while typing and improving keyboard UX by managing focus and removing panel chrome.
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/fix-quick-capture-bugs

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
Owner Author

EtanHey commented Mar 30, 2026

@coderabbitai review

Copy link
Copy Markdown
Owner Author

EtanHey commented Mar 30, 2026

@codex review

Copy link
Copy Markdown
Owner Author

EtanHey commented Mar 30, 2026

@cursor @BugBot review

Copy link
Copy Markdown
Owner Author

EtanHey commented Mar 30, 2026

@greptileai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 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.

@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 30, 2026

The PR already exists and has been reviewed by other bots (Macroscope and CodeRabbit). Since I can't update the PR body (it's not agent-managed), let me create a final comprehensive summary of my review findings.

BugBot Review Complete ✅

I've completed a thorough bug review of PR #144 "fix: repair quick capture search and keyboard ux". Here's what I found:

Overall Assessment: APPROVED

The PR is safe to merge. All changes are well-implemented with good test coverage.

Key Findings:

✅ What Works Well:

  1. Live search implementation - Properly triggers on every keystroke in search mode with correct state management
  2. Bug fix - Fixed mode switch order (now switches mode before setting input text, preventing race conditions)
  3. Focus management - Added .focusable(false) to 8 UI elements, keeping Tab focused on mode toggle
  4. Visual polish - Borderless panel style and reduced padding for cleaner appearance
  5. Test coverage - 2 new comprehensive tests added

⚠️ One Performance Consideration (Non-Blocking):

  • Live search triggers on every keystroke without debouncing
  • Risk: Low for typical databases (FTS5 is fast, limited to 8 results)
  • Recommendation: Monitor in production, add 150ms debounce if users report lag
  • Verdict: Not a blocker, can be addressed in follow-up if needed

Minor Polish Items (Optional):

  1. Search doesn't trim whitespace like capture does
  2. Could add tests for empty input and rapid typing
  3. No test for database errors during live search

Documents Created:

  • BUGBOT_REVIEW_QUICK_CAPTURE_UX_FIX.md - Detailed technical review (442 lines)
  • BUGBOT_REVIEW_SUMMARY.md - Executive summary

Recommendation:

Merge now - Core functionality is solid and well-tested. Optional follow-up work can address performance optimization if needed.

Open in Web Open in Cursor 

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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.

Co-authored-by: Etan Heyman <EtanHey@users.noreply.github.com>
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@brain-bar/Sources/BrainBar/QuickCapturePanel.swift`:
- Around line 338-342: Remove the redundant direct binding write in
controlTextDidChange: instead of setting parent.text there, let
parent.onTextChange(_) drive updates so handleInputChange remains the single
path that updates inputText; keep the existing guard in handleInputChange to
avoid re-entrancy and ensure controlTextDidChange only calls
parent.onTextChange(textField.stringValue) (referencing controlTextDidChange,
parent.text, parent.onTextChange, handleInputChange, and inputText).
- Around line 150-157: The handler currently calls submitSearch() on every
keystroke (in handleInputChange), causing synchronous DB queries; add a debounce
by storing a cancellable Task (e.g., a property like searchTask) and when
handleInputChange(_:) runs cancel any existing task, then create a new Task that
awaits a short sleep (150–300ms) and then calls submitSearch() only if mode ==
.search and inputText still matches newValue; ensure you update inputText
immediately as you do now, cancel the previous Task before creating a new one,
and clean up/cancel searchTask when the view deinitializes or mode changes.
🪄 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: bb6abb08-78de-4416-ae05-46cf242e23b7

📥 Commits

Reviewing files that changed from the base of the PR and between 86d14df and 5fcd1de.

📒 Files selected for processing (2)
  • brain-bar/Sources/BrainBar/QuickCapturePanel.swift
  • brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift
📜 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.11)
  • GitHub Check: test (3.12)
🧰 Additional context used
🧠 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/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/QuickCapturePanel.swift
🔇 Additional comments (8)
brain-bar/Sources/BrainBar/QuickCapturePanel.swift (6)

273-283: LGTM!

The reordering correctly captures the row data before switching modes, preventing any state interference from the mode change clearing results. This ensures inputText is populated with the selected result's title.


408-413: LGTM!

The borderless style mask is appropriate for this floating utility panel and aligns with the PR objective to remove extra panel chrome.


539-550: LGTM!

Adding .focusable(false) to mode buttons and their container correctly excludes them from the keyboard focus loop, ensuring Tab remains dedicated to mode toggling as intended by the PR.


687-690: LGTM!

The .focusable(false) on the button style ensures keyboard navigation stays on the text field.


742-748: LGTM!

Marking result rows and the scroll view as non-focusable keeps keyboard interaction centered on the input field while allowing mouse interactions on results.


573-578: LGTM!

Padding adjustments align with the PR objective to refine the panel layout.

brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift (2)

70-92: LGTM!

This test comprehensively verifies the live search behavior: text update, results population, auto-selection of first result, and idle feedback state. Good coverage of the new handleInputChange functionality.


196-210: LGTM!

This test validates the capture-mode return behavior: successful storage, confirmation flash trigger, input clearing, and database persistence. Good end-to-end coverage of the handleInputReturn path.

Comment on lines +150 to +157
func handleInputChange(_ newValue: String) {
if inputText != newValue {
inputText = newValue
}

guard mode == .search else { return }
submitSearch()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider debouncing search queries to avoid excessive database calls.

Calling submitSearch() on every keystroke can cause performance issues during rapid typing, especially with larger databases. Each call performs a synchronous database query on the main thread.

Consider adding a small debounce (e.g., 150-300ms) using Task with cancellation:

♻️ Example debounce approach
+    private var searchTask: Task<Void, Never>?
+
     func handleInputChange(_ newValue: String) {
-        if inputText != newValue {
-            inputText = newValue
-        }
-
+        inputText = newValue
         guard mode == .search else { return }
-        submitSearch()
+        searchTask?.cancel()
+        searchTask = Task {
+            try? await Task.sleep(nanoseconds: 150_000_000)
+            guard !Task.isCancelled else { return }
+            submitSearch()
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brain-bar/Sources/BrainBar/QuickCapturePanel.swift` around lines 150 - 157,
The handler currently calls submitSearch() on every keystroke (in
handleInputChange), causing synchronous DB queries; add a debounce by storing a
cancellable Task (e.g., a property like searchTask) and when
handleInputChange(_:) runs cancel any existing task, then create a new Task that
awaits a short sleep (150–300ms) and then calls submitSearch() only if mode ==
.search and inputText still matches newValue; ensure you update inputText
immediately as you do now, cancel the previous Task before creating a new one,
and clean up/cancel searchTask when the view deinitializes or mode changes.

Comment on lines 338 to 342
func controlTextDidChange(_ notification: Notification) {
guard let textField = notification.object as? NSTextField else { return }
parent.text = textField.stringValue
parent.onTextChange(textField.stringValue)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Dual text update is redundant but functional.

Line 340 updates parent.text (the binding), and line 341 calls onTextChange which calls handleInputChange, which also updates inputText. The guard in handleInputChange prevents a double-write, but the design could be simplified by removing line 340 and letting handleInputChange be the sole path for updating inputText.

♻️ Optional simplification
         func controlTextDidChange(_ notification: Notification) {
             guard let textField = notification.object as? NSTextField else { return }
-            parent.text = textField.stringValue
             parent.onTextChange(textField.stringValue)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func controlTextDidChange(_ notification: Notification) {
guard let textField = notification.object as? NSTextField else { return }
parent.text = textField.stringValue
parent.onTextChange(textField.stringValue)
}
func controlTextDidChange(_ notification: Notification) {
guard let textField = notification.object as? NSTextField else { return }
parent.onTextChange(textField.stringValue)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brain-bar/Sources/BrainBar/QuickCapturePanel.swift` around lines 338 - 342,
Remove the redundant direct binding write in controlTextDidChange: instead of
setting parent.text there, let parent.onTextChange(_) drive updates so
handleInputChange remains the single path that updates inputText; keep the
existing guard in handleInputChange to avoid re-entrancy and ensure
controlTextDidChange only calls parent.onTextChange(textField.stringValue)
(referencing controlTextDidChange, parent.text, parent.onTextChange,
handleInputChange, and inputText).

Co-authored-by: Etan Heyman <EtanHey@users.noreply.github.com>
@EtanHey EtanHey merged commit a1ae03f into main Mar 30, 2026
3 of 6 checks passed
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.

2 participants