diff --git a/web/src/components/NewSession/index.test.tsx b/web/src/components/NewSession/index.test.tsx index 4f244ef0..ef29fb1d 100644 --- a/web/src/components/NewSession/index.test.tsx +++ b/web/src/components/NewSession/index.test.tsx @@ -170,19 +170,24 @@ vi.mock('./ModelSelector', () => ({ isDisabled: boolean isLoading?: boolean onModelChange: (model: string) => void - }) => ( - <> - -
{props.options?.map((option) => option.label).join(',')}
- - ) + }) => { + const displayedModel = props.options && !props.options.some((option) => option.value === props.model) + ? props.options[0]?.value ?? props.model + : props.model + return ( + <> + +
{props.options?.map((option) => option.label).join(',')}
+ + ) + } })) vi.mock('./ReasoningEffortSelector', () => ({ ReasoningEffortSelector: (props: { value: string; onChange: (effort: string) => void }) => ( @@ -631,4 +636,49 @@ describe('NewSession launch preferences', () => { expect(screen.getByTestId('reasoning')).toHaveTextContent('max') }) }) + + it('keeps a browse-return custom Claude model visible when it is no longer configured', async () => { + savePreferredAgent('claude') + saveNewSessionFormDraft({ + agent: 'claude', + model: 'deepseek-v4-flash[1m]', + cursorSelectedBase: 'auto', + machineId: 'machine-1', + effort: 'high', + modelReasoningEffort: 'default', + serviceTier: 'standard', + collaborationMode: 'default', + copilotAgentMode: 'interactive', + yoloMode: false, + codexFamilyPermissionMode: 'default', + grokPermissionMode: 'default', + sessionType: 'simple', + worktreeName: '' + }) + mocks.spawnSession.mockResolvedValue({ type: 'success', sessionId: 'session-1' }) + const claudeApi = { + getClaudeCustomModels: vi.fn().mockResolvedValue({ models: [] }) + } as unknown as ApiClient + + render( + {}} + /> + ) + + await waitFor(() => { + expect(screen.getByTestId('model')).toHaveTextContent('deepseek-v4-flash[1m]') + expect(screen.getByTestId('create')).toBeEnabled() + }) + fireEvent.click(screen.getByTestId('create')) + + await waitFor(() => expect(mocks.spawnSession).toHaveBeenCalledWith( + expect.objectContaining({ model: 'deepseek-v4-flash[1m]' }) + )) + }) }) diff --git a/web/src/components/NewSession/index.tsx b/web/src/components/NewSession/index.tsx index 0213b8fe..ed5734a4 100644 --- a/web/src/components/NewSession/index.tsx +++ b/web/src/components/NewSession/index.tsx @@ -302,11 +302,20 @@ export function NewSession(props: { options.push({ value: modelName, label: modelName }) } } + if ( + agent === 'claude' + && model !== 'auto' + && !options.some((option) => option.value === model) + ) { + options.splice(1, 0, { value: model, label: model }) + } return options - }, [claudeCustomModels]) + }, [agent, claudeCustomModels, model]) const claudePreferredModelValues = useMemo( - () => agent === 'claude' ? claudeModelOptions.map((option) => option.value) : null, - [agent, claudeModelOptions] + () => agent === 'claude' + ? [...MODEL_OPTIONS.claude.map((option) => option.value), ...claudeCustomModels] + : null, + [agent, claudeCustomModels] ) const claudeModelsLoading = agent === 'claude' && !claudeModelsLoaded const preferredModelCatalogReady = !claudeModelsLoading