From b7c09ee137dd30f24cb88af056bec57d9ea7d9b2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 12:42:23 +0000 Subject: [PATCH] fix custom provider edit gating and form reset on re-render Gate Edit/incomplete-auth on config-layer sources so catalog providers are not treated as editable custom overrides. Snapshot form initial values when Edit is clicked and ignore same-id initialValues identity changes so parent re-renders cannot wipe in-progress edits. Co-authored-by: Serhii Dziupin --- .../sections/providers/CustomProviderForm.tsx | 20 +++++++++---- .../sections/providers/ProvidersPage.tsx | 28 ++++++++++++------- .../providers/custom-provider-form.test.ts | 21 ++++++++++++++ .../providers/custom-provider-form.ts | 24 ++++++++++++++++ 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/components/sections/providers/CustomProviderForm.tsx b/packages/ui/src/components/sections/providers/CustomProviderForm.tsx index 1d805dd0..6fce317e 100644 --- a/packages/ui/src/components/sections/providers/CustomProviderForm.tsx +++ b/packages/ui/src/components/sections/providers/CustomProviderForm.tsx @@ -58,15 +58,23 @@ export const CustomProviderForm: React.FC = ({ const [err, setErr] = React.useState({}); const [modelErrors, setModelErrors] = React.useState([]); const [headerErrors, setHeaderErrors] = React.useState([]); + const seededEditProviderIdRef = React.useRef(null); React.useEffect(() => { - if (initialValues) { - setForm(initialValues); - setErr({}); - setModelErrors([]); - setHeaderErrors([]); + if (!initialValues) { + return; } - }, [initialValues]); + // Edit mode: seed once per provider id so parent re-renders (new object + // identity for the same snapshot) do not wipe in-progress edits. + if (isEdit && seededEditProviderIdRef.current === initialValues.providerID) { + return; + } + seededEditProviderIdRef.current = isEdit ? initialValues.providerID : null; + setForm(initialValues); + setErr({}); + setModelErrors([]); + setHeaderErrors([]); + }, [initialValues, isEdit]); const setField = (key: keyof Pick, value: string) => { setForm((prev) => ({ ...prev, [key]: value })); diff --git a/packages/ui/src/components/sections/providers/ProvidersPage.tsx b/packages/ui/src/components/sections/providers/ProvidersPage.tsx index f0eef612..a9a64fe7 100644 --- a/packages/ui/src/components/sections/providers/ProvidersPage.tsx +++ b/packages/ui/src/components/sections/providers/ProvidersPage.tsx @@ -31,8 +31,9 @@ import { buildAuthSetRequest, buildProviderUpsertRequest, CUSTOM_PROVIDER_ID, - isCustomOpenAICompatibleProvider, + isConfigDefinedCustomProvider, providerToCustomFormState, + type CustomProviderFormState, type CustomProviderPersistPlan, } from './custom-provider-form'; @@ -182,6 +183,7 @@ export const ProvidersPage: React.FC = () => { const [providerSources, setProviderSources] = React.useState>({}); const [showAuthPanel, setShowAuthPanel] = React.useState(false); const [editingCustomProviderId, setEditingCustomProviderId] = React.useState(null); + const [editingCustomFormInitial, setEditingCustomFormInitial] = React.useState(null); const [customAuthFailureHint, setCustomAuthFailureHint] = React.useState(null); const [lastCustomPersistId, setLastCustomPersistId] = React.useState(null); const isAddMode = selectedProviderId === ADD_PROVIDER_ID; @@ -303,6 +305,7 @@ export const ProvidersPage: React.FC = () => { if (selectedProviderId === ADD_PROVIDER_ID) { setShowAuthPanel(true); setEditingCustomProviderId(null); + setEditingCustomFormInitial(null); setCustomAuthFailureHint(null); return; } @@ -310,9 +313,10 @@ export const ProvidersPage: React.FC = () => { setShowAuthPanel(false); if (editingCustomProviderId && editingCustomProviderId !== selectedProviderId) { setEditingCustomProviderId(null); + setEditingCustomFormInitial(null); setCustomAuthFailureHint(null); } - }, [selectedProviderId, editingCustomProviderId, t]); + }, [selectedProviderId, editingCustomProviderId]); React.useEffect(() => { if (!selectedProviderId || selectedProviderId === ADD_PROVIDER_ID) { @@ -427,6 +431,7 @@ export const ProvidersPage: React.FC = () => { toast.success(t('settings.providers.page.toast.customProviderSaved', { provider: plan.name })); setCandidateProviderId(''); setEditingCustomProviderId(null); + setEditingCustomFormInitial(null); setCustomAuthFailureHint(null); setLastCustomPersistId(null); await reloadOpenCodeConfiguration({ scopes: ['providers'], mode: 'active' }); @@ -587,6 +592,7 @@ export const ProvidersPage: React.FC = () => { } await handleDisconnectProvider(providerId); setEditingCustomProviderId(null); + setEditingCustomFormInitial(null); setCustomAuthFailureHint(null); setLastCustomPersistId(null); setCandidateProviderId(''); @@ -902,15 +908,16 @@ export const ProvidersPage: React.FC = () => { const providerModels = Array.isArray(selectedProvider.models) ? selectedProvider.models : []; const providerAuthMethods = authMethodsByProvider[selectedProvider.id] ?? []; const oauthAuthMethods = providerAuthMethods.filter((method) => normalizeAuthType(method) === 'oauth'); - const isCustomProvider = isCustomOpenAICompatibleProvider(selectedProvider); + const sourcesLoaded = Boolean(selectedSources); + const isEditableCustomProvider = sourcesLoaded + && isConfigDefinedCustomProvider(selectedProvider, selectedSources); const providerEnv = Array.isArray(selectedProvider.env) ? selectedProvider.env.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0) : []; - const sourcesLoaded = Boolean(selectedSources); const hasStoredAuth = Boolean(selectedSources?.auth.exists); const hasEnvCredentials = providerEnv.length > 0; const hasCredentials = hasStoredAuth || hasEnvCredentials; - const authStatusIncomplete = isCustomProvider && sourcesLoaded && !hasCredentials; + const authStatusIncomplete = isEditableCustomProvider && !hasCredentials; const filteredModels = providerModels.filter((model) => { const name = typeof model?.name === 'string' ? model.name : ''; @@ -920,8 +927,7 @@ export const ProvidersPage: React.FC = () => { return name.toLowerCase().includes(query) || id.toLowerCase().includes(query); }); - if (isCustomEditMode && isCustomProvider) { - const initialValues = providerToCustomFormState(selectedProvider); + if (isCustomEditMode && isEditableCustomProvider && editingCustomFormInitial) { return ( { { setEditingCustomProviderId(null); + setEditingCustomFormInitial(null); setCustomAuthFailureHint(null); setLastCustomPersistId(null); }} @@ -960,13 +967,14 @@ export const ProvidersPage: React.FC = () => { divider={false} headerAction={(
- {isCustomProvider ? ( + {isEditableCustomProvider ? (