From 75d5a612c6b81258ca9b30751167eb335d2fafa5 Mon Sep 17 00:00:00 2001 From: akramj13 <125495000+akramj13@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:51:55 -0400 Subject: [PATCH 1/2] fix: check for updates once daily --- .../Services/Utilities/AppUpdateManager.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Cotabby/Services/Utilities/AppUpdateManager.swift b/Cotabby/Services/Utilities/AppUpdateManager.swift index 54c143d4..677dae11 100644 --- a/Cotabby/Services/Utilities/AppUpdateManager.swift +++ b/Cotabby/Services/Utilities/AppUpdateManager.swift @@ -17,6 +17,11 @@ final class AppUpdateManager { private var isStarted = false + /// Sparkle persists this setting in user defaults, which take precedence over the value in + /// `CotabbyInfo.plist`. Reapplying the product policy repairs older installs that may have saved + /// a shorter development interval while the plist still gives fresh installs the same default. + private static let automaticCheckInterval: TimeInterval = 24 * 60 * 60 + private static let debugCheckForUpdatesOnLaunchArgument = "-Cotabby-check-for-updates-on-launch" private static let publicKeyPlaceholder = "REPLACE_WITH_GENERATED_SPARKLE_PUBLIC_ED_KEY" @@ -53,19 +58,13 @@ final class AppUpdateManager { return } + // Configure the interval before starting so Sparkle's first scheduling decision uses the + // daily cadence together with its persisted last-check date. + updaterController.updater.updateCheckInterval = Self.automaticCheckInterval updaterController.startUpdater() isStarted = true log("Sparkle updater started.") - // Check once on every launch. Sparkle's scheduled check only fires on launch when the - // interval has already elapsed, so frequent users (who reopen within a day) would never - // see a check on open. This is a *background* check: it silently does nothing when the app - // is up to date and only surfaces UI when an update is actually available — unlike - // `checkForUpdates()`, which always shows a result dialog and is reserved for the manual - // "Check for Updates" button. The daily `SUScheduledCheckInterval` then covers long-running - // sessions where the app stays open for days. - updaterController.updater.checkForUpdatesInBackground() - #if DEBUG if ProcessInfo.processInfo.arguments.contains(Self.debugCheckForUpdatesOnLaunchArgument) { log("Debug launch argument requested an immediate update check.") From f5d078c34980e45f00f1c814f8ba502da84dc939 Mon Sep 17 00:00:00 2001 From: akramj13 <125495000+akramj13@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:24:41 -0400 Subject: [PATCH 2/2] fix: catch up overdue update checks at launch --- Cotabby/Services/Utilities/AppUpdateManager.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Cotabby/Services/Utilities/AppUpdateManager.swift b/Cotabby/Services/Utilities/AppUpdateManager.swift index 677dae11..94e08d17 100644 --- a/Cotabby/Services/Utilities/AppUpdateManager.swift +++ b/Cotabby/Services/Utilities/AppUpdateManager.swift @@ -60,11 +60,20 @@ final class AppUpdateManager { // Configure the interval before starting so Sparkle's first scheduling decision uses the // daily cadence together with its persisted last-check date. - updaterController.updater.updateCheckInterval = Self.automaticCheckInterval + let updater = updaterController.updater + updater.updateCheckInterval = Self.automaticCheckInterval updaterController.startUpdater() isStarted = true log("Sparkle updater started.") + // Catch up immediately when the app returns after the daily interval. Recent launches leave + // the check to Sparkle's scheduler, so reopening Cotabby cannot bypass the daily cadence. + let shouldCatchUp = updater.lastUpdateCheckDate + .map { Date().timeIntervalSince($0) >= Self.automaticCheckInterval } ?? true + if shouldCatchUp { + updater.checkForUpdatesInBackground() + } + #if DEBUG if ProcessInfo.processInfo.arguments.contains(Self.debugCheckForUpdatesOnLaunchArgument) { log("Debug launch argument requested an immediate update check.")