fix(web): preserve draft Claude model options

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
2026-08-05 11:16:41 +08:00
co-authored by HAPI
parent 70185e3e67
commit 55746075e4
2 changed files with 75 additions and 16 deletions
+52 -2
View File
@@ -170,7 +170,11 @@ vi.mock('./ModelSelector', () => ({
isDisabled: boolean
isLoading?: boolean
onModelChange: (model: string) => void
}) => (
}) => {
const displayedModel = props.options && !props.options.some((option) => option.value === props.model)
? props.options[0]?.value ?? props.model
: props.model
return (
<>
<button
type="button"
@@ -178,11 +182,12 @@ vi.mock('./ModelSelector', () => ({
disabled={props.isDisabled || props.isLoading}
onClick={() => props.onModelChange('gpt-5.6-terra')}
>
{props.model}
{displayedModel}
</button>
<div data-testid="model-options">{props.options?.map((option) => option.label).join(',')}</div>
</>
)
}
}))
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(
<NewSession
api={claudeApi}
machines={[machine]}
initialMachineId="machine-1"
initialDirectory="C:\\repo"
onSuccess={mocks.onSuccess}
onCancel={() => {}}
/>
)
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]' })
))
})
})
+12 -3
View File
@@ -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