@@ -98,7 +98,25 @@ const confirmEl = ref(null)
const pendingMessages = ref([])
const pendingInfoOnly = ref(false)
-const dirtyKeys = computed(() => Object.keys(dirty.value))
+// A field is dirty if the user changed it through a control (tracked in the
+// `dirty` ref by onChange) OR its working value diverges from the last-saved
+// original. The latter catches values written onto `state.working` from OUTSIDE
+// a control — notably the instructions prefill in Settings.vue (MCP-2484).
+// Without it, "Save without editing" after a prefill would PATCH nothing because
+// the field was never marked dirty.
+const dirtyKeys = computed(() => {
+ const keys = new Set(Object.keys(dirty.value))
+ for (const f of props.fields) {
+ if (!eq(getPath(props.working, f.key), getPath(props.original, f.key))) keys.add(f.key)
+ }
+ return [...keys]
+})
+
+function isFieldDirty(key: string): boolean {
+ if (key in dirty.value) return true
+ const f = props.fields.find((x) => x.key === key)
+ return f != null && !eq(getPath(props.working, key), getPath(props.original, key))
+}
// Block Save only when a CHANGED field is invalid — a pre-existing value the
// user hasn't touched must never block saving unrelated edits.
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue
index c841626c..c2d24661 100644
--- a/frontend/src/views/Settings.vue
+++ b/frontend/src/views/Settings.vue
@@ -174,7 +174,7 @@