Skip to content

fix: BrainBar stability — async store, dates, Enter key, popover#161

Merged
EtanHey merged 1 commit into
mainfrom
fix/brainbar-stability
Mar 31, 2026
Merged

fix: BrainBar stability — async store, dates, Enter key, popover#161
EtanHey merged 1 commit into
mainfrom
fix/brainbar-stability

Conversation

@EtanHey
Copy link
Copy Markdown
Owner

@EtanHey EtanHey commented Mar 31, 2026

Summary

Five QA fixes from user testing after overnight sprint PRs #155-#160:

  1. Store freezes UIstoreAsync() runs DB writes off main thread via DispatchQueue.global. QuickCapture submitCapture uses async path. Exposed _pendingStoreTask for test awaiting.
  2. Search results missing datesSearchQueryCandidate now carries date, project, importance. SQL extended. SearchViewModel passes metadata through to SearchResult.
  3. Enter goes to captureapplySelectedSearchResult() now copies to clipboard instead of switching modes. Auto-selects first result when none highlighted.
  4. Popover oversizedcontentSize reduced from 420×620 to 360×320.
  5. VoiceBar duplicate struct — Merged voicelayer PR feat: mock MCP harness for behavioral agent testing #109.

Test plan

  • swift test --package-path brain-bar — 212 tests, 0 failures
  • BRAINBAR_APP_DIR=/Applications/BrainBar.app bash brain-bar/build-app.sh — builds, signs
  • Async store tests pass (storeAsync returns chunk, content searchable)
  • Search candidate date/project/importance tests pass
  • Enter key copies result, stays in search mode
  • Existing capture tests updated to await async store
  • Manual: store note, verify no UI freeze
  • Manual: search, verify dates appear in results
  • Manual: Enter on search result copies to clipboard

🤖 Generated with Claude Code

Note

Fix BrainBar stability with async store, Enter key copy behavior, and compact popover

  • Adds BrainDatabase.storeAsync to write captured content off the main thread using withCheckedThrowingContinuation and DispatchQueue.global, replacing the synchronous capture path in QuickCaptureViewModel.submitCapture.
  • Changes Enter key behavior in search mode: pressing Enter with results but no selection now auto-selects the first result and copies its content to the clipboard, keeping the UI in search mode with transient visual feedback instead of switching to capture mode.
  • Extends SearchQueryCandidate with date, project, and importance fields, populated from the database result set and mapped through to SearchResult for display.
  • Reduces the status item popover frame from 420×620 to 360×320 in AppDelegate.configureStatusItem.
  • Behavioral Change: applySelectedSearchResult no longer switches to capture mode; it copies content to the clipboard and stays in search mode.
📊 Macroscope summarized 18faff5. 8 files reviewed, 4 issues evaluated, 0 issues filtered, 2 comments posted

🗂️ Filtered Issues

…er size

Five QA fixes from user testing after overnight sprint PRs #155-#160:

(1) Store freezes UI: Added storeAsync() — runs DB write on background
    DispatchQueue via withCheckedThrowingContinuation. QuickCapture
    submitCapture now uses async path, UI stays responsive.

(2) Search results missing dates: SearchQueryCandidate now includes
    date, project, importance fields. searchCandidates SQL extended to
    SELECT created_at, project, importance. SearchViewModel passes
    these through to SearchResult for display.

(3) Enter in search goes to capture: Changed applySelectedSearchResult()
    to copy result to clipboard instead of switching to capture mode.
    Also auto-selects first result when selectedResultIndex is nil.

(4) Popover oversized: Reduced contentSize from 420x620 to 360x320.

(5) VoiceBar duplicate struct: Merged voicelayer PR #109.

Also: Updated busy_timeout test to match PR #160's 30s value.

212 tests, 0 failures. Build and sign pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 31, 2026

Warning

Rate limit exceeded

@EtanHey has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 16 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4a3e43ef-433a-4e56-a8a7-07a00f6b1291

📥 Commits

Reviewing files that changed from the base of the PR and between 0fe5403 and 18faff5.

