aa7bb5ef2536

feat: add type-to-search escape hatch from inline popup silver platter (v3.6.99)

- Add searchSvgStr() helper paired with the existing plusSvgStr() - Render a .domain-search-btn next to the existing + in the silver platter header (wrapped in .domain-header-actions) - Clicking the search icon switches the popup into the existing renderList() search view with the input focused - Add a capture-phase document keydown handler active only while currentMode === 'silver-platter': a printable single-character keystroke seeds the search query and switches into the list view, so the user can pivot to a different entry without first clicking the icon - renderList now resets currentMode to 'list' and places the input cursor at query.length so seeded queries extend naturally - Bump package/lockfile/Chrome+Firefox manifests to 3.6.99 (manifests were still at 3.6.95 — carried-over save only bumped package files) The remember-this-site toast already fires from src/content/index.ts when the chosen entry's domain set does not include the page domain, so picking another entry through search auto-asks whether to remember the mapping and then fills.

time
2026-05-27 16:03 UTC · 5 months ago
machine
koh2
parent
827c7e4a4890
5 files manifest.firefox.json+1 −1manifest.json+1 −1package-lock.json+2 −2package.json+1 −1src/content/inject.ts+60 −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.95",5+  "version": "3.6.99",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.95",4+  "version": "3.6.99",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package-lock.json+2 −2
@@ -1,12 +1,12 @@11 {22   "name": "peach",3-  "version": "3.6.98",3+  "version": "3.6.99",44   "lockfileVersion": 3,55   "requires": true,66   "packages": {77     "": {88       "name": "peach",9-      "version": "3.6.98",9+      "version": "3.6.99",1010       "dependencies": {1111         "@bokuweb/zstd-wasm": "^0.0.27",1212         "@stablelib/blake2s": "^2.0.1",

view file

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

view file

src/content/inject.ts+60 −5
@@ -91,6 +91,10 @@9191   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"><path d="M6 1.5v9M1.5 6h9"/></svg>`;9292 }9393 94+function searchSvgStr(): string {95+  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>`;96+}97+9498 function peachIconSvg(): string {9599   // Peach body stays brand-peach regardless of `accentHex` — a sage or96100   // sky accent must not turn the autofill icon green or blue. Accent@@ -585,8 +589,13 @@585589     font-family: "IBM Plex Mono", monospace;586590     white-space: nowrap; overflow: hidden; text-overflow: ellipsis;587591   }588-  .domain-add-btn {592+  .domain-header-actions {593+    display: inline-flex; align-items: center; gap: 5px;589594     flex-shrink: 0;595+  }596+  .domain-add-btn,597+  .domain-search-btn {598+    flex-shrink: 0;590599     width: 22px; height: 22px;591600     display: inline-flex; align-items: center; justify-content: center;592601     background: color-mix(in srgb, var(--peach-accent) 12%, transparent);@@ -598,12 +607,14 @@598607     font-family: "IBM Plex Mono", monospace;599608     font-size: 14px; line-height: 1;600609   }601-  .domain-add-btn:hover {610+  .domain-add-btn:hover,611+  .domain-search-btn:hover {602612     background: color-mix(in srgb, var(--peach-accent) 22%, transparent);603613     border-color: color-mix(in srgb, var(--peach-accent) 48%, transparent);604614     transform: scale(1.06);605615   }606-  .domain-add-btn:active { transform: scale(0.96); }616+  .domain-add-btn:active,617+  .domain-search-btn:active { transform: scale(0.96); }607618 608619   /* Search */609620   .search-wrap {@@ -1548,6 +1559,7 @@15481559     container.innerHTML = "";15491560     focusedIndex = -1;15501561     searchInput = null;1562+    currentMode = "list";15511563 15521564     const current = activeEntries();15531565 @@ -1671,7 +1683,12 @@16711683 16721684     // Focus search if present16731685     if (searchInput) {1674-      requestAnimationFrame(() => searchInput?.focus());1686+      requestAnimationFrame(() => {1687+        searchInput?.focus();1688+        // Seed query (from type-to-search or domain-search-btn) lands at the1689+        // end so the next keystroke extends it, not overwrites.1690+        searchInput?.setSelectionRange(query.length, query.length);1691+      });16751692     }16761693   }16771694 @@ -1971,6 +1988,25 @@19711988     headText.append(eyebrow, domName);19721989     domHdr.appendChild(headText);19731990     if (!options.isSignup) {1991+      const actions = document.createElement("div");1992+      actions.className = "domain-header-actions";1993+1994+      // Search shortcut — same idea as type-to-search below, but visible so1995+      // the user can discover that another entry is reachable from a1996+      // single-match platter. Sits to the left of the create button.1997+      const searchBtn = document.createElement("button");1998+      searchBtn.className = "domain-search-btn";1999+      searchBtn.type = "button";2000+      searchBtn.setAttribute("aria-label", "Search for a different entry");2001+      searchBtn.title = "Search for a different entry";2002+      searchBtn.innerHTML = searchSvgStr();2003+      searchBtn.addEventListener("click", (e) => {2004+        e.stopPropagation();2005+        query = "";2006+        renderList();2007+      });2008+      actions.appendChild(searchBtn);2009+19742010       const addBtn = document.createElement("button");19752011       addBtn.className = "domain-add-btn";19762012       addBtn.type = "button";@@ -1978,7 +2014,9 @@19782014       addBtn.title = "Create new entry";19792015       addBtn.textContent = "+";19802016       addBtn.addEventListener("click", (e) => { e.stopPropagation(); void showCreateForm(); });1981-      domHdr.appendChild(addBtn);2017+      actions.appendChild(addBtn);2018+2019+      domHdr.appendChild(actions);19822020     }19832021     container.appendChild(domHdr);19842022 @@ -2596,6 +2634,23 @@25962634   }25972635 25982636   document.addEventListener("keydown", handleKeyDown);2637+2638+  // Type-to-search from the silver platter. A printable single-character2639+  // keystroke (no modifier) seeds the search query and switches into the2640+  // list view, so the user can pivot to a different entry without first2641+  // clicking the search icon. Capture phase so the anchor input does not2642+  // receive the character before we hijack it.2643+  const silverPlatterTypeHandler = (e: KeyboardEvent) => {2644+    if (currentMode !== "silver-platter") return;2645+    if (e.ctrlKey || e.metaKey || e.altKey) return;2646+    if (e.key.length !== 1) return;2647+    e.preventDefault();2648+    e.stopImmediatePropagation();2649+    query = e.key;2650+    renderList();2651+  };2652+  document.addEventListener("keydown", silverPlatterTypeHandler, true);2653+  cleanupHooks.push(() => document.removeEventListener("keydown", silverPlatterTypeHandler, true));25992654 26002655   document.body.appendChild(host);26012656 

view file