diff --git a/apps/desktop/src/app/settings/fallback-models-field.test.tsx b/apps/desktop/src/app/settings/fallback-models-field.test.tsx index 415ea4ba4..8abc71a65 100644 --- a/apps/desktop/src/app/settings/fallback-models-field.test.tsx +++ b/apps/desktop/src/app/settings/fallback-models-field.test.tsx @@ -43,6 +43,23 @@ async function renderField(value: unknown, onChange = vi.fn()) { return onChange } +async function renderFieldWithRerender(value: unknown, onChange = vi.fn()) { + const { FallbackModelsField } = await import('./fallback-models-field') + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + const view = render( + + + + ) + + return (next: unknown) => + view.rerender( + + + + ) +} + const CHAIN = [ { provider: 'copilot', model: 'gpt-5-mini' }, { provider: 'openai-codex', model: 'gpt-5.4-mini' } @@ -84,4 +101,13 @@ describe('FallbackModelsField', () => { expect(screen.getByText(/No fallback models/)).toBeTruthy() expect(screen.queryAllByLabelText('Remove')).toHaveLength(0) }) + + it('resyncs rows when persisted config changes', async () => { + const rerender = await renderFieldWithRerender(CHAIN) + expect(screen.getAllByLabelText('Remove')).toHaveLength(2) + + rerender([{ provider: 'nous', model: 'hermes-4' }]) + + await waitFor(() => expect(screen.getAllByLabelText('Remove')).toHaveLength(1)) + }) }) diff --git a/apps/desktop/src/app/settings/fallback-models-field.tsx b/apps/desktop/src/app/settings/fallback-models-field.tsx index b2b620115..d904d021f 100644 --- a/apps/desktop/src/app/settings/fallback-models-field.tsx +++ b/apps/desktop/src/app/settings/fallback-models-field.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' @@ -70,6 +70,12 @@ export function FallbackModelsField({ const [rows, setRows] = useState(() => normalizeEntries(value)) + // Settings can reload after a profile/config change while this component + // stays mounted. Avoid displaying or saving the previous profile's chain. + useEffect(() => { + setRows(normalizeEntries(value)) + }, [value]) + const commit = (next: FallbackEntry[]) => { setRows(next) onChange(next.filter(entry => entry.provider && entry.model))