From 7bef53ed20daa8fa56c0178d7e0a64c1bdd6ab46 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 23 Apr 2026 21:35:54 +0000 Subject: [PATCH 1/6] feat: add provider reconfigure and disconnect actions --- app-prefixable/src/context/providers.tsx | 14 ++ app-prefixable/src/pages/settings.tsx | 258 ++++++++++++++++------- 2 files changed, 193 insertions(+), 79 deletions(-) diff --git a/app-prefixable/src/context/providers.tsx b/app-prefixable/src/context/providers.tsx index d6ab82d5..1dc25921 100644 --- a/app-prefixable/src/context/providers.tsx +++ b/app-prefixable/src/context/providers.tsx @@ -123,6 +123,7 @@ interface ProviderContextValue { } refetch: () => void connectProvider: (providerID: string, apiKey: string) => Promise + disconnectProvider: (providerID: string) => Promise startOAuth: (providerID: string, methodIndex: number) => Promise completeOAuth: (providerID: string, methodIndex: number, code?: string) => Promise } @@ -447,6 +448,18 @@ export function ProviderProvider(props: ParentProps) { } } + async function disconnectProvider(providerID: string): Promise { + try { + await client.auth.remove({ providerID }) + await client.instance.dispose() + await refetchProviders() + return true + } catch (e) { + console.error("Failed to disconnect provider:", e) + return false + } + } + async function startOAuth(providerID: string, methodIndex: number): Promise { try { const res = await client.provider.oauth.authorize({ @@ -526,6 +539,7 @@ export function ProviderProvider(props: ParentProps) { }, refetch, connectProvider, + disconnectProvider, startOAuth, completeOAuth, } diff --git a/app-prefixable/src/pages/settings.tsx b/app-prefixable/src/pages/settings.tsx index 89552493..5ce0be18 100644 --- a/app-prefixable/src/pages/settings.tsx +++ b/app-prefixable/src/pages/settings.tsx @@ -51,6 +51,7 @@ export function Settings() { const [mcpLoading, setMcpLoading] = createSignal(null) const [mcpDeleting, setMcpDeleting] = createSignal(null) const [mcpToDelete, setMcpToDelete] = createSignal(null) + const [providerToDelete, setProviderToDelete] = createSignal(null) // Saved prompts const savedPrompts = useSavedPrompts() @@ -217,6 +218,12 @@ export function Settings() { return providers.authMethods[id] || [] }) + const selectedProviderName = createMemo(() => { + const id = selectedProvider() + if (!id) return null + return getProviderDisplayName(id) + }) + // Popular providers shown first const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter"] @@ -564,6 +571,7 @@ Add your project-specific instructions here. const key = apiKey().trim() if (!providerID || !key) return + const wasConnected = providers.connected.includes(providerID) setConnecting(true) setError(null) @@ -574,14 +582,47 @@ Add your project-specific instructions here. setConnecting(false) if (ok) { - setSuccess(`Connected to ${providerID}!`) + setSuccess(`${wasConnected ? "Updated" : "Connected to"} ${getProviderDisplayName(providerID)}!`) setApiKey("") setSelectedProvider(null) + setProviderSearch("") } else { setError("Failed to connect. Please check your API key.") } } + function startProviderEdit(providerID: string) { + setError(null) + setSuccess(null) + setOauthPending(null) + setOauthCode("") + setCodeCopied(false) + setApiKey("") + setProviderSearch("") + setSelectedProvider(providerID) + } + + async function confirmProviderDelete() { + const providerID = providerToDelete() + if (!providerID) return + setProviderToDelete(null) + setError(null) + setSuccess(null) + const ok = await providers.disconnectProvider(providerID) + if (ok) { + if (selectedProvider() === providerID) { + setSelectedProvider(null) + setApiKey("") + setOauthPending(null) + setOauthCode("") + setCodeCopied(false) + } + setSuccess(`Disconnected ${getProviderDisplayName(providerID)}.`) + return + } + setError(`Failed to disconnect ${getProviderDisplayName(providerID)}. Please try again.`) + } + async function handleOAuthStart(providerID: string, methodIndex: number) { setError(null) setSuccess(null) @@ -1039,7 +1080,7 @@ Add your project-specific instructions here. {(providerID) => (
@@ -1050,9 +1091,31 @@ Add your project-specific instructions here. {getProviderDisplayName(providerID)}
- - Connected - +
+ + Connected + + + +
)}
@@ -1220,92 +1283,119 @@ Add your project-specific instructions here. {/* Search and Provider Selection */}
- {/* Search input */} -
- - setProviderSearch(e.currentTarget.value)} - placeholder="Search providers..." - class="w-full pl-9 pr-8 py-2 rounded-md text-sm" + +
- + > + Reconfiguring {selectedProviderName()} +
+
+ + {/* Search input */} + setProviderSearch("")} - class="absolute right-2 top-1/2 -translate-y-1/2 p-1" + onClick={() => setSelectedProvider(null)} + class="text-sm" style={{ color: "var(--text-weak)" }} > - + Choose a different provider - -
- - {/* Provider grid - max height with scroll */} -
- - {(provider) => ( + } + > +
+ + setProviderSearch(e.currentTarget.value)} + placeholder="Search providers..." + class="w-full pl-9 pr-8 py-2 rounded-md text-sm" + style={{ + background: "var(--background-base)", + border: "1px solid var(--border-base)", + color: "var(--text-base)", + }} + /> + - )} - -
+ +
- -

