mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site/src/pages/AISettingsPage/ModelsPage): disable Update model until form is dirty (#26684)
The Update model button on `/ai/settings/models/:modelId` was enabled on mount even when the form had not been edited, so it was possible to submit an unchanged update. This matches the Provider form behavior already established in #25551 by gating submit on `form.dirty` when editing. ### Changes - `ModelForm.tsx` adds `(!isEditing || form.dirty)` to the `canSubmit` predicate so Update is disabled until the user changes a field. Add/duplicate flows are unaffected because their existing `model.trim().length > 0` requirement already enforces user input. - `ModelForm.stories.tsx` tightens `EditSaveSubmits` to assert the disabled-on-mount and enabled-after-edit transitions, and adds `EditUpdateDisabledUntilDirty` covering the case where the user reverts an edit back to the original value. ### Verification - `pnpm exec biome check src/pages/AISettingsPage/ModelsPage/components/ModelForm.tsx src/pages/AISettingsPage/ModelsPage/components/ModelForm.stories.tsx`: clean - `pnpm exec tsc -p . --noEmit`: clean - `pnpm test:storybook --project=chromium src/pages/AISettingsPage/ModelsPage`: 21/21 stories pass (including the two new dirty-state stories) - `make pre-commit` via the project git hooks: passed (lint/ts, lint/go, lint/emdash, lint/agents, lint/check-scopes, build, all green) > [!NOTE] > 🤖 This PR was written by Coder Agents on behalf of @tracyjohnsonux
This commit is contained in:
@@ -183,11 +183,33 @@ export const EditSaveSubmits: Story = {
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const save = canvas.getByRole("button", { name: /^update model$/i });
|
||||
// Update is disabled until the user makes a change.
|
||||
await expect(save).toBeDisabled();
|
||||
await userEvent.type(canvas.getByLabelText(/display name/i), " (updated)");
|
||||
await expect(save).toBeEnabled();
|
||||
await userEvent.click(save);
|
||||
await expect(args.onUpdateModel).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
};
|
||||
|
||||
export const EditUpdateDisabledUntilDirty: Story = {
|
||||
args: {
|
||||
editingModel: mockGPT5,
|
||||
onDeleteModel: fn(async () => undefined),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const save = canvas.getByRole("button", { name: /^update model$/i });
|
||||
await expect(save).toBeDisabled();
|
||||
const displayName = canvas.getByLabelText(/display name/i);
|
||||
await userEvent.type(displayName, " (edited)");
|
||||
await expect(save).toBeEnabled();
|
||||
await userEvent.clear(displayName);
|
||||
await userEvent.type(displayName, mockGPT5.display_name);
|
||||
await expect(save).toBeDisabled();
|
||||
},
|
||||
};
|
||||
|
||||
export const CostTrackingExpanded: Story = {
|
||||
args: {
|
||||
editingModel: mockGPT5,
|
||||
|
||||
@@ -235,7 +235,8 @@ export const ModelForm: FC<ModelFormProps> = ({
|
||||
!hasFieldErrors &&
|
||||
form.values.model.trim().length > 0 &&
|
||||
contextLimitValid &&
|
||||
compressionThresholdValid;
|
||||
compressionThresholdValid &&
|
||||
(!isEditing || form.dirty);
|
||||
|
||||
const handleConfirmReplaceDefault = () => {
|
||||
replaceDefaultConfirmedRef.current = true;
|
||||
|
||||
Reference in New Issue
Block a user