From 55482166450869de4cab33e818184b3d2d15f444 Mon Sep 17 00:00:00 2001 From: golem Date: Sat, 1 Aug 2026 22:51:15 -0600 Subject: [PATCH] doc: sync plan options-panel code with the shipped fixes Records the doctype, the render() activeElement guard, and the blur in the Restore handler. storage.onChanged fires for this page's own writes, so every autosave re-entered the listener and could overwrite in-progress typing; the guard closes that. --- .../2026-07-30-extension-options-panel.md | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) 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;