-
Notifications
You must be signed in to change notification settings - Fork 7
fix: route BrainBar search through Python hybrid helper #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a110829
e0323c7
c232dc2
f8e2b8e
aa5448d
fee3ae3
f3d4af5
942d195
b65df04
82d02ae
740e90c
d984f06
3d8ec78
ea9064f
0564f88
34f5bc6
1f1e3e5
6c66db2
5768b4b
ecdafd5
4b2b4ce
ca64399
28343e6
17750c2
6348695
183da6a
fed97bb
6e29ad9
7da8ae8
7286baf
095bc5b
47d829a
931fa93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,26 +7,51 @@ enum BrainBarAppSupport { | |
| static func hotkeyPermissionFailureMessage(permissions: HotkeyPermissionStatus) -> String { | ||
| "BrainBar could not start the fallback hotkey listener. Enable \(permissions.missingPermissionsMessage) in System Settings. The CGEventTap fallback requires both Input Monitoring and Accessibility." | ||
| } | ||
|
|
||
| @MainActor | ||
| static func makeStatsCollector( | ||
| dbPath: String, | ||
| targetPID: pid_t, | ||
| brainBusEvents: BrainBusEventSource? = BrainBusClient(), | ||
| databaseOpenConfiguration: BrainDatabase.OpenConfiguration = BrainDatabase.OpenConfiguration() | ||
| ) -> StatsCollector { | ||
| StatsCollector( | ||
| dbPath: dbPath, | ||
| daemonMonitor: DaemonHealthMonitor(targetPID: targetPID), | ||
| brainBusEvents: brainBusEvents, | ||
| databaseOpenConfiguration: databaseOpenConfiguration | ||
| ) | ||
| } | ||
|
|
||
| @MainActor | ||
| static func makeUIStatsCollector( | ||
| dbPath: String, | ||
| brainBusEvents: BrainBusEventSource? = BrainBusClient() | ||
| ) -> StatsCollector { | ||
| makeStatsCollector( | ||
| dbPath: dbPath, | ||
| targetPID: 0, | ||
| brainBusEvents: brainBusEvents, | ||
| databaseOpenConfiguration: BrainDatabase.OpenConfiguration(readOnly: true) | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| @MainActor | ||
| final class AppDelegate: NSObject, NSApplicationDelegate { | ||
| let runtime = BrainBarRuntime() | ||
| private static let menuBarWindowAutosaveKey = "NSWindow Frame BrainBarMenuBarExtraWindow" | ||
|
|
||
| private var server: BrainBarServer? | ||
| private var statusPopoverController: BrainBarStatusPopoverController? | ||
| private var legacyStatusItem: NSStatusItem? | ||
| private var legacyPopover: NSPopover? | ||
| private var collector: StatsCollector? | ||
| private var injectionStore: InjectionStore? | ||
| private var quickCapturePanel: QuickCapturePanelController? | ||
| private var searchPanel: SearchPanelController? | ||
| private var dashboardPanel: BrainBarDashboardPanelController? | ||
| private var quickCaptureHotkey: HotkeyManager? | ||
| private weak var menuBarExtraWindow: NSWindow? | ||
| private weak var discoveredMenuBarWindow: NSWindow? | ||
| private var cancellables: Set<AnyCancellable> = [] | ||
| private var sharedDatabase: BrainDatabase? | ||
| private var pendingBrainBarURLs: [URL] = [] | ||
| private var hotkeyFileWatcher: DispatchSourceFileSystemObject? | ||
| private var menuBarWindowObservers: [NSObjectProtocol] = [] | ||
|
|
@@ -47,11 +72,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
|
|
||
| startHotkeyFileWatcher() | ||
|
|
||
| if launchMode == .menuBarWindow { | ||
| UserDefaults.standard.removeObject(forKey: Self.menuBarWindowAutosaveKey) | ||
| dashboardPanel = BrainBarDashboardPanelController(runtime: runtime) | ||
| } | ||
|
|
||
| let runningInstances = NSRunningApplication.runningApplications( | ||
| withBundleIdentifier: Bundle.main.bundleIdentifier ?? "com.brainlayer.BrainBar" | ||
| ) | ||
|
|
@@ -69,47 +89,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| self?.configureQuickCaptureHotkey() | ||
| } | ||
|
|
||
| if launchMode == .legacyStatusItem { | ||
| if launchMode == .menuBarWindow { | ||
| statusPopoverController = BrainBarStatusPopoverController(runtime: runtime) | ||
| } else if launchMode == .legacyStatusItem { | ||
| createLegacyStatusItem() | ||
| } | ||
|
|
||
| let dbPath = BrainBarServer.defaultDBPath() | ||
| NSLog("[BrainBar] Starting server before database readiness at %@", dbPath) | ||
| let server = BrainBarServer(dbPath: dbPath) | ||
| server.onStartRejected = { reason in | ||
| NSLog("[BrainBar] Startup rejected: %@", reason) | ||
| Task { @MainActor in | ||
| NSApp.terminate(nil) | ||
| } | ||
| } | ||
| server.onDatabaseReady = { [weak self] database in | ||
| Task { @MainActor in | ||
| guard let self else { return } | ||
| self.sharedDatabase = database | ||
| self.configureQuickCapture(database: database) | ||
| self.runtime.install( | ||
| collector: self.collector ?? StatsCollector( | ||
| dbPath: dbPath, | ||
| daemonMonitor: DaemonHealthMonitor(targetPID: ProcessInfo.processInfo.processIdentifier) | ||
| ), | ||
| injectionStore: self.injectionStore, | ||
| database: database | ||
| ) | ||
| self.flushPendingBrainBarURLs() | ||
| } | ||
| } | ||
|
|
||
| let collector = StatsCollector( | ||
| NSLog("[BrainBar] Starting UI shell; daemon owns %@", BrainBarServer.defaultSocketPath()) | ||
| let collector = BrainBarAppSupport.makeUIStatsCollector( | ||
| dbPath: dbPath, | ||
| daemonMonitor: DaemonHealthMonitor(targetPID: ProcessInfo.processInfo.processIdentifier) | ||
| brainBusEvents: BrainBusClient() | ||
| ) | ||
| let injectionStore = try? InjectionStore(databasePath: dbPath) | ||
|
|
||
| self.server = server | ||
| self.collector = collector | ||
| self.injectionStore = injectionStore | ||
| runtime.install( | ||
| collector: collector, | ||
| injectionStore: nil, | ||
| database: nil | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When BrainBar is launched in the normal menu-bar UI mode, quick search/capture now only calls Useful? React with 👍 / 👎. |
||
| ) | ||
|
Comment on lines
+105
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the UI shell starts in the default menu-bar mode, this now installs Useful? React with 👍 / 👎. |
||
|
|
||
| server.start() | ||
| flushPendingBrainBarURLs() | ||
|
|
||
| if launchMode == .legacyStatusItem { | ||
| installLegacyMenuBarSurface(with: collector) | ||
|
|
@@ -123,13 +122,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| func applicationWillTerminate(_ notification: Notification) { | ||
| menuBarWindowObservers.forEach(NotificationCenter.default.removeObserver) | ||
| menuBarWindowObservers.removeAll() | ||
| statusPopoverController?.stop() | ||
| statusPopoverController = nil | ||
| menuBarWindowSyncTask?.cancel() | ||
| menuBarWindowSyncTask = nil | ||
| hotkeyFileWatcher?.cancel() | ||
| quickCaptureHotkey?.stop() | ||
| collector?.stop() | ||
| injectionStore?.stop() | ||
| server?.stop() | ||
| } | ||
|
|
||
| func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { | ||
|
|
@@ -147,17 +146,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| } | ||
|
|
||
| runtime.presentQuickAction(.search) | ||
| showMenuBarWindow(nil) | ||
| statusPopoverController?.show(nil) | ||
| } | ||
|
|
||
| func showQuickCapturePanel() { | ||
| guard launchMode == .menuBarWindow else { | ||
| quickCapturePanel?.toggle() | ||
| NSLog("[BrainBar] Quick capture requires the menu-bar command surface in UI-split mode") | ||
| return | ||
| } | ||
|
|
||
| runtime.presentQuickAction(.capture) | ||
| showMenuBarWindow(nil) | ||
| statusPopoverController?.show(nil) | ||
| } | ||
|
|
||
| private func configureRuntimeCallbacks() { | ||
|
|
@@ -236,6 +235,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| return | ||
| } | ||
|
|
||
| if launchMode == .menuBarWindow, let statusPopoverController { | ||
| statusPopoverController.toggle(sender) | ||
| return | ||
| } | ||
|
|
||
| if let dashboardPanel { | ||
| dashboardPanel.toggle() | ||
| return | ||
|
|
@@ -284,10 +288,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| return | ||
| } | ||
|
|
||
| if let statusPopoverController { | ||
| statusPopoverController.show(nil) | ||
| return | ||
| } | ||
|
|
||
| dashboardPanel?.show() | ||
| } | ||
|
|
||
| private func showMenuBarWindow(_ sender: Any?) { | ||
| if launchMode == .menuBarWindow, let statusPopoverController { | ||
| statusPopoverController.show(sender) | ||
| return | ||
| } | ||
|
|
||
| if launchMode == .menuBarWindow, let dashboardPanel { | ||
| dashboardPanel.show() | ||
| return | ||
|
|
@@ -797,16 +811,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| } | ||
| } | ||
|
|
||
| private func configureQuickCapture(database: BrainDatabase) { | ||
| guard launchMode == .legacyStatusItem else { return } | ||
| guard quickCapturePanel == nil else { return } | ||
| quickCapturePanel = QuickCapturePanelController(db: database) | ||
| if searchPanel == nil { | ||
| searchPanel = SearchPanelController(db: database) | ||
| } | ||
| flushPendingBrainBarURLs() | ||
| } | ||
|
|
||
| private func ingestBrainBarURLs(_ urls: [URL]) { | ||
| for url in urls { | ||
| guard BrainBarURLAction.parse(url: url) != nil else { continue } | ||
|
|
@@ -827,18 +831,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| } | ||
| } | ||
|
|
||
| /// URL actions are dispatched once the backing surface is ready. In | ||
| /// menuBarWindow mode the command bar is driven by `runtime.database` + | ||
| /// the MenuBarExtra window, so readiness means the database has been | ||
| /// installed into the runtime. In legacy mode the floating panel still | ||
| /// drives routing. | ||
| private func isReadyToHandleBrainBarURL() -> Bool { | ||
| switch launchMode { | ||
| case .menuBarWindow: | ||
| return runtime.database != nil | ||
| case .legacyStatusItem: | ||
| return quickCapturePanel != nil | ||
| } | ||
| true | ||
| } | ||
|
|
||
| private func handleBrainBarURL(_ url: URL) { | ||
|
|
@@ -859,26 +853,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { | |
| @main | ||
| struct BrainBarApp: App { | ||
| @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate | ||
| private let launchMode = BrainBarLaunchMode.resolve() | ||
|
|
||
| var body: some Scene { | ||
| MenuBarExtra(isInserted: .constant(launchMode == .menuBarWindow)) { | ||
| Button("Open Dashboard") { | ||
| appDelegate.showDashboardPanel() | ||
| } | ||
|
|
||
| Button("Search BrainLayer") { | ||
| appDelegate.showSearchPanel() | ||
| } | ||
|
|
||
| Button("Capture Note") { | ||
| appDelegate.showQuickCapturePanel() | ||
| } | ||
| } label: { | ||
| BrainBarMenuBarLabel(runtime: appDelegate.runtime) | ||
| } | ||
| .menuBarExtraStyle(.menu) | ||
|
|
||
| Settings { | ||
| EmptyView() | ||
| } | ||
|
|
@@ -900,27 +876,3 @@ struct BrainBarApp: App { | |
| } | ||
| } | ||
| } | ||
|
|
||
| private struct BrainBarMenuBarLabel: View { | ||
| @ObservedObject var runtime: BrainBarRuntime | ||
|
|
||
| var body: some View { | ||
| if let collector = runtime.collector { | ||
| let livePresentation = BrainBarLivePresentation.derive(stats: collector.stats) | ||
| HStack(spacing: 6) { | ||
| Image(systemName: "brain") | ||
| Image( | ||
| nsImage: SparklineRenderer.render( | ||
| state: collector.state, | ||
| values: collector.stats.recentEnrichmentBuckets, | ||
| size: NSSize(width: 22, height: 12), | ||
| accentColor: livePresentation.accentColor | ||
| ) | ||
| ) | ||
| .interpolation(.high) | ||
| } | ||
| } else { | ||
| Image(systemName: "brain") | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI pipeline state always shows degraded with PID 0
Medium Severity
makeUIStatsCollectorpassestargetPID: 0toDaemonHealthMonitor. SinceDaemonHealthMonitor.sample()hasguard targetPID > 0 else { return nil }, it always returnsnil. BothrefreshandhandleBrainBusEvent(.healthTick)derive pipeline state from this nil snapshot viaPipelineState.derive(daemon: nil, ...), which the test suite confirms always yields.degraded. The UI sparkline and status indicators will perpetually show degraded state even while the daemon is healthy and actively sendinghealthTickevents over BrainBus.Additional Locations (1)
brain-bar/Sources/BrainBar/Dashboard/StatsCollector.swift#L131-L140Reviewed by Cursor Bugbot for commit 931fa93. Configure here.