From 157e04ed244c673abaea0306dd1badba3c998a6d Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Sat, 22 Aug 2026 22:22:22 -0400 Subject: [PATCH] Skip no-op saves in the edit dialogs; clear a stale model-spec error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Save that changed nothing still sent a PATCH, which bumped the row's version and landed an "Edited garden settings" step in History that undid nothing — the drift is gone since the last commit, but the write was still there. Both dialogs now close without a request when every field matches the loaded row. In Settings, a rejected model spec's reason stayed under the field after the field was blanked back to the saved value; committing an unchanged value now clears it. Co-Authored-By: Claude Fable 5 --- web/src/components/gardens/GardenDialog.tsx | 7 +++++++ web/src/components/plants/PlantDialog.tsx | 6 ++++++ web/src/pages/SettingsPage.tsx | 3 +++ 3 files changed, 16 insertions(+) diff --git a/web/src/components/gardens/GardenDialog.tsx b/web/src/components/gardens/GardenDialog.tsx index 2d81e3e..12a0f49 100644 --- a/web/src/components/gardens/GardenDialog.tsx +++ b/web/src/components/gardens/GardenDialog.tsx @@ -98,6 +98,13 @@ export function GardenDialog({ garden, onClose }: { garden?: Garden; onClose: () return } const input = { name: name.trim(), widthCm, heightCm, unitPref: unit, notes: notes.trim(), gridSizeCm, snapToGrid } + // Nothing changed: close without a request. A PATCH that writes the same + // row still bumps the version and lands an "Edited garden settings" step + // in History that undoes nothing. + if (isEdit && (Object.keys(input) as (keyof typeof input)[]).every((k) => input[k] === garden[k])) { + onClose() + return + } try { if (isEdit) await update.mutateAsync({ id: garden.id, ...input, version }) else await create.mutateAsync(input) diff --git a/web/src/components/plants/PlantDialog.tsx b/web/src/components/plants/PlantDialog.tsx index 4cbfe8e..2adc3fa 100644 --- a/web/src/components/plants/PlantDialog.tsx +++ b/web/src/components/plants/PlantDialog.tsx @@ -99,6 +99,12 @@ export function PlantDialog({ vendor: vendor.trim(), notes: notes.trim(), } + // Nothing changed: close without a request, so a look-and-Save doesn't bump + // the version for every garden that shares the plant. + if (isEdit && (Object.keys(input) as (keyof PlantInput)[]).every((k) => input[k] === (k === 'color' ? expandHex(plant.color) : plant[k]))) { + onClose() + return + } try { if (isEdit) await update.mutateAsync({ id: plant.id, ...input, version }) else await create.mutateAsync(input) diff --git a/web/src/pages/SettingsPage.tsx b/web/src/pages/SettingsPage.tsx index 31c693c..0edc4fc 100644 --- a/web/src/pages/SettingsPage.tsx +++ b/web/src/pages/SettingsPage.tsx @@ -171,6 +171,9 @@ function AssistantCard({ data }: { data: SettingsResponse }) { const commit = (field: ModelField, value: string) => { const v = value.trim() if (v !== settings[field]) save({ [field]: v }) + // Back to what's saved (the typo was cleared): nothing to send, and the + // old reason would be about a value no longer in the field. + else setFieldError((e) => (e?.field === field ? null : e)) } const errorFor = (field: ModelField) => fieldError?.field === field ? {fieldError.message} : null