- No providers found matching "{providerSearch()}" -

-
+ {/* Provider grid - max height with scroll */} +
+ + {(provider) => ( + + )} + +
- !providers.connected.includes(p.id)).length === 0 && - !providerSearch() - } - > -

- All available providers are connected! -

+ +

+ No providers found matching "{providerSearch()}" +

+
+ + !providers.connected.includes(p.id)).length === 0 && + !providerSearch() + } + > +

+ All available providers are connected! +

+
@@ -1314,7 +1404,7 @@ Add your project-specific instructions here.
{/* Show auth method buttons */} @@ -1344,7 +1434,7 @@ Add your project-specific instructions here. color: "white", }} > - + Connecting... @@ -1388,7 +1478,7 @@ Add your project-specific instructions here. color: "white", }} > - + Connecting... @@ -2603,6 +2693,16 @@ Add your project-specific instructions here. onCancel={() => setMcpToDelete(null)} /> + setProviderToDelete(null)} + /> + {/* Prompt Add/Edit Dialog */} Date: Thu, 23 Apr 2026 21:39:05 +0000 Subject: [PATCH 2/6] fix: clear stale model selections on provider disconnect --- app-prefixable/src/context/providers.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app-prefixable/src/context/providers.tsx b/app-prefixable/src/context/providers.tsx index 1dc25921..d4f971c6 100644 --- a/app-prefixable/src/context/providers.tsx +++ b/app-prefixable/src/context/providers.tsx @@ -451,6 +451,18 @@ export function ProviderProvider(props: ParentProps) { async function disconnectProvider(providerID: string): Promise { try { await client.auth.remove({ providerID }) + setStore("modelsByAgent", produce((state) => { + for (const [agent, model] of Object.entries(state)) { + if (model.providerID !== providerID) continue + delete state[agent] + } + })) + setStore("sessionModels", produce((state) => { + for (const [sessionID, model] of Object.entries(state)) { + if (model.providerID !== providerID) continue + delete state[sessionID] + } + })) await client.instance.dispose() await refetchProviders() return true From a03025e6646958d4859b1ee7dd8265673acdd732 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 23 Apr 2026 21:43:56 +0000 Subject: [PATCH 3/6] fix: polish provider reconfigure feedback --- app-prefixable/src/pages/settings.tsx | 55 ++++++++++++++++++++------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/app-prefixable/src/pages/settings.tsx b/app-prefixable/src/pages/settings.tsx index 5ce0be18..0fddaff3 100644 --- a/app-prefixable/src/pages/settings.tsx +++ b/app-prefixable/src/pages/settings.tsx @@ -52,6 +52,8 @@ export function Settings() { const [mcpDeleting, setMcpDeleting] = createSignal(null) const [mcpToDelete, setMcpToDelete] = createSignal(null) const [providerToDelete, setProviderToDelete] = createSignal(null) + const [providerDeleting, setProviderDeleting] = createSignal(null) + const [providerDeleteError, setProviderDeleteError] = createSignal(null) // Saved prompts const savedPrompts = useSavedPrompts() @@ -224,6 +226,12 @@ export function Settings() { return getProviderDisplayName(id) }) + const selectedProviderConnected = createMemo(() => { + const id = selectedProvider() + if (!id) return false + return providers.connected.includes(id) + }) + // Popular providers shown first const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter"] @@ -592,6 +600,7 @@ Add your project-specific instructions here. } function startProviderEdit(providerID: string) { + if (providerDeleting()) return setError(null) setSuccess(null) setOauthPending(null) @@ -604,12 +613,15 @@ Add your project-specific instructions here. async function confirmProviderDelete() { const providerID = providerToDelete() - if (!providerID) return - setProviderToDelete(null) + if (!providerID || providerDeleting()) return + setProviderDeleting(providerID) + setProviderDeleteError(null) setError(null) setSuccess(null) const ok = await providers.disconnectProvider(providerID) + setProviderDeleting(null) if (ok) { + setProviderToDelete(null) if (selectedProvider() === providerID) { setSelectedProvider(null) setApiKey("") @@ -620,7 +632,7 @@ Add your project-specific instructions here. setSuccess(`Disconnected ${getProviderDisplayName(providerID)}.`) return } - setError(`Failed to disconnect ${getProviderDisplayName(providerID)}. Please try again.`) + setProviderDeleteError(`Failed to disconnect ${getProviderDisplayName(providerID)}. Please try again.`) } async function handleOAuthStart(providerID: string, methodIndex: number) { @@ -1098,8 +1110,12 @@ Add your project-specific instructions here.
@@ -1478,9 +1498,9 @@ Add your project-specific instructions here. color: "white", }} > - + - Connecting... + {selectedProviderConnected() ? "Updating..." : "Connecting..."} @@ -2697,10 +2717,17 @@ Add your project-specific instructions here. open={!!providerToDelete()} title="Disconnect Provider" message={`Are you sure you want to disconnect ${providerToDelete() ? getProviderDisplayName(providerToDelete()!) : "this provider"}?`} - confirmLabel="Disconnect" + confirmLabel={providerDeleting() ? "Disconnecting..." : "Disconnect"} + confirmDisabled={!!providerDeleting()} + cancelDisabled={!!providerDeleting()} variant="danger" + error={providerDeleteError()} onConfirm={confirmProviderDelete} - onCancel={() => setProviderToDelete(null)} + onCancel={() => { + if (providerDeleting()) return + setProviderToDelete(null) + setProviderDeleteError(null) + }} /> {/* Prompt Add/Edit Dialog */} From d91ccb7ae6b3bb577ad91a8901b433e2b9adeafe Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 23 Apr 2026 21:49:59 +0000 Subject: [PATCH 4/6] fix: treat provider disconnect as primary success --- app-prefixable/src/context/providers.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app-prefixable/src/context/providers.tsx b/app-prefixable/src/context/providers.tsx index d4f971c6..98bce5bc 100644 --- a/app-prefixable/src/context/providers.tsx +++ b/app-prefixable/src/context/providers.tsx @@ -463,8 +463,12 @@ export function ProviderProvider(props: ParentProps) { delete state[sessionID] } })) - await client.instance.dispose() - await refetchProviders() + try { + await client.instance.dispose() + await refetchProviders() + } catch (e) { + console.error("Failed to refresh providers after disconnect:", e) + } return true } catch (e) { console.error("Failed to disconnect provider:", e) From 617074fcaa7c6f23a54010a8cea312943de9b4a3 Mon Sep 17 00:00:00 2001 From: Christian Geier Date: Thu, 23 Apr 2026 21:56:01 +0000 Subject: [PATCH 5/6] fix: block provider action races --- app-prefixable/src/pages/settings.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app-prefixable/src/pages/settings.tsx b/app-prefixable/src/pages/settings.tsx index 0fddaff3..da4173d9 100644 --- a/app-prefixable/src/pages/settings.tsx +++ b/app-prefixable/src/pages/settings.tsx @@ -232,6 +232,8 @@ export function Settings() { return providers.connected.includes(id) }) + const providerActionBusy = createMemo(() => !!providerDeleting() || connecting() || !!oauthPending()) + // Popular providers shown first const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter"] @@ -600,7 +602,7 @@ Add your project-specific instructions here. } function startProviderEdit(providerID: string) { - if (providerDeleting()) return + if (providerActionBusy()) return setError(null) setSuccess(null) setOauthPending(null) @@ -1110,11 +1112,11 @@ Add your project-specific instructions here.