From 73c3b15a16d82f12cbc3f2ec16d9b4502a7d56e4 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sat, 11 Jul 2026 03:22:26 +0800 Subject: [PATCH] fix(auth-files): save pending custom excluded rule --- src/features/authFiles/oauthExcludedRules.ts | 7 +++++ src/pages/AuthFilesOAuthExcludedEditPage.tsx | 29 ++++++++++---------- tests/oauthExcludedRules.test.ts | 13 +++++++++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/features/authFiles/oauthExcludedRules.ts b/src/features/authFiles/oauthExcludedRules.ts index 363ddf1a..01f1fbbf 100644 --- a/src/features/authFiles/oauthExcludedRules.ts +++ b/src/features/authFiles/oauthExcludedRules.ts @@ -15,6 +15,13 @@ export function normalizeOAuthExcludedRules(values: Iterable): string[] return rules; } +export function getEffectiveOAuthExcludedRules( + selectedRules: Iterable, + customRule: string +): string[] { + return normalizeOAuthExcludedRules([...selectedRules, customRule]); +} + export function hasOAuthExcludedRule(values: Iterable, candidate: string): boolean { const candidateKey = getRuleKey(candidate); if (!candidateKey) return false; diff --git a/src/pages/AuthFilesOAuthExcludedEditPage.tsx b/src/pages/AuthFilesOAuthExcludedEditPage.tsx index ef86e05a..b63c1682 100644 --- a/src/pages/AuthFilesOAuthExcludedEditPage.tsx +++ b/src/pages/AuthFilesOAuthExcludedEditPage.tsx @@ -21,6 +21,7 @@ import { import { getStringSetSignature, isOAuthEditorDirty } from '@/features/authFiles/oauthEditorState'; import { getCustomOAuthExcludedRules, + getEffectiveOAuthExcludedRules, hasOAuthExcludedRule, normalizeOAuthExcludedRules, updateOAuthExcludedRule, @@ -87,11 +88,15 @@ export function AuthFilesOAuthExcludedEditPage() { () => getStringSetSignature(normalizeOAuthExcludedRules(excluded[resolvedProviderKey] ?? [])), [excluded, resolvedProviderKey] ); - const selectedModelsSignature = useMemo( - () => getStringSetSignature(normalizeOAuthExcludedRules(selectedModels)), - [selectedModels] + const effectiveRules = useMemo( + () => getEffectiveOAuthExcludedRules(selectedModels, customRule), + [customRule, selectedModels] ); - const contentDirty = baselineModelsSignature !== selectedModelsSignature; + const effectiveRulesSignature = useMemo( + () => getStringSetSignature(effectiveRules), + [effectiveRules] + ); + const contentDirty = baselineModelsSignature !== effectiveRulesSignature; const customRules = useMemo( () => getCustomOAuthExcludedRules( @@ -104,7 +109,7 @@ export function AuthFilesOAuthExcludedEditPage() { initialProviderKey, provider, baselineModelsSignature, - selectedModelsSignature + effectiveRulesSignature ); const unsavedChangesDialog = useMemo( () => ({ @@ -298,14 +303,10 @@ export function AuthFilesOAuthExcludedEditPage() { }, []); const handleAddCustomRule = useCallback(() => { - const nextRule = customRule.trim(); - if (!nextRule) return; - setSelectedModels((prev) => { - if (hasOAuthExcludedRule(prev, nextRule)) return prev; - return new Set(updateOAuthExcludedRule(prev, nextRule, true)); - }); + if (!customRule.trim()) return; + setSelectedModels(new Set(effectiveRules)); setCustomRule(''); - }, [customRule]); + }, [customRule, effectiveRules]); const handleSave = useCallback(async () => { const normalizedProvider = normalizeProviderKey(provider); @@ -314,7 +315,7 @@ export function AuthFilesOAuthExcludedEditPage() { return; } - const models = normalizeOAuthExcludedRules(selectedModels); + const models = effectiveRules; setSaving(true); try { if (models.length) { @@ -331,7 +332,7 @@ export function AuthFilesOAuthExcludedEditPage() { } finally { setSaving(false); } - }, [allowNextNavigation, handleBack, isEditing, provider, selectedModels, showNotification, t]); + }, [allowNextNavigation, effectiveRules, handleBack, isEditing, provider, showNotification, t]); const canSave = !disableControls && !saving && !excludedUnsupported; diff --git a/tests/oauthExcludedRules.test.ts b/tests/oauthExcludedRules.test.ts index ff105309..310ffe99 100644 --- a/tests/oauthExcludedRules.test.ts +++ b/tests/oauthExcludedRules.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from 'bun:test'; import { getCustomOAuthExcludedRules, + getEffectiveOAuthExcludedRules, hasOAuthExcludedRule, normalizeOAuthExcludedRules, updateOAuthExcludedRule, @@ -28,4 +29,16 @@ describe('OAuth excluded rules', () => { ) ).toEqual(['gpt-*', 'retired-model']); }); + + test('includes a pending custom rule in the effective rules', () => { + expect(getEffectiveOAuthExcludedRules(['gpt-4o'], ' gpt-* ')).toEqual(['gpt-4o', 'gpt-*']); + }); + + test('ignores a blank pending custom rule', () => { + expect(getEffectiveOAuthExcludedRules(['gpt-4o'], ' ')).toEqual(['gpt-4o']); + }); + + test('keeps the original spelling for a case-insensitive duplicate', () => { + expect(getEffectiveOAuthExcludedRules(['GPT-4o'], 'gpt-4O')).toEqual(['GPT-4o']); + }); });