Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions clients/ios/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ 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)
Comment thread
ashleeradka marked this conversation as resolved.
Comment thread
ashleeradka marked this conversation as resolved.

// Mark migration as done
defaults.set(true, forKey: Self.pairingV4MigrationKey)

Expand Down
32 changes: 32 additions & 0 deletions clients/macos/vellum-assistant/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ public final class AppDelegate: NSObject, NSApplicationDelegate {
// renders EmptyView — we handle settings in the main window panel).
UserDefaults.standard.removeObject(forKey: "NSWindow Frame com_apple_SwiftUI_Settings_window")

// Migration: clear stale pairing override values when the toggle was OFF.
// M9 removed the isOverrideEnabled gate — any non-empty override now
// applies unconditionally. Users who had override values typed in but
// the toggle OFF would have those stale values silently activate.
migratePairingOverridesIfNeeded()

if let envPath = FeatureFlagManager.findRepoEnvFile() {
FeatureFlagManager.shared.loadFromFile(at: envPath)
}
Expand Down Expand Up @@ -587,6 +593,32 @@ public final class AppDelegate: NSObject, NSApplicationDelegate {
log.info("Configured HTTP transport for remote assistant \(assistant.assistantId) at \(runtimeUrl, privacy: .public)")
}

// 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
/// (or absent). Runs once; the flag persists across future launches.
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)
}
Comment thread
ashleeradka marked this conversation as resolved.

// Clean up the legacy toggle key itself — no longer used.
defaults.removeObject(forKey: "iosPairingUseOverride")

defaults.set(true, forKey: Self.pairingOverrideMigrationKey)
}

func setupDaemonClient() {
guard !hasSetupDaemon else { return }
hasSetupDaemon = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,42 @@ final class SettingsStoreOverrideResolutionTests: XCTestCase {
// Each test manipulates UserDefaults keys that the override resolution
// reads. Clean up after each test to avoid cross-contamination.
override func tearDown() {
UserDefaults.standard.removeObject(forKey: "iosPairingUseOverride")
UserDefaults.standard.removeObject(forKey: "iosPairingGatewayOverride")
UserDefaults.standard.removeObject(forKey: "iosPairingTokenOverride")
super.tearDown()
}

// MARK: - iOS Gateway URL

func testIosGatewayReturnsGlobalWhenOverrideOff() {
UserDefaults.standard.set(false, forKey: "iosPairingUseOverride")
func testIosGatewayReturnsOverrideWhenNonEmpty() {
UserDefaults.standard.set("https://custom.example.com", forKey: "iosPairingGatewayOverride")

let store = SettingsStore()
// Simulate global URL being set via IPC response
store.ingressPublicBaseUrl = "https://global.example.com"

XCTAssertEqual(store.resolvedIosGatewayUrl, "https://global.example.com")
XCTAssertEqual(store.resolvedIosGatewayUrl, "https://custom.example.com")
}

func testIosGatewayReturnsOverrideWhenOverrideOn() {
UserDefaults.standard.set(true, forKey: "iosPairingUseOverride")
UserDefaults.standard.set("https://custom.example.com", forKey: "iosPairingGatewayOverride")
func testIosGatewayFallsBackToGlobalWhenOverrideEmpty() {
UserDefaults.standard.set("", forKey: "iosPairingGatewayOverride")

let store = SettingsStore()
store.ingressPublicBaseUrl = "https://global.example.com"

XCTAssertEqual(store.resolvedIosGatewayUrl, "https://custom.example.com")
XCTAssertEqual(store.resolvedIosGatewayUrl, "https://global.example.com")
}

func testIosGatewayFallsBackToGlobalWhenOverrideOnButEmpty() {
UserDefaults.standard.set(true, forKey: "iosPairingUseOverride")
UserDefaults.standard.set("", forKey: "iosPairingGatewayOverride")
func testIosGatewayFallsBackToGlobalWhenOverrideWhitespaceOnly() {
UserDefaults.standard.set(" ", forKey: "iosPairingGatewayOverride")

let store = SettingsStore()
store.ingressPublicBaseUrl = "https://global.example.com"

XCTAssertEqual(store.resolvedIosGatewayUrl, "https://global.example.com")
}

func testIosGatewayFallsBackToGlobalWhenOverrideOnAndWhitespaceOnly() {
UserDefaults.standard.set(true, forKey: "iosPairingUseOverride")
UserDefaults.standard.set(" ", forKey: "iosPairingGatewayOverride")

func testIosGatewayFallsBackToGlobalWhenOverrideAbsent() {
// No override key set at all
let store = SettingsStore()
store.ingressPublicBaseUrl = "https://global.example.com"

Expand Down
Loading