785cca9b28d7

detach popup to chrome.windows for PDF import — survive file dialog focus loss, replace emoji icons with SVGs, lift guidance text contrast (v3.6.70)

time
2026-05-21 01:32 UTC · 5 months ago
machine
koh2
parent
0a34bc3ebed3
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/codex.css+21 −6src/popup/views/CodexRestoreView.tsx+70 −5
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",5-  "version": "3.6.69",5+  "version": "3.6.70",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",4-  "version": "3.6.69",4+  "version": "3.6.70",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.69",3+  "version": "3.6.70",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/popup/styles/codex.css+21 −6
@@ -393,11 +393,27 @@393393 }394394 395395 .peach-codex-restore-option-icon {396-  font-size: 22px;397396   flex-shrink: 0;398-  line-height: 1;397+  width: 28px;398+  height: 28px;399+  display: inline-flex;400+  align-items: center;401+  justify-content: center;402+  color: var(--peach-accent);403+  border-radius: 8px;404+  background: color-mix(in srgb, var(--peach-accent) 10%, transparent);405+  border: 1px solid color-mix(in srgb, var(--peach-accent) 22%, transparent);399406 }400407 408+.peach-codex-restore-option-icon svg {409+  display: block;410+}411+412+.peach-codex-restore-option:hover .peach-codex-restore-option-icon {413+  background: color-mix(in srgb, var(--peach-accent) 16%, transparent);414+  border-color: color-mix(in srgb, var(--peach-accent) 36%, transparent);415+}416+401417 .peach-codex-restore-option-content {402418   display: flex;403419   flex-direction: column;@@ -416,11 +432,10 @@416432 }417433 418434 .peach-codex-restore-guidance {419-  font-size: 0.6875rem;420-  color: var(--peach-t3);435+  font-size: 0.75rem;436+  color: var(--peach-t2);421437   text-align: center;422-  margin: 4px 0 0;423-  font-family: var(--font-mono, ui-monospace, monospace);438+  margin: 8px 0 0;424439   line-height: 1.5;425440 }426441 

view file

src/popup/views/CodexRestoreView.tsx+70 −5
@@ -163,6 +163,39 @@163163     }164164   }165165 166+  // The Chrome action popup dismisses the moment the OS file picker steals167+  // focus, which stranded users mid-import. From the popup we instead open168+  // the restore view in a standalone window (chrome.windows.create) and169+  // let that window own the file dialog. Within the detached window, or in170+  // any non-Chromium context that lacks chrome.windows, we trigger the171+  // input directly.172+  async function openPdfPicker() {173+    if (autoPickPdf) {174+      pdfFileInput?.click();175+      return;176+    }177+    const canDetach =178+      typeof chrome !== "undefined" &&179+      typeof chrome.windows?.create === "function" &&180+      typeof chrome.runtime?.getURL === "function";181+    if (!canDetach) {182+      pdfFileInput?.click();183+      return;184+    }185+    try {186+      await chrome.windows.create({187+        url: chrome.runtime.getURL("popup.html?codex=pdf"),188+        type: "popup",189+        width: 460,190+        height: 720,191+      });192+      window.close();193+    } catch (err) {194+      console.warn("[codex] failed to detach popup for PDF import:", err);195+      pdfFileInput?.click();196+    }197+  }198+166199   async function handlePDFSelected(e: Event) {167200     const input = e.currentTarget as HTMLInputElement;168201     const file = input.files?.[0];@@ -217,9 +250,25 @@217250     }218251   }219252 253+  // Detect whether we are running inside a chrome.windows.create() window254+  // that was opened specifically to handle the PDF picker. The action255+  // popup dismisses on focus loss, so the file dialog tears it down. When256+  // routed via ?codex=pdf we know we are in a survivable window and can257+  // safely auto-trigger the picker on mount.258+  const autoPickPdf = (() => {259+    try {260+      return new URLSearchParams(window.location.search).get("codex") === "pdf";261+    } catch {262+      return false;263+    }264+  })();265+220266   onMount(() => {221-    // Camera does not start automatically — the user picks a path on the222-    // choose screen first.267+    if (autoPickPdf) {268+      // Tiny delay so the click happens after the SolidJS mount settles269+      // and the hidden <input> is in the DOM.270+      setTimeout(() => pdfFileInput?.click(), 50);271+    }223272   });224273 225274   onCleanup(() => {@@ -342,7 +391,14 @@342391               class="peach-codex-restore-option"343392               onClick={() => void startCameraScan()}344393             >345-              <div class="peach-codex-restore-option-icon">📷</div>394+              <div class="peach-codex-restore-option-icon">395+                <svg width="22" height="22" viewBox="0 0 24 24" fill="none"396+                  stroke="currentColor" stroke-width="1.5"397+                  stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">398+                  <path d="M3 8a2 2 0 0 1 2-2h2l1.5-2h7L17 6h2a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8z" />399+                  <circle cx="12" cy="13" r="3.5" />400+                </svg>401+              </div>346402               <div class="peach-codex-restore-option-content">347403                 <span class="peach-codex-restore-option-title">348404                   scan printed pages@@ -355,9 +411,18 @@355411 356412             <button357413               class="peach-codex-restore-option"358-              onClick={() => pdfFileInput?.click()}414+              onClick={() => void openPdfPicker()}359415             >360-              <div class="peach-codex-restore-option-icon">📄</div>416+              <div class="peach-codex-restore-option-icon">417+                <svg width="22" height="22" viewBox="0 0 24 24" fill="none"418+                  stroke="currentColor" stroke-width="1.5"419+                  stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">420+                  <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />421+                  <path d="M14 3v5h5" />422+                  <path d="M9 14h6" />423+                  <path d="M9 17h4" />424+                </svg>425+              </div>361426               <div class="peach-codex-restore-option-content">362427                 <span class="peach-codex-restore-option-title">363428                   import PDF file

view file