From 31cedf1c3fdcb828f893e7cee100332e3de33568 Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin Date: Thu, 7 May 2026 08:39:16 +0000 Subject: [PATCH 1/4] chore(desktop/analytics): drop Update Check Started/Not Found/Failed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These three events fire on Sparkle's 10-minute polling timer (SUScheduledCheckInterval=600), not on user action. Cumulatively they are 834,313 events in April 2026 alone (4.07% of the desktop-era month, per the Mixpanel raw export bundle). Update Available and Update Installed remain — they carry the actual product signal. The Sparkle delegate already logs the start / not-found / failed cases via logSync to /private/tmp/omi.log, so on-device diagnostics are unaffected. Refs #7148. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Desktop/Sources/AnalyticsManager.swift | 18 ------------- desktop/Desktop/Sources/PostHogManager.swift | 20 -------------- .../Desktop/Sources/UpdaterViewModel.swift | 27 ------------------- 3 files changed, 65 deletions(-) diff --git a/desktop/Desktop/Sources/AnalyticsManager.swift b/desktop/Desktop/Sources/AnalyticsManager.swift index 68eb1e55b4d..dfa60f296b6 100644 --- a/desktop/Desktop/Sources/AnalyticsManager.swift +++ b/desktop/Desktop/Sources/AnalyticsManager.swift @@ -636,10 +636,6 @@ class AnalyticsManager { // MARK: - Update Events - func updateCheckStarted() { - PostHogManager.shared.updateCheckStarted() - } - func updateAvailable(version: String) { PostHogManager.shared.updateAvailable(version: version) } @@ -648,20 +644,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) { diff --git a/desktop/Desktop/Sources/PostHogManager.swift b/desktop/Desktop/Sources/PostHogManager.swift index 184762d1f7b..bbb80633c5d 100644 --- a/desktop/Desktop/Sources/PostHogManager.swift +++ b/desktop/Desktop/Sources/PostHogManager.swift @@ -607,10 +607,6 @@ extension PostHogManager { // MARK: - Update Events - func updateCheckStarted() { - track("Update Check Started") - } - func updateAvailable(version: String) { track("Update Available", properties: [ "version": version @@ -623,22 +619,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) { diff --git a/desktop/Desktop/Sources/UpdaterViewModel.swift b/desktop/Desktop/Sources/UpdaterViewModel.swift index 890f0c97b7e..2f4fb62c1fd 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. From ef0b7b1706cfe9d45b49cb259a0d3449ffda3fcb Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin Date: Thu, 7 May 2026 08:40:17 +0000 Subject: [PATCH 2/4] chore(desktop/analytics): drop App Became/Resigned Active focus events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These fire on every NSApplication.didBecomeActiveNotification and willResignActiveNotification — i.e. every cmd-tab. Pure focus noise, not actionable signal. 505,377 events in April 2026 alone (2.46% of the desktop-era month). The applicationDidBecomeActive delegate still runs SettingsSyncManager on focus, which is the only meaningful side effect; the empty applicationWillResignActive delegate is dropped entirely (it's an optional NSApplicationDelegate method). Refs #7148. Co-Authored-By: Claude Opus 4.7 (1M context) --- desktop/Desktop/Sources/AnalyticsManager.swift | 8 -------- desktop/Desktop/Sources/OmiApp.swift | 5 ----- desktop/Desktop/Sources/PostHogManager.swift | 12 ++---------- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/desktop/Desktop/Sources/AnalyticsManager.swift b/desktop/Desktop/Sources/AnalyticsManager.swift index dfa60f296b6..91dc3f48e96 100644 --- a/desktop/Desktop/Sources/AnalyticsManager.swift +++ b/desktop/Desktop/Sources/AnalyticsManager.swift @@ -356,14 +356,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". diff --git a/desktop/Desktop/Sources/OmiApp.swift b/desktop/Desktop/Sources/OmiApp.swift index 7cd51f15d61..e92e36d0440 100644 --- a/desktop/Desktop/Sources/OmiApp.swift +++ b/desktop/Desktop/Sources/OmiApp.swift @@ -1251,12 +1251,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 bbb80633c5d..6ac13f8d2ee 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) { From e9e0903a9dfdf4ac6a21fd2df4f9309dbe512b74 Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin Date: Thu, 7 May 2026 08:42:56 +0000 Subject: [PATCH 3/4] chore(desktop/analytics): drop Display Info / Launch At Login Status / Settings State MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three operational/diagnostic events with no product signal: - Display Info — screen-dimensions snapshot fired on app launch - Launch At Login Status — polled config-state read at launch - Settings State — 3-boolean settings snapshot fired on monitoring start AND every 24h via a Timer in ProactiveAssistantsPlugin Folded into the existing "All Settings State" event, which already fires once per calendar day on launch with all ~45 user settings — that snapshot covers the same use case at a fraction of the volume. The settingsStateTimer (Timer with 86400s interval) and its plumbing in ProactiveAssistantsPlugin are removed entirely. Launch At Login Changed (the user-action event, not the polled status) is preserved. Total April 2026 cull from this commit: 292,916 events (1.43% of desktop-era month). Refs #7148. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Desktop/Sources/AnalyticsManager.swift | 45 ------------------- desktop/Desktop/Sources/OmiApp.swift | 7 --- desktop/Desktop/Sources/PostHogManager.swift | 20 --------- .../ProactiveAssistantsPlugin.swift | 28 ------------ 4 files changed, 100 deletions(-) diff --git a/desktop/Desktop/Sources/AnalyticsManager.swift b/desktop/Desktop/Sources/AnalyticsManager.swift index 91dc3f48e96..37058183326 100644 --- a/desktop/Desktop/Sources/AnalyticsManager.swift +++ b/desktop/Desktop/Sources/AnalyticsManager.swift @@ -537,11 +537,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 @@ -683,18 +678,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" @@ -901,32 +884,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 e92e36d0440..25fb6739edd 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, diff --git a/desktop/Desktop/Sources/PostHogManager.swift b/desktop/Desktop/Sources/PostHogManager.swift index 6ac13f8d2ee..0a5e01fc43b 100644 --- a/desktop/Desktop/Sources/PostHogManager.swift +++ b/desktop/Desktop/Sources/PostHogManager.swift @@ -467,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, @@ -681,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 4a06b9bbe73..df05bdd2e44 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]) { From fb0ac156bbd1437f3b75ab1c141e88a3280c34c3 Mon Sep 17 00:00:00 2001 From: Mohammed Mohsin Date: Thu, 7 May 2026 08:43:55 +0000 Subject: [PATCH 4/4] chore(desktop/analytics): redirect App Startup Timing to Sentry breadcrumb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cold-start timing (db_init_ms, time_to_interactive_ms, unclean shutdown flag, db init failure flag) is operational perf telemetry, not product analytics. Move it from PostHog to Sentry as a breadcrumb on category 'app.startup'. Sentry only sends breadcrumbs when an event is captured in the same session, so: - Normal launches: nothing transmitted (good — no per-launch volume) - Same-session crash: timing data is attached to the crash report automatically (good — that's exactly when this data has value) If we want active perf metrics later, the right move is SentrySDK.startTransaction with measurements; this commit is just the analytics-volume cull. Refs #7148. Co-Authored-By: Claude Opus 4.7 (1M context) --- desktop/Desktop/Sources/AnalyticsManager.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/desktop/Desktop/Sources/AnalyticsManager.swift b/desktop/Desktop/Sources/AnalyticsManager.swift index 37058183326..a236bdf3a18 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