Lock the AI assistant form while a prompt reset is pending
Closing the Reset sheet while the reset request was in flight left the form editable, so a prompt edit made then was replaced by the reset response and the leave guard could report the form as saved. The form's fieldset is now disabled while a prompt reset is pending as well as during a save, so no edit can be made that the reset response would overwrite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -305,8 +305,9 @@
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* A form locked while its save is pending, so no edit is made that the
|
||||
saved settings would then replace. It lays out as if it were not there. */
|
||||
/* A form locked while its save or prompt reset is pending, so no edit is
|
||||
made that the response would then replace. It lays out as if it were not
|
||||
there. */
|
||||
.lock {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
@@ -431,6 +431,39 @@ describe('AISettingsPage', () => {
|
||||
await waitFor(() => expect(unload()).toBe(false))
|
||||
})
|
||||
|
||||
it('locks the form while a prompt reset is pending, even with the sheet closed', async () => {
|
||||
let answer: (response: Response) => void = () => {}
|
||||
stubAPI({
|
||||
'POST /api/v1/settings/ai/reset-prompt': () => new Promise<Response>((resolve) => (answer = resolve)),
|
||||
})
|
||||
await renderPage()
|
||||
const unload = () => {
|
||||
const event = new Event('beforeunload', { cancelable: true })
|
||||
act(() => void window.dispatchEvent(event))
|
||||
return event.defaultPrevented
|
||||
}
|
||||
fireEvent.change(slider(), { target: { value: '0.2' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Reset' }))
|
||||
const confirm = screen.getByRole('dialog', { name: 'Reset the system prompt?' })
|
||||
fireEvent.click(within(confirm).getByRole('button', { name: 'Reset prompt' }))
|
||||
expect(await within(confirm).findByRole('button', { name: 'Resetting…' })).toBeDisabled()
|
||||
fireEvent.click(within(confirm).getByRole('button', { name: 'Cancel' }))
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
|
||||
for (const field of [provider(), model(), slider(), prompt()]) expect(field).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: 'Reset' })).toBeDisabled()
|
||||
expect(save()).toBeDisabled()
|
||||
expect(unload()).toBe(true)
|
||||
|
||||
answer(jsonResponse(200, { ...AI_SETTINGS, system_prompt: DEFAULT_PROMPT }))
|
||||
await waitFor(() => expect(prompt()).toBeEnabled())
|
||||
expect(prompt()).toHaveValue(DEFAULT_PROMPT)
|
||||
expect(slider()).toHaveValue('0.2')
|
||||
expect(unload()).toBe(true)
|
||||
fireEvent.change(prompt(), { target: { value: 'After the reset.' } })
|
||||
expect(prompt()).toHaveValue('After the reset.')
|
||||
})
|
||||
|
||||
it('keeps 44 px tap targets', async () => {
|
||||
stubAPI()
|
||||
await renderPage()
|
||||
|
||||
@@ -201,7 +201,7 @@ function AISettingsForm({ saved, onDirtyChange }: AISettingsFormProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<fieldset className={styles.lock} disabled={save.isPending}>
|
||||
<fieldset className={styles.lock} disabled={save.isPending || resetPrompt.isPending}>
|
||||
<div className={styles.columns}>
|
||||
<Select
|
||||
label="Provider"
|
||||
|
||||
Reference in New Issue
Block a user