diff --git a/options/options.html b/options/options.html index cebdd4a..07f9e8d 100644 --- a/options/options.html +++ b/options/options.html @@ -1,3 +1,4 @@ + diff --git a/options/options.js b/options/options.js index 3afa0a7..bd244f7 100644 --- a/options/options.js +++ b/options/options.js @@ -14,11 +14,19 @@ let applying = false; function render(settings) { applying = true; - field("model").value = settings.model; - field("temporaryChat").checked = settings.temporaryChat; - field("webSearch").checked = settings.webSearch; - field("openIn").value = settings.openIn; - field("promptTemplate").value = settings.promptTemplate; + for (const [id, value] of Object.entries(settings)) { + const element = field(id); + // Never overwrite the control the user is actively editing — this page's + // own saves echo back through storage.onChanged (see below). + if (element === document.activeElement) { + continue; + } + if (typeof value === "boolean") { + element.checked = value; + } else { + element.value = value; + } + } applying = false; } @@ -69,12 +77,18 @@ for (const id of Object.keys(DEFAULT_SETTINGS)) { field("restore").addEventListener("click", () => { clearTimeout(saveTimer); + // Restoring defaults is an explicit user action, so it should reset every + // field even if one currently has focus. Blur first so render()'s + // activeElement guard (see below) has nothing to skip. + document.activeElement?.blur(); render(DEFAULT_SETTINGS); persist({ ...DEFAULT_SETTINGS }); }); -// A remote sync landing while the panel is open must update the controls rather -// than being clobbered by stale field values on the next edit. +// browser.storage.onChanged fires for every write to the "sync" area, not just +// a remote sync landing on another device — this page's own debounced +// autosaves and its Restore-defaults save re-enter this listener too, which +// render()'s activeElement guard makes safe to react to unconditionally. browser.storage.onChanged.addListener(async (changes, area) => { if (area !== "sync") { return;