📒 Files selected for processing (8)
  • brain-bar/Sources/BrainBar/BrainBarApp.swift
  • brain-bar/Sources/BrainBar/BrainDatabase.swift
  • brain-bar/Sources/BrainBar/QuickCapturePanel.swift
  • brain-bar/Sources/BrainBar/SearchQueryActor.swift
  • brain-bar/Sources/BrainBar/SearchViewModel.swift
  • brain-bar/Tests/BrainBarTests/DatabaseTests.swift
  • brain-bar/Tests/BrainBarTests/QuickCapturePanelTests.swift
  • brain-bar/Tests/BrainBarTests/StabilityFixTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/brainbar-stability

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.

@@ -101,6 +101,8 @@ final class QuickCaptureViewModel: ObservableObject {
private let panelState: QuickCapturePanelState
private let clipboard: QuickCaptureClipboard
private var copyResetTask: Task<Void, Never>?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium BrainBar/QuickCapturePanel.swift:103

Assigning a new Task to _pendingStoreTask without cancelling the previous task causes duplicate entries when submitCapture is called rapidly. Both tasks run concurrently and each calls db.storeAsync, and they race to mutate inputText and feedback, producing unpredictable UI state. Consider cancelling the previous task with _pendingStoreTask?.cancel() before assigning the new one, and checking Task.isCancelled before mutating state.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file brain-bar/Sources/BrainBar/QuickCapturePanel.swift around line 103:

Assigning a new `Task` to `_pendingStoreTask` without cancelling the previous task causes duplicate entries when `submitCapture` is called rapidly. Both tasks run concurrently and each calls `db.storeAsync`, and they race to mutate `inputText` and `feedback`, producing unpredictable UI state. Consider cancelling the previous task with `_pendingStoreTask?.cancel()` before assigning the new one, and checking `Task.isCancelled` before mutating state.

Evidence trail:
brain-bar/Sources/BrainBar/QuickCapturePanel.swift lines 279-305 (REVIEWED_COMMIT): `submitCapture` function assigns `_pendingStoreTask = Task { @MainActor in` at line 287 without calling `_pendingStoreTask?.cancel()` first. The task calls `db.storeAsync()` at line 289, then mutates `inputText` at line 295 and `feedback` at line 297.

git_diff base=MERGE_BASE head=REVIEWED_COMMIT: Shows PR introduces `_pendingStoreTask` (lines 104-105) and the Task assignment (line 287) without cancellation, while the same PR adds proper `copyResetTask?.cancel()` pattern elsewhere (around line 350).

brain-bar/Sources/BrainBar/QuickCapturePanel.swift lines 253-258 (REVIEWED_COMMIT): `copyResultToClipboard` demonstrates the proper pattern: `copyResetTask?.cancel()` before `copyResetTask = Task { ... }` with `guard !Task.isCancelled else { return }` check.


final class PopoverSizeTests: XCTestCase {

func testStatusPopoverViewFrameIsCompact() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low BrainBarTests/StabilityFixTests.swift:152

testStatusPopoverViewFrameIsCompact creates a hardcoded NSRect with width 360 and height 320, then asserts both dimensions are ≤ 360 and ≤ 320. These assertions always pass because the values are identical, so the test cannot detect whether StatusPopoverView actually exceeds size constraints in production.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file brain-bar/Tests/BrainBarTests/StabilityFixTests.swift around line 152:

`testStatusPopoverViewFrameIsCompact` creates a hardcoded `NSRect` with width 360 and height 320, then asserts both dimensions are ≤ 360 and ≤ 320. These assertions always pass because the values are identical, so the test cannot detect whether `StatusPopoverView` actually exceeds size constraints in production.

Evidence trail:
brain-bar/Tests/BrainBarTests/StabilityFixTests.swift lines 151-157 at REVIEWED_COMMIT - shows the test creates `let frame = NSRect(x: 0, y: 0, width: 360, height: 320)` and then asserts `XCTAssertLessThanOrEqual(frame.width, 360)` and `XCTAssertLessThanOrEqual(frame.height, 320)`, which always pass since 360<=360 and 320<=320.

@EtanHey EtanHey merged commit 119395d into main Mar 31, 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.

1 participant