ae1591d81f00

fix: popup steadiness — will-change top/left, track modal internal scroll, dismiss when anchor leaves DOM (v3.6.152)

time
2026-06-03 14:15 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
inline popup fixes
parent
dfa5e826f09d
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+21 −0
manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22   "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33   "manifest_version": 3,44   "name": "Peach Password Manager",5-  "version": "3.6.151",5+  "version": "3.6.152",66   "description": "Zero-knowledge password manager",77   "browser_specific_settings": {88     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "3.6.151",4+  "version": "3.6.152",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "3.6.151",3+  "version": "3.6.152",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/content/inject.ts+21 −0
@@ -1336,6 +1336,10 @@13361336   host.style.setProperty("--peach-accent-dim", withAlpha(accentHex, "28"));13371337   host.style.setProperty("--peach-glow", withAlpha(accentHex, "18"));13381338   host.style.setProperty("--peach-border-accent", withAlpha(accentHex, "30"));1339+  // Hint the compositor that top/left animate during scroll repositioning so the1340+  // popup stays on the GPU layer and doesn't flicker as observeAnchorPosition1341+  // re-asserts its coordinates.1342+  host.style.willChange = "top, left";13391343   // delegatesFocus: focus on an internal input (search, inline unlock) makes the13401344   // HOST appear focused to the outside DOM. Modal focus traps that monitor13411345   // focusin then see the host (mounted inside the modal) as the focused element@@ -2953,6 +2957,23 @@29532957   }29542958   const stopObserving = observeAnchorPosition(anchorEl, updatePosition);29552959   updatePosition();2960+2961+  // observeAnchorPosition listens on window scroll, but a modal with its own2962+  // internal scroll container doesn't move the window — track the modal's scroll2963+  // too so the popup follows the field inside it.2964+  if (modalAncestor) {2965+    modalAncestor.addEventListener("scroll", updatePosition, { passive: true });2966+    cleanupHooks.push(() => modalAncestor.removeEventListener("scroll", updatePosition));2967+  }2968+2969+  // Dismiss when the anchor leaves the DOM. A modal closing (or an SPA route2970+  // swap) removes the field without firing blur/click, so none of the normal2971+  // dismiss triggers fire and the popup would otherwise linger over nothing.2972+  const removalObserver = new MutationObserver(() => {2973+    if (!document.contains(anchorEl)) dismiss();2974+  });2975+  removalObserver.observe(document.body, { childList: true, subtree: true });2976+  cleanupHooks.push(() => removalObserver.disconnect());29562977 29572978   if (options.locked) {29582979     renderLocked();

view file