diff --git a/clients/ios/App/AppDelegate.swift b/clients/ios/App/AppDelegate.swift index 1531cf991e3..907afb2962d 100644 --- a/clients/ios/App/AppDelegate.swift +++ b/clients/ios/App/AppDelegate.swift @@ -90,6 +90,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { // the new approval flow. Runs once; the flag persists across future launches. migrateToPairingV4IfNeeded() + // Separate migration: clear stale override values when the old toggle was OFF. + // This runs independently of the v4 migration so users who already completed + // v4 migration still get the override cleanup. + migratePairingOverridesIfNeeded() + // Initial connect is handled by SceneDelegate.sceneWillEnterForeground, which fires // during launch and on every background→foreground transition. Calling connect() here // too would race with the scene's connect() since isConnected is false while in-flight. @@ -181,13 +186,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { defaults.removeObject(forKey: "devLocalPairingEnabled") defaults.removeObject(forKey: "iosPairingUseOverride") - // Clear stale override values — the isOverrideEnabled gate was removed - // in M9, so any non-empty override now applies unconditionally. Users - // who had values typed in but the toggle OFF would have those stale - // values silently activate on upgrade. - defaults.removeObject(forKey: PairingConfiguration.gatewayOverrideKey) - defaults.removeObject(forKey: PairingConfiguration.tokenOverrideKey) - // Mark migration as done defaults.set(true, forKey: Self.pairingV4MigrationKey) @@ -197,6 +195,36 @@ class AppDelegate: NSObject, UIApplicationDelegate { log.info("v4 pairing migration complete — legacy pairing state cleared") } + // MARK: - Pairing Override Migration + + /// Key that tracks whether the pairing override migration has run. + private static let pairingOverrideMigrationKey = "pairing_override_migration_done" + + /// Clears stale gateway/token override values when the old toggle was OFF. + /// Runs once; the flag persists across future launches. + /// + /// This is separate from the v4 migration so users who already completed + /// v4 migration still get the override cleanup. + private func migratePairingOverridesIfNeeded() { + let defaults = UserDefaults.standard + guard !defaults.bool(forKey: Self.pairingOverrideMigrationKey) else { return } + + // Only clean up when the legacy toggle key is actually present. + // After M9 the toggle is no longer persisted, so absence means + // the user may have intentionally set overrides post-M9. + if defaults.object(forKey: "iosPairingUseOverride") != nil { + let overrideWasEnabled = defaults.bool(forKey: "iosPairingUseOverride") + if !overrideWasEnabled { + defaults.removeObject(forKey: PairingConfiguration.gatewayOverrideKey) + defaults.removeObject(forKey: PairingConfiguration.tokenOverrideKey) + } + // Clean up the legacy toggle key itself — no longer used. + defaults.removeObject(forKey: "iosPairingUseOverride") + } + + defaults.set(true, forKey: Self.pairingOverrideMigrationKey) + } + func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, diff --git a/clients/macos/vellum-assistant/App/AppDelegate.swift b/clients/macos/vellum-assistant/App/AppDelegate.swift index a1bee502154..78a5878502a 100644 --- a/clients/macos/vellum-assistant/App/AppDelegate.swift +++ b/clients/macos/vellum-assistant/App/AppDelegate.swift @@ -600,24 +600,29 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { /// Key that tracks whether the pairing override migration has run. private static let pairingOverrideMigrationKey = "pairing_override_migration_done" - /// Clears stale gateway/token override values when the old toggle was OFF - /// (or absent). Runs once; the flag persists across future launches. + /// Clears stale gateway/token override values when the old toggle was OFF. + /// Runs once; the flag persists across future launches. + /// + /// Only acts when the legacy `iosPairingUseOverride` key is actually present. + /// After M9 the toggle is no longer persisted, so absence means the user + /// may have intentionally set overrides post-M9 — skip cleanup to preserve them. private func migratePairingOverridesIfNeeded() { let defaults = UserDefaults.standard guard !defaults.bool(forKey: Self.pairingOverrideMigrationKey) else { return } - // If the legacy toggle was off (false or absent), the user did not - // intend for overrides to be active. Clear them so they don't - // silently take effect now that the gate is removed. - let overrideWasEnabled = defaults.bool(forKey: "iosPairingUseOverride") - if !overrideWasEnabled { - defaults.removeObject(forKey: PairingConfiguration.gatewayOverrideKey) - defaults.removeObject(forKey: PairingConfiguration.tokenOverrideKey) + // Only clean up when the legacy toggle key is actually present. + // After M9 the toggle is no longer persisted, so absence means + // the user may have intentionally set overrides post-M9. + if defaults.object(forKey: "iosPairingUseOverride") != nil { + let overrideWasEnabled = defaults.bool(forKey: "iosPairingUseOverride") + if !overrideWasEnabled { + defaults.removeObject(forKey: PairingConfiguration.gatewayOverrideKey) + defaults.removeObject(forKey: PairingConfiguration.tokenOverrideKey) + } + // Clean up the legacy toggle key itself — no longer used. + defaults.removeObject(forKey: "iosPairingUseOverride") } - // Clean up the legacy toggle key itself — no longer used. - defaults.removeObject(forKey: "iosPairingUseOverride") - defaults.set(true, forKey: Self.pairingOverrideMigrationKey) }