1eb81220588e
fix: inline popup search reaches full vault + back-arrow returns to silver platter (v3.6.100)
- time
- 2026-05-27 16:57 UTC · 5 months ago
- machine
- koh2
- parent
- d9877efa8475
↓ peach-browser-1eb81220.tar.gzatlas at this save
files changed8 files
manifest.firefox.json+1 −1manifest.json+1 −1package-lock.json+2 −2package.json+1 −1src/background/messages.test.ts+15 −3src/background/messages.ts+8 −0src/content/index.ts+5 −3src/content/inject.ts+32 −1manifest.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.99",5+ "version": "3.6.100",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",4- "version": "3.6.99",4+ "version": "3.6.100",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package-lock.json+2 −2
@@ -1,12 +1,12 @@11 {22 "name": "peach",3- "version": "3.6.99",3+ "version": "3.6.100",44 "lockfileVersion": 3,55 "requires": true,66 "packages": {77 "": {88 "name": "peach",9- "version": "3.6.99",9+ "version": "3.6.100",1010 "dependencies": {1111 "@bokuweb/zstd-wasm": "^0.0.27",1212 "@stablelib/blake2s": "^2.0.1",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.6.99",3+ "version": "3.6.100",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/background/messages.test.ts+15 −3
@@ -401,7 +401,7 @@401401 expect((response.data as Array<Record<string, unknown>>)[0].totp).toBeUndefined();402402 });403403 404- it("AUTOFILL_QUERY includeAll returns only id and title for non-note, non-passkey entries", async () => {404+ it("AUTOFILL_QUERY includeAll returns search-ready summaries for non-note, non-passkey entries", async () => {405405 const vault = await import("./vault");406406 await vault.createVault("correct-pass-12345");407407 await vault.addEntry({@@ -423,9 +423,21 @@423423 424424 expect(response).toMatchObject({425425 ok: true,426- data: [{ id: expect.any(String), title: "GitHub" }],426+ data: [427+ {428+ id: expect.any(String),429+ title: "GitHub",430+ username: "june",431+ url: "https://github.com",432+ hasPasskey: false,433+ },434+ ],427435 });428- expect(response.data).toHaveLength(1);436+ const data = response.data as Array<Record<string, unknown>>;437+ expect(data).toHaveLength(1);438+ // No secrets cross the boundary.439+ expect(data[0]).not.toHaveProperty("password");440+ expect(data[0]).not.toHaveProperty("totp");429441 });430442 431443 it("AUTOFILL_QUERY kind=totp returns only matching entries with 2FA summaries", async () => {src/background/messages.ts+8 −0
@@ -545,6 +545,14 @@545545 ? {546546 id: e.id,547547 title: e.title,548+ // username/email/url shipped so the inline popup's search549+ // affordance can match on them, not just titles. No secrets550+ // cross the boundary — same shape as the domain-matched551+ // response below minus hasTotp (which the search list552+ // doesn't surface).553+ username: e.username,554+ email: e.email,555+ url: e.url,548556 hasPasskey: e.passkey !== undefined,549557 }550558 : {src/content/index.ts+5 −3
@@ -655,9 +655,11 @@655655 }656656 657657 const entries = firstResult.entries;658- const allEntries = entries.length === 0659- ? (await fetchAutofillEntries(domain, true)).entries660- : entries;658+ // Always fetch the vault-wide list so the inline popup's search affordance659+ // can reach entries that don't match this domain — the user may have a660+ // valid login filed under a different URL (e.g. corp account on a vanity661+ // domain) and still want to fill it here.662+ const allEntries = (await fetchAutofillEntries(domain, true)).entries;661663 662664 if (passkeyToastActive) return;663665 src/content/inject.ts+32 −1
@@ -95,6 +95,10 @@9595 return `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"><circle cx="5.25" cy="5.25" r="3.5"/><path d="M7.9 7.9l2.35 2.35"/></svg>`;9696 }9797 98+function backSvgStr(): string {99+ return `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 2.5L3.5 6l4 3.5"/></svg>`;100+}101+98102 function peachIconSvg(): string {99103 // Peach body stays brand-peach regardless of `accentHex` — a sage or100104 // sky accent must not turn the autofill icon green or blue. Accent@@ -1280,6 +1284,10 @@12801284 let focusedIndex = -1;12811285 let currentMode: "list" | "create" | "success" | "silver-platter" = "list";12821286 let searchInput: HTMLInputElement | null = null;1287+ // Tracks the silver-platter entry the user pivoted away from, so the1288+ // search/list header can offer a one-click way back to it. null when the1289+ // popup never showed a single-match platter (e.g. multi-match list view).1290+ let silverPlatterOrigin: SelectorEntry | null = null;12831291 // Hooks any sub-state owns for cleanup at dismiss time. Today: TOTP tray12841292 // setInterval. Add to this list — don't fan out the dismiss function.12851293 const cleanupHooks: Array<() => void> = [];@@ -1582,6 +1590,26 @@15821590 domHdr.appendChild(headText);15831591 15841592 if (!options.isSignup) {1593+ const actions = document.createElement("div");1594+ actions.className = "domain-header-actions";1595+1596+ // Back arrow takes the place of the silver-platter's search icon when1597+ // the user has pivoted away from a single-match platter — gives them1598+ // a one-click way back to the original recommendation.1599+ if (silverPlatterOrigin) {1600+ const backBtn = document.createElement("button");1601+ backBtn.className = "domain-search-btn";1602+ backBtn.type = "button";1603+ backBtn.setAttribute("aria-label", "Back to suggested entry");1604+ backBtn.title = "Back to suggested entry";1605+ backBtn.innerHTML = backSvgStr();1606+ backBtn.addEventListener("click", (e) => {1607+ e.stopPropagation();1608+ if (silverPlatterOrigin) renderSilverPlatter(silverPlatterOrigin);1609+ });1610+ actions.appendChild(backBtn);1611+ }1612+15851613 const addBtn = document.createElement("button");15861614 addBtn.className = "domain-add-btn";15871615 addBtn.type = "button";@@ -1589,7 +1617,9 @@15891617 addBtn.title = "Create new entry";15901618 addBtn.textContent = "+";15911619 addBtn.addEventListener("click", (e) => { e.stopPropagation(); void showCreateForm(); });1592- domHdr.appendChild(addBtn);1620+ actions.appendChild(addBtn);1621+1622+ domHdr.appendChild(actions);15931623 }15941624 15951625 container.appendChild(domHdr);@@ -1974,6 +2004,7 @@19742004 searchInput = null;19752005 focusedIndex = -1;19762006 currentMode = "silver-platter";2007+ silverPlatterOrigin = entry;19772008 19782009 const domHdr = document.createElement("div");19792010 domHdr.className = "domain-header";