fix(agents): preserve unapplied Claude model drafts across navigation

This commit is contained in:
lzt404 committed 2026-08-11 18:18:23 +08:00
1 parent 476d6e6769
commit 7b192cc6a4
2 files changed
+50 -19

No files matched your search

+38 -8
View File
@@ -141,6 +141,10 @@ const createClaudeBooleanByClient = (): Record<ClaudeModelMappingClientId, boole
'claude-desktop': false,
});
let claudeModelMappingsDraftCache = createClaudeModelMappingsByClient();
let claudeCustomMappingCache = createClaudeBooleanByClient();
const claudeModelMappingsDirtyCache = createClaudeBooleanByClient();
const claudeMappingRoles = [
{
key: 'opus',
@@ -563,11 +567,14 @@ export function AgentsPage() {
const [modelByClient, setModelByClient] = useState<Partial<Record<AgentClientId, string>>>(
readAgentModelSelections,
);
const [claudeModelMappingsDraftByClient, setClaudeModelMappingsDraftByClient] = useState(
createClaudeModelMappingsByClient,
const [claudeModelMappingsDraftByClient, setClaudeModelMappingsDraftByClientState] = useState(
() => ({
'claude-code': { ...claudeModelMappingsDraftCache['claude-code'] },
'claude-desktop': { ...claudeModelMappingsDraftCache['claude-desktop'] },
}),
);
const [claudeCustomMappingByClient, setClaudeCustomMappingByClient] = useState(
createClaudeBooleanByClient,
const [claudeCustomMappingByClient, setClaudeCustomMappingByClientState] = useState(
() => ({ ...claudeCustomMappingCache }),
);
const [loading, setLoading] = useState(true);
const [modelLoading, setModelLoading] = useState(false);
@@ -589,7 +596,31 @@ export function AgentsPage() {
const [piProviderUpdateStatus, setPiProviderUpdateStatus] = useState<PiProviderUpdateStatus | null>(null);
const modelRequestRef = useRef(0);
const piUpdateRequestRef = useRef(0);
const claudeModelMappingsDirtyRef = useRef(createClaudeBooleanByClient());
const claudeModelMappingsDirtyRef = useRef(claudeModelMappingsDirtyCache);
const setClaudeModelMappingsDraftByClient = useCallback((
update: (
current: Record<ClaudeModelMappingClientId, ClaudeModelMappings>,
) => Record<ClaudeModelMappingClientId, ClaudeModelMappings>,
) => {
setClaudeModelMappingsDraftByClientState((current) => {
const next = update(current);
claudeModelMappingsDraftCache = next;
return next;
});
}, []);
const setClaudeCustomMappingByClient = useCallback((
update: (
current: Record<ClaudeModelMappingClientId, boolean>,
) => Record<ClaudeModelMappingClientId, boolean>,
) => {
setClaudeCustomMappingByClientState((current) => {
const next = update(current);
claudeCustomMappingCache = next;
return next;
});
}, []);
const loadStatuses = useCallback(async (forceRefresh = false) => {
const command = forceRefresh
@@ -685,9 +716,8 @@ export function AgentsPage() {
setClearConfirmOpen(false);
setOauthLoginRequiredAction(null);
setOauthConfigurationDraft(null);
if (selected === 'claude-code' || selected === 'claude-desktop') {
claudeModelMappingsDirtyRef.current[selected] = false;
}
// Preserve each Claude client's unsaved draft while navigating between clients.
// Its dirty flag is cleared only after a successful apply, close, or reset action.
}, [selected]);
const activeDefinition = agentDefinitions.find((agent) => agent.id === selected)
+12 -11
View File
@@ -100,7 +100,7 @@ const appliedConfiguration = (
});
describe('agent configuration update action', () => {
test('Claude mapping clients do not inherit another client draft', () => {
test('Claude mapping clients keep their own unsaved draft after switching away and back', () => {
const codeDraft = { opus: 'code-opus', sonnet: 'code-sonnet', haiku: 'code-haiku' };
const desktopDraft = {
opus: 'desktop-opus',
@@ -116,26 +116,27 @@ describe('agent configuration update action', () => {
'claude-code': codeDraft,
'claude-desktop': desktopDraft,
};
const dirtyByClient = {
'claude-code': true,
'claude-desktop': false,
};
// Switch from Claude Code to Desktop: Desktop still loads its own default.
expect(resolveAgentModelMappingsDraftSourceForClient(
drafts,
'claude-desktop',
null,
desktopFallback,
false,
dirtyByClient['claude-desktop'],
)).toEqual(desktopFallback);
expect(resolveAgentModelMappingsDraftSourceForClient(
drafts,
'claude-desktop',
null,
desktopFallback,
true,
)).toEqual(desktopDraft);
// Switch back to Claude Code: its unapplied selection must not be overwritten.
expect(resolveAgentModelMappingsDraftSourceForClient(
drafts,
'claude-code',
null,
{ opus: 'applied', sonnet: 'applied', haiku: 'applied' },
desktopFallback,
true,
dirtyByClient['claude-code'],
)).toEqual(codeDraft);
});