Fix model preset reset flow

Add a confirmed Reset to default action to the preset editor and refresh its draft after resets from either entry point. Resolve the toast helper at action time so resetting model settings cannot abort before removing the preset override. Rename the generic reset action for clarity and add regression coverage.

Alessandro committed Jul 13, 2026 at 14:46 UTC 48cba46181e503bc4745520a2b4849a1e9a7aa77
7 files changed +45 -5
plugins/_model_config/AGENTS.md
+1
@@ -22,6 +22,7 @@
22 - Changing a model provider in the settings UI must clear `api_base` and `kwargs` because both may be provider-specific.
23 - Repair provider-specific model-config aliases at the model-config read/build boundary; keep provider-specific repairs out of provider-agnostic core wrappers such as `models.py`.
24 - `modelConfig.createPresetEditor()` owns local preset drafts, row actions, and stable UI-only row keys so deletion or renaming cannot rebind nested model fields.
25 +- Preset editor reset actions must remove the user override through the preset API and refresh the open draft from bundled defaults.
26 - Model-name catalogs open below the input from either a field click or the embedded magnifier.
27
28 ## Work Guidance
plugins/_model_config/webui/main.html
+5
@@ -68,6 +68,7 @@
68 <button class="text-button" @click="(async () => {
69 await import('/components/plugins/plugin-settings-store.js');
70 await $store.pluginSettingsPrototype.openConfig('_model_config');
71 + refreshPresets();
72 })()">
73 <span class="material-symbols-outlined">settings</span>
74 <span>Settings</span>
@@ -79,6 +80,9 @@
80 </div>
81
82 <div class="presets-footer">
83 + <button class="button" @click="$confirmClick($event, () => resetPresets())">
84 + <span class="icon material-symbols-outlined">restart_alt</span> Reset to default
85 + </button>
86 <button class="button" @click="savePresets()">
87 <span class="icon material-symbols-outlined">save</span> Save Presets
88 </button>
@@ -100,6 +104,7 @@
104 margin-top: 12px;
105 display: flex;
106 justify-content: flex-end;
107 + gap: 8px;
108 }
109 .preset-card {
110 border: 1px solid var(--color-border);
plugins/_model_config/webui/model-config-store.js
+14
@@ -294,6 +294,17 @@ export const store = createStore("modelConfig", {
294 await store.persistAllDirtyApiKeys();
295 await store.saveGlobalPresets(this.presets);
296 },
297 + refreshPresets() {
298 + this.presets = clonePlain(store.globalPresets).map(preset => ({
299 + ...preset,
300 + _key: nextPresetKey++,
301 + }));
302 + },
303 + async resetPresets() {
304 + if (!await store.resetGlobalPresets()) return;
305 + this.refreshPresets();
306 + globalThis.justToast?.('Presets reset to default.', 'info');
307 + },
308 };
309 },
310
@@ -355,8 +366,11 @@ export const store = createStore("modelConfig", {
366 this.globalPresets = this._normalizePresets(data.presets);
367 this.switcherPresets = this.globalPresets.filter(p => p.name);
368 this._presetsLoaded = true;
369 + return true;
370 } catch (e) {
371 console.error('Failed to reset presets:', e);
372 + globalThis.justToast?.('Failed to reset presets', 'error');
373 + return false;
374 }
375 },
376
tests/test_model_config_ui.py
+21
@@ -34,3 +34,24 @@ def test_model_preset_rows_keep_stable_identity_after_middle_delete() -> None:
34 assert ':key="idx"' not in preset_modal
35 assert "JSON.parse(JSON.stringify" not in preset_modal
36 assert "presets.splice" not in preset_modal
37 +
38 +
39 +def test_model_preset_editor_can_reset_to_bundled_defaults() -> None:
40 + preset_modal = read("plugins", "_model_config", "webui", "main.html")
41 + preset_store = read("plugins", "_model_config", "webui", "model-config-store.js")
42 +
43 + assert '$confirmClick($event, () => resetPresets())' in preset_modal
44 + assert "Reset to default" in preset_modal
45 + assert "async resetPresets()" in preset_store
46 + assert "if (!await store.resetGlobalPresets()) return;" in preset_store
47 + assert "this.refreshPresets();" in preset_store
48 + assert "refreshPresets();" in preset_modal
49 +
50 +
51 +def test_plugin_settings_reset_is_explicit_and_does_not_capture_toast_early() -> None:
52 + settings_modal = read("webui", "components", "plugins", "plugin-settings.html")
53 + settings_store = read("webui", "components", "plugins", "plugin-settings-store.js")
54 +
55 + assert "Reset to default" in settings_modal
56 + assert "const justToast = globalThis.justToast" not in settings_store
57 + assert 'globalThis.justToast?.("Settings reset to default.", "info")' in settings_store
webui/components/plugins/AGENTS.md
+1
@@ -16,6 +16,7 @@
16 - Keep plugin settings modals bound through `$store.pluginSettingsPrototype` conventions.
17 - Preserve global and scoped toggle semantics using `.toggle-1` and `.toggle-0`.
18 - Use notification helpers for plugin UI feedback.
19 +- Label reset actions `Reset to default` and resolve notification globals when the action runs so late WebUI initialization cannot abort the reset lifecycle.
20
21 ## Work Guidance
22
webui/components/plugins/plugin-settings-store.js
+2 -4
@@ -4,8 +4,6 @@ import { fetchApi } from "/js/api.js";
4 import { showConfirmDialog } from "/js/confirmDialog.js";
5 import { store as pluginToggleStore } from "/components/plugins/toggle/plugin-toggle-store.js";
6
7 -const justToast = globalThis.justToast;
8 -
7 const model = {
8 // which plugin this modal is showing
9 pluginName: null,
@@ -359,7 +357,7 @@ const model = {
357 async resetToDefault() {
358 if (!this.pluginName) return;
359 const confirmed = await showConfirmDialog({
362 - title: "Reset to Default",
360 + title: "Reset to default",
361 message: "This will replace the current settings with the plugin defaults. Any unsaved changes will be lost.",
362 confirmText: "Reset",
363 type: "warning",
@@ -373,7 +371,7 @@ const model = {
371 const result = await response.json().catch(() => ({}));
372 if (result.ok) {
373 this.settings = result.data || {};
376 - justToast("Settings reset to default.", "info");
374 + globalThis.justToast?.("Settings reset to default.", "info");
375 }
376 },
377
webui/components/plugins/plugin-settings.html
+1 -1
@@ -139,7 +139,7 @@
139 x-show="!context.hideSettingsActions"
140 @click="context.resetToDefault()"
141 :disabled="context?.isSaving || context?.isLoading">
142 - Default
142 + Reset to default
143 </button>
144 <button class="btn btn-field"
145 x-show="context.wizardFooter?.showNext?.()"