diff --git a/docs/superpowers/plans/2026-07-30-extension-options-panel.md b/docs/superpowers/plans/2026-07-30-extension-options-panel.md index 3881717..aa62d23 100644 --- a/docs/superpowers/plans/2026-07-30-extension-options-panel.md +++ b/docs/superpowers/plans/2026-07-30-extension-options-panel.md @@ -843,6 +843,7 @@ The panel renders **inline inside `about:addons`**, which is a narrow, constrain The element `id`s must match the keys in `DEFAULT_SETTINGS` exactly — `options.js` relies on that. ```html + @@ -1054,13 +1055,25 @@ let statusTimer = null; // values into the form, so a remote sync cannot trigger a save loop. let applying = false; +// Corrected during execution: the activeElement guard is required because +// storage.onChanged fires for this page's OWN writes, so every debounced +// autosave re-entered the listener below and could overwrite whatever the user +// had typed since the save started. 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; } @@ -1111,12 +1124,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;