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
↓ peach-browser-ae1591d8.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+21 −0manifest.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": {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",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": {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();