feat(settings): add Remove configuration for non-primary providers#280
Open
caezium wants to merge 3 commits into
Open
feat(settings): add Remove configuration for non-primary providers#280caezium wants to merge 3 commits into
caezium wants to merge 3 commits into
Conversation
Today the Settings -> Providers tab lets you Set / Unset a provider in the secondary slot, but leaves the underlying credentials (Keychain entries, setup-complete flags, endpoint URLs, CLI tool preference) behind. You can't fully revert after trying a provider out. Adds: - ProvidersSettingsViewModel.removeProviderConfig(_:) — wipes the per-provider persisted state for Gemini (Keychain + model preference + setup flag), Ollama (endpoint, model, engine, local API key, setup flag), and ChatGPT/Claude (CLI tool preference, setup flag). Also clears the backup-slot assignment if the removed provider was occupying it. - canRemoveProviderConfig(_:) — guards against removing the currently active primary (would leave the app with no working provider) and against Dayflow Pro (server-side entitlement, not local config). - GeminiModelPreference.clear() — small static helper so the view model doesn't need to know the private storage key. - "Remove configuration" button per provider row + destructive confirmation alert with a per-provider message explaining what gets cleared (e.g. "deletes Gemini API key from Keychain"). `canonicalProviderId(for:)` promoted from private to internal so the alert can render a provider-specific message.
ChatGPT and Claude share one persisted configuration under the canonical id chatgpt_claude, but each surfaces as its own provider id in the routing layer. The old guards compared the raw provider id against primaryRoutingProviderId / isBackupProvider, which meant the user could remove the chatgpt_claude config while "claude" (or vice-versa) was still the active routing primary — bricking the provider until they re-onboarded. - canRemoveProviderConfig now compares canonical ids on both sides (canonicalProviderId(for: primaryRoutingProviderId) != canonical). - removeProviderConfig's backup-clear branch likewise checks the stored backupProvider against canonical instead of routing the comparison through isBackupProvider(providerId) which uses raw ids.
…provider removeProviderConfig's local branch did UserDefaults.removeObject for llmLocalBaseURL, llmLocalModelId, llmLocalEngine, llmLocalAPIKey, then set the @published fields to empty strings. Each @published has a didSet that writes the new value back into UserDefaults — so the removes were immediately overwritten with empty strings, leaving stale keys instead of cleared state. - Add a suppressLocalSettingsPersistence flag guarded inside every Local-prop didSet (localEngine, localBaseURL, localModelId, localAPIKey). - Extract a resetLocalProviderFieldsAfterRemoval() helper that flips the flag, restores proper defaults (Ollama base URL, default model id) instead of blanks, and clears in defer. - Also call LocalModelPreferences.clearPreset() so the preset selection doesn't survive removal.
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
There's currently no way to remove a configured provider from Settings → Providers — you can only switch which one is active. If you've experimented with multiple providers (Local, Gemini, ChatGPT/Claude) the abandoned configs, Keychain entries, and UserDefaults keys stay around indefinitely, and the only escape hatch is hand-editing.
This PR adds a Remove configuration affordance for non-primary providers, plus two safety fixes for edge cases the removal path uncovered during implementation.
1. Add Remove configuration UI (feat)
A "Remove configuration" button on each non-primary provider row in
SettingsProvidersTabView, wired through a newProvidersSettingsViewModel.removeProviderConfig(_:)which:\(provider)SetupCompleteand provider-specific UserDefaults (model id, base URL, CLI tool preference)@Publishedstate so the UI reflects the cleared config immediatelycanRemoveProviderConfig(_:)gates the affordance to prevent removing:2. Compare canonical provider IDs when gating removal (fix)
ChatGPT and Claude share one persisted config under the canonical id
chatgpt_claude, but each surfaces as its own provider id in the routing layer. The initial guard compared raw provider IDs, which meant thechatgpt_claudeconfig could be removed whileclaude(orchatgpt) was still active — bricking the provider until the user re-onboarded.The backup-clear branch in
removeProviderConfiggets the same canonical comparison.3. Stop UserDefaults write-back race on Local provider removal (fix)
The Local-provider branch of
removeProviderConfigcalledUserDefaults.removeObject(forKey:)forllmLocalBaseURL,llmLocalModelId,llmLocalEngine,llmLocalAPIKey, then set the corresponding@Publishedproperties to"". Each@Publishedhas adidSetthat writes the new value back into UserDefaults — so the removes were immediately overwritten with empty strings, leaving stale empty-string keys instead of cleared state.Fix:
suppressLocalSettingsPersistenceflag guarded inside every Local-propdidSetresetLocalProviderFieldsAfterRemoval()which flips the flag, restores proper defaults (Ollama base URL, default model id) rather than blanks, and clears the flag indeferLocalModelPreferences.clearPreset()so the preset selection doesn't survive removalTest plan
geminiSetupCompletecleared, UI reverts to "Not configured"chatgpt_clauderow until primary switches away