codeburn/mac/Sources/CodeBurnMenubar/Data/CopilotExplicitDisconnect.swift
Resham Joshi ff7c40e9ab
fix(menubar): check a provider when its settings open, keep the dock order (#1735)
* fix(menubar): check Cursor when its settings pane opens, keep the dock seed stable

Generic providers were only refreshed while selected in the dock, so a
signed-in Cursor outside a full dock read "Not connected". Opening a
provider's settings pane now runs a quiet probe that records nothing on
failure. Auto-seed also keeps the providers already in the dock and only
fills free slots, so a newly connected provider no longer pushes one out or
resets the preferred card. Remaining edge: after an explicit Disconnect,
reopening the pane re-probes, and a free dock slot would take it back.

* fix(menubar): keep a disconnected provider off until the user connects it

The explicit-disconnect flag Copilot already persists is now per provider.
Disconnect sets it for generic providers too, Connect clears it, and while
it is set the settings pane's quiet probe and the dock auto-seed skip that
provider, so reopening the pane no longer silently reconnects Cursor.

* fix(menubar): only the Disconnect action opts a provider out

Clear Override and Remove saved override share the disconnect path but
mean "go back to automatic", so they no longer set the explicit-disconnect
flag; the pane probe and dock auto-seed keep working after them.

* fix(menubar): the quiet pane probe hides only a missing sign-in

The Settings pane probe recorded nothing on any failure, so an expired login
opened before any refresh read "Not connected". Fetch failures now carry
whether the provider had no credentials or local data at all; the quiet probe
suppresses only that case and records every other failure like a refresh.

* fix(menubar): keep the not-signed-in flag clear of the ClinePass failure fields
2026-10-10 21:40:47 +02:00

57 lines
2.1 KiB
Swift

import Foundation
/// Persisted opt-out for live quota tracking after an explicit Disconnect.
/// Credentials stay untouched; this flag only stops automatic discovery from
/// bringing quota tracking back on the next refresh or relaunch, until the user
/// connects again. Absent key is false, so first-use autodiscovery is unchanged.
enum ProviderExplicitDisconnect {
static func defaultsKey(_ providerID: String) -> String {
"codeburn.\(providerID).explicitlyDisconnected"
}
static func isSet(_ providerID: String, defaults: UserDefaults = .standard) -> Bool {
defaults.bool(forKey: defaultsKey(providerID))
}
static func mark(_ providerID: String, defaults: UserDefaults = .standard) {
defaults.set(true, forKey: defaultsKey(providerID))
}
static func clear(_ providerID: String, defaults: UserDefaults = .standard) {
defaults.removeObject(forKey: defaultsKey(providerID))
}
}
enum CopilotExplicitDisconnect {
static let defaultsKey = ProviderExplicitDisconnect.defaultsKey("copilot")
static func isSet(defaults: UserDefaults = .standard) -> Bool {
ProviderExplicitDisconnect.isSet("copilot", defaults: defaults)
}
static func mark(defaults: UserDefaults = .standard) {
ProviderExplicitDisconnect.mark("copilot", defaults: defaults)
}
static func clear(defaults: UserDefaults = .standard) {
ProviderExplicitDisconnect.clear("copilot", defaults: defaults)
}
}
/// Injectable Copilot quota I/O for AppStore. Production uses `.live`;
/// tests pass an ephemeral UserDefaults suite and stubbed fetch/credential
/// checks so they never touch installed prefs, Keychain, or `gh`.
@MainActor
struct CopilotQuotaRuntime {
var hasCredential: () -> Bool
var refresh: @Sendable () async throws -> CopilotUsage
var disconnectService: () -> Void
var defaults: UserDefaults
static let live = CopilotQuotaRuntime(
hasCredential: { CopilotSubscriptionService.hasCredential },
refresh: { try await CopilotSubscriptionService.refresh() },
disconnectService: { CopilotSubscriptionService.disconnect() },
defaults: .standard
)
}