diff --git a/desktop/Desktop/Sources/AnalyticsManager.swift b/desktop/Desktop/Sources/AnalyticsManager.swift index 68eb1e55b4..a236bdf3a1 100644 --- a/desktop/Desktop/Sources/AnalyticsManager.swift +++ b/desktop/Desktop/Sources/AnalyticsManager.swift @@ -1,5 +1,6 @@ import AppKit import Foundation +import Sentry /// Unified analytics manager that sends events to PostHog. /// Use this instead of calling PostHogManager directly @@ -259,13 +260,18 @@ class AnalyticsManager { databaseInitFailed: Bool ) { guard !Self.isDevBuild else { return } - let properties: [String: Any] = [ + // Routed to Sentry as a breadcrumb (perf telemetry, not product analytics) so the data + // is attached to any same-session crash report without creating a per-launch analytics + // event. If we ever need real perf metrics, wire up SentrySDK.startTransaction here. + let breadcrumb = Breadcrumb(level: .info, category: "app.startup") + breadcrumb.message = "App Startup Timing" + breadcrumb.data = [ "db_init_ms": round(dbInitMs), "time_to_interactive_ms": round(timeToInteractiveMs), "had_unclean_shutdown": hadUncleanShutdown, "database_init_failed": databaseInitFailed, ] - PostHogManager.shared.track("App Startup Timing", properties: properties) + SentrySDK.addBreadcrumb(breadcrumb) } /// Track first launch with comprehensive system diagnostics @@ -356,14 +362,6 @@ class AnalyticsManager { return diagnostics } - func appBecameActive() { - PostHogManager.shared.appBecameActive() - } - - func appResignedActive() { - PostHogManager.shared.appResignedActive() - } - // MARK: - Conversation Events // Note: The event is named "Memory Created" in analytics for historical reasons, // but it actually tracks when a conversation/recording is created, not a "memory". @@ -545,11 +543,6 @@ class AnalyticsManager { // MARK: - Launch At Login Events - /// Track launch at login status once per app launch (not continuously) - func launchAtLoginStatusChecked(enabled: Bool) { - PostHogManager.shared.launchAtLoginStatusChecked(enabled: enabled) - } - /// Track when launch at login state changes /// - Parameters: /// - enabled: New state @@ -636,10 +629,6 @@ class AnalyticsManager { // MARK: - Update Events - func updateCheckStarted() { - PostHogManager.shared.updateCheckStarted() - } - func updateAvailable(version: String) { PostHogManager.shared.updateAvailable(version: version) } @@ -648,20 +637,6 @@ class AnalyticsManager { PostHogManager.shared.updateInstalled(version: version) } - func updateNotFound() { - PostHogManager.shared.updateNotFound() - } - - func updateCheckFailed( - error: String, errorDomain: String, errorCode: Int, underlyingError: String? = nil, - underlyingDomain: String? = nil, underlyingCode: Int? = nil - ) { - PostHogManager.shared.updateCheckFailed( - error: error, errorDomain: errorDomain, errorCode: errorCode, - underlyingError: underlyingError, underlyingDomain: underlyingDomain, - underlyingCode: underlyingCode) - } - // MARK: - Notification Events func notificationSent(notificationId: String, title: String, assistantId: String, surface: String) { @@ -709,18 +684,6 @@ class AnalyticsManager { PostHogManager.shared.chatBridgeModeChanged(from: oldMode, to: newMode) } - // MARK: - Settings State - - /// Track the current state of key settings (screenshots, memory extraction, notifications) - /// Called when monitoring starts and daily while monitoring is active - func trackSettingsState( - screenshotsEnabled: Bool, memoryExtractionEnabled: Bool, memoryNotificationsEnabled: Bool - ) { - PostHogManager.shared.settingsStateTracked( - screenshotsEnabled: screenshotsEnabled, memoryExtractionEnabled: memoryExtractionEnabled, - memoryNotificationsEnabled: memoryNotificationsEnabled) - } - // MARK: - All Settings State (Comprehensive daily report) private let lastAllSettingsReportKey = "lastAllSettingsReportDate" @@ -927,32 +890,4 @@ class AnalyticsManager { PostHogManager.shared.track("knowledge_graph_build_failed", properties: props) } - // MARK: - Display Info - - /// Track display characteristics (notch, screen size, etc.) - /// Called at app launch to help diagnose menu bar visibility issues - func trackDisplayInfo() { - guard let screen = NSScreen.main else { return } - - let frame = screen.frame - let visibleFrame = screen.visibleFrame - let safeAreaInsets = screen.safeAreaInsets - - // Detect notch: MacBooks with notch have safeAreaInsets.top > 0 - let hasNotch = safeAreaInsets.top > 0 - - // Calculate menu bar height (difference between frame and visible frame at top) - let menuBarHeight = frame.height - visibleFrame.height - visibleFrame.origin.y - - let displayInfo: [String: Any] = [ - "screen_width": Int(frame.width), - "screen_height": Int(frame.height), - "has_notch": hasNotch, - "safe_area_top": Int(safeAreaInsets.top), - "menu_bar_height": Int(menuBarHeight), - "scale_factor": screen.backingScaleFactor, - ] - - PostHogManager.shared.displayInfoTracked(info: displayInfo) - } } diff --git a/desktop/Desktop/Sources/OmiApp.swift b/desktop/Desktop/Sources/OmiApp.swift index 7cd51f15d6..25fb6739ed 100644 --- a/desktop/Desktop/Sources/OmiApp.swift +++ b/desktop/Desktop/Sources/OmiApp.swift @@ -352,7 +352,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { AnalyticsManager.shared.initialize() AnalyticsManager.shared.detectAndReportCrash() AnalyticsManager.shared.appLaunched() - AnalyticsManager.shared.trackDisplayInfo() // Tier gating: migrate old boolean key to new 6-tier system TierManager.migrateExistingUsersIfNeeded() @@ -433,12 +432,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { self?.updateOnboardingLifecyclePolicy(reason: "user_defaults_changed") } - // Track launch at login status once per app launch - Task { @MainActor in - let isEnabled = LaunchAtLoginManager.shared.isEnabled - AnalyticsManager.shared.launchAtLoginStatusChecked(enabled: isEnabled) - } - // Register for Apple Events to handle URL scheme NSAppleEventManager.shared().setEventHandler( self, @@ -1251,12 +1244,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } func applicationDidBecomeActive(_ notification: Notification) { - AnalyticsManager.shared.appBecameActive() // Sync remote assistant settings so server-side changes take effect promptly Task { await SettingsSyncManager.shared.syncFromServer() } } - - func applicationWillResignActive(_ notification: Notification) { - AnalyticsManager.shared.appResignedActive() - } } diff --git a/desktop/Desktop/Sources/PostHogManager.swift b/desktop/Desktop/Sources/PostHogManager.swift index 184762d1f7..0a5e01fc43 100644 --- a/desktop/Desktop/Sources/PostHogManager.swift +++ b/desktop/Desktop/Sources/PostHogManager.swift @@ -25,8 +25,8 @@ class PostHogManager { // Disable automatic lifecycle events — PostHog's observer calls setResourceValues(isExcludedFromBackupKey:) // synchronously on the main thread (via NSApplicationDidFinishLaunchingNotification), which XPCs to the - // mds (Spotlight) daemon and can hang for 2000ms+ when the daemon is slow. We already track lifecycle - // events manually via AnalyticsManager.shared.appLaunched() / appBecameActive() etc. + // mds (Spotlight) daemon and can hang for 2000ms+ when the daemon is slow. We track the meaningful + // lifecycle event (App Launched / First Launch) manually via AnalyticsManager.shared. config.captureApplicationLifecycleEvents = false config.captureScreenViews = true config.preloadFeatureFlags = true @@ -331,14 +331,6 @@ extension PostHogManager { track("First Launch", properties: diagnostics) } - func appBecameActive() { - track("App Became Active") - } - - func appResignedActive() { - track("App Resigned Active") - } - // MARK: - Page/Screen Views (PostHog specific) func pageViewed(_ pageName: String) { @@ -475,12 +467,6 @@ extension PostHogManager { // MARK: - Launch At Login Events - func launchAtLoginStatusChecked(enabled: Bool) { - track("Launch At Login Status", properties: [ - "enabled": enabled - ]) - } - func launchAtLoginChanged(enabled: Bool, source: String) { track("Launch At Login Changed", properties: [ "enabled": enabled, @@ -607,10 +593,6 @@ extension PostHogManager { // MARK: - Update Events - func updateCheckStarted() { - track("Update Check Started") - } - func updateAvailable(version: String) { track("Update Available", properties: [ "version": version @@ -623,22 +605,6 @@ extension PostHogManager { ]) } - func updateNotFound() { - track("Update Not Found") - } - - func updateCheckFailed(error: String, errorDomain: String, errorCode: Int, underlyingError: String? = nil, underlyingDomain: String? = nil, underlyingCode: Int? = nil) { - var props: [String: Any] = [ - "error": error, - "error_domain": errorDomain, - "error_code": errorCode - ] - if let underlyingError { props["underlying_error"] = underlyingError } - if let underlyingDomain { props["underlying_domain"] = underlyingDomain } - if let underlyingCode { props["underlying_code"] = underlyingCode } - track("Update Check Failed", properties: props) - } - // MARK: - Notification Events func notificationSent(notificationId: String, title: String, assistantId: String, surface: String) { @@ -709,22 +675,8 @@ extension PostHogManager { // MARK: - Settings State - func settingsStateTracked(screenshotsEnabled: Bool, memoryExtractionEnabled: Bool, memoryNotificationsEnabled: Bool) { - track("Settings State", properties: [ - "screenshots_enabled": screenshotsEnabled, - "memory_extraction_enabled": memoryExtractionEnabled, - "memory_notifications_enabled": memoryNotificationsEnabled - ]) - } - /// Comprehensive all-settings snapshot (fired on app launch, at most once per day) func allSettingsStateTracked(properties: [String: Any]) { track("All Settings State", properties: properties) } - - // MARK: - Display Info - - func displayInfoTracked(info: [String: Any]) { - track("Display Info", properties: info) - } } diff --git a/desktop/Desktop/Sources/ProactiveAssistants/ProactiveAssistantsPlugin.swift b/desktop/Desktop/Sources/ProactiveAssistants/ProactiveAssistantsPlugin.swift index 4a06b9bbe7..df05bdd2e4 100644 --- a/desktop/Desktop/Sources/ProactiveAssistants/ProactiveAssistantsPlugin.swift +++ b/desktop/Desktop/Sources/ProactiveAssistants/ProactiveAssistantsPlugin.swift @@ -52,9 +52,6 @@ public class ProactiveAssistantsPlugin: NSObject { private var wasMonitoringBeforeLock = false private var systemEventObservers: [NSObjectProtocol] = [] - // Daily settings state tracking - private var settingsStateTimer: Timer? - // Video call throttling: reduce capture frequency when a call app is frontmost // to avoid competing with the call app for CPU/GPU (ScreenCaptureKit, encoding, OCR). private var videoCallFrameCounter = 0 @@ -456,8 +453,6 @@ public class ProactiveAssistantsPlugin: NSObject { sendEvent(type: "monitoringStarted", data: [:]) AnalyticsManager.shared.monitoringStarted() - trackSettingsState() - startSettingsStateTimer() NotificationCenter.default.post( name: .assistantMonitoringStateDidChange, object: nil, @@ -478,8 +473,6 @@ public class ProactiveAssistantsPlugin: NSObject { analysisDelayTimer = nil distributionDebounceTimer?.invalidate() distributionDebounceTimer = nil - settingsStateTimer?.invalidate() - settingsStateTimer = nil isInDelayPeriod = false lastDistributedApp = nil lastDistributedWindowTitle = nil @@ -956,27 +949,6 @@ public class ProactiveAssistantsPlugin: NSObject { AssistantCoordinator.shared.distributeFrame(frame) } - // MARK: - Settings State Tracking - - /// Track current settings state to analytics - private func trackSettingsState() { - AnalyticsManager.shared.trackSettingsState( - screenshotsEnabled: isMonitoring, - memoryExtractionEnabled: MemoryAssistantSettings.shared.isEnabled, - memoryNotificationsEnabled: MemoryAssistantSettings.shared.notificationsEnabled - ) - } - - /// Start a daily timer to report settings state - private func startSettingsStateTimer() { - settingsStateTimer?.invalidate() - settingsStateTimer = Timer.scheduledTimer(withTimeInterval: 86400, repeats: true) { [weak self] _ in - Task { @MainActor in - self?.trackSettingsState() - } - } - } - // MARK: - Event Broadcasting private func sendEvent(type: String, data: [String: Any]) { diff --git a/desktop/Desktop/Sources/UpdaterViewModel.swift b/desktop/Desktop/Sources/UpdaterViewModel.swift index 890f0c97b7..2f4fb62c1f 100644 --- a/desktop/Desktop/Sources/UpdaterViewModel.swift +++ b/desktop/Desktop/Sources/UpdaterViewModel.swift @@ -43,9 +43,6 @@ final class UpdaterDelegate: NSObject, SPUUpdaterDelegate { /// Called when Sparkle is about to check for updates (permission gate) func updater(_ updater: SPUUpdater, mayPerform check: SPUUpdateCheck) throws { logSync("Sparkle: Starting update check") - Task { @MainActor in - AnalyticsManager.shared.updateCheckStarted() - } } /// Called when Sparkle finishes loading the appcast @@ -85,7 +82,6 @@ final class UpdaterDelegate: NSObject, SPUUpdaterDelegate { func updaterDidNotFindUpdate(_ updater: SPUUpdater) { logSync("Sparkle: No update available") Task { @MainActor in - AnalyticsManager.shared.updateNotFound() self.viewModel?.updateAvailable = false } } @@ -112,29 +108,6 @@ final class UpdaterDelegate: NSObject, SPUUpdaterDelegate { for (key, value) in nsError.userInfo where key != NSUnderlyingErrorKey { logSync("Sparkle: Error info [\(key)] = \(value)") } - // Build diagnostic properties for analytics - let errorDomain = nsError.domain - let errorCode = nsError.code - var underlyingMessage: String? = nil - var underlyingDomain: String? = nil - var underlyingCode: Int? = nil - - if let underlying = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { - underlyingMessage = underlying.localizedDescription - underlyingDomain = underlying.domain - underlyingCode = underlying.code - } - - Task { @MainActor in - AnalyticsManager.shared.updateCheckFailed( - error: message, - errorDomain: errorDomain, - errorCode: errorCode, - underlyingError: underlyingMessage, - underlyingDomain: underlyingDomain, - underlyingCode: underlyingCode - ) - } // SUInstallationError (4005): Sparkle's installer failed to launch. // Don't open the browser — Sparkle will retry on next check cycle.