d5f614d9f6b0

filter passkey-only + notes from inline autofill selector, add passkey badge to entries with both password and passkey, move tags section above TOTP so it stays discoverable when 2FA is present, bump v2.11.1

time
2026-04-28 22:25 UTC · 6 months ago
machine
koh2
parent
ca652c13429f
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.ts+14 −5src/content/inject.ts+14 −0src/popup/views/Entry.tsx+61 −63
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.11.0",4+  "version": "2.11.1",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.11.0",4+  "version": "2.11.1",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": "2.11.0",3+  "version": "2.11.1",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/background/messages.ts+14 −5
@@ -265,11 +265,18 @@265265             })),266266           };267267         }268-        const matches = includeAll269-          ? vault270-              .getEntries()271-              .filter((entry) => entry.type !== "note" && entry.type !== "passkey" && entry.type !== "card")272-          : vault.queryDomain(domain);268+        // The inline selector fills username+password into a form. Passkey-269+        // only entries have no password to fill; sign-in for those goes270+        // through the WebAuthn path instead. Filter them out everywhere so271+        // the user doesn't see four "GitHub" rows that mostly do nothing.272+        // Notes and cards never belonged here either.273+        const matches = (274+          includeAll275+            ? vault276+                .getEntries()277+                .filter((entry) => entry.type !== "note" && entry.type !== "card")278+            : vault.queryDomain(domain)279+        ).filter((entry) => !isPasskeyOnly(entry));273280         return {274281           ok: true,275282           data: matches.map((e) =>@@ -277,6 +284,7 @@277284               ? {278285                   id: e.id,279286                   title: e.title,287+                  hasPasskey: e.passkey !== undefined,280288                 }281289               : {282290                   id: e.id,@@ -284,6 +292,7 @@284292                   username: e.username,285293                   url: e.url,286294                   hasTotp: Boolean(e.totp),295+                  hasPasskey: e.passkey !== undefined,287296                 }288297           ),289298         };

view file

src/content/inject.ts+14 −0
@@ -39,6 +39,8 @@3939   username?: string;4040   url?: string;4141   hasTotp?: boolean;42+  /** True when the entry also has a passkey credential attached. */43+  hasPasskey?: boolean;4244 }4345 4446 interface SelectorOptions {@@ -447,6 +449,14 @@447449     color: color-mix(in srgb, var(--peach-accent) 75%, transparent);448450     font-family: "IBM Plex Mono", monospace; flex-shrink: 0; margin-left: 8px;449451   }452+  .passkey-badge {453+    font-size: 8px; letter-spacing: 0.04em; padding: 2px 6px;454+    border-radius: 4px;455+    background: color-mix(in srgb, var(--peach-accent) 14%, transparent);456+    color: color-mix(in srgb, var(--peach-accent) 85%, transparent);457+    border: 1px solid color-mix(in srgb, var(--peach-accent) 24%, transparent);458+    font-family: "IBM Plex Mono", monospace; flex-shrink: 0; margin-left: 6px;459+  }450460 451461   .divider { height: 1px; background: var(--peach-border); margin: 2px 0; }452462 @@ -1152,6 +1162,10 @@11521162 11531163       if (e.hasTotp) {11541164         const badge = document.createElement("span"); badge.className = "totp-badge"; badge.textContent = "TOTP";1165+        entryEl.appendChild(badge);1166+      }1167+      if (e.hasPasskey) {1168+        const badge = document.createElement("span"); badge.className = "passkey-badge"; badge.textContent = "passkey";11551169         entryEl.appendChild(badge);11561170       }11571171 

view file

src/popup/views/Entry.tsx+61 −63
@@ -918,6 +918,67 @@918918                 </Show>919919               </Show>920920 921+              {/* ── Tags editor / add-tags link (between notes and TOTP) ─── */}922+              <Show923+                when={editingTags()}924+                fallback={925+                  <Show when={!entry().tags || entry().tags!.length === 0}>926+                    <button927+                      class="peach-add-field-link peach-entry-add-link"928+                      onClick={startEditTags}929+                    >930+                      + tags931+                    </button>932+                  </Show>933+                }934+              >935+                <div936+                  class="peach-inline-tag-editor"937+                  onFocusOut={(e) => {938+                    const next = e.relatedTarget;939+                    if (next instanceof Node && e.currentTarget.contains(next)) return;940+                    void saveTags();941+                  }}942+                >943+                  <span class="peach-label">tags</span>944+                  <div class="peach-tag-input-wrap">945+                    <For each={tagsDraft()}>946+                      {(tag) => (947+                        <span class="peach-tag-chip">948+                          {tag}949+                          <button950+                            class="peach-tag-chip-remove"951+                            onMouseDown={(e) => e.preventDefault()}952+                            onClick={() => setTagsDraft(tagsDraft().filter(t => t !== tag))}953+                            aria-label={`Remove ${tag}`}954+                          >×</button>955+                        </span>956+                      )}957+                    </For>958+                    <input959+                      class="peach-tag-input"960+                      placeholder={tagsDraft().length === 0 ? "type and press Enter…" : ""}961+                      value={tagInput()}962+                      onInput={(e) => setTagInput(e.currentTarget.value)}963+                      onKeyDown={(e) => {964+                        if (e.key === "Enter" || e.key === ",") {965+                          e.preventDefault();966+                          addTag(tagInput());967+                        } else if (e.key === "Backspace" && !tagInput() && tagsDraft().length > 0) {968+                          setTagsDraft(tagsDraft().slice(0, -1));969+                        } else if (e.key === "Escape") {970+                          e.preventDefault();971+                          setEditingTags(false);972+                          setTagInput("");973+                        }974+                      }}975+                      ref={(el) => requestAnimationFrame(() => el?.focus())}976+                      style="flex:1;min-width:80px;background:transparent;border:none;outline:none;color:var(--t1);font-size:12px;padding:2px;"977+                    />978+                  </div>979+                </div>980+              </Show>981+921982               {/* ── TOTP ─────────────────────────────────────────── */}922983               <Show when={entry().type === "login" && !isPasskeyOnly(entry())}>923984                 <Show@@ -1071,69 +1132,6 @@10711132                     </section>10721133                   );10731134                 }}1074-              </Show>1075-1076-              {/* ── Tags editor / add-tags link ──────────────────── */}1077-              <Show when={true}>1078-                <Show1079-                  when={editingTags()}1080-                  fallback={1081-                    <Show when={!entry().tags || entry().tags!.length === 0}>1082-                      <button1083-                        class="peach-add-field-link peach-entry-add-link"1084-                        onClick={startEditTags}1085-                      >1086-                        + tags1087-                      </button>1088-                    </Show>1089-                  }1090-                >1091-                  <div1092-                    class="peach-inline-tag-editor"1093-                    onFocusOut={(e) => {1094-                      const next = e.relatedTarget;1095-                      if (next instanceof Node && e.currentTarget.contains(next)) return;1096-                      void saveTags();1097-                    }}1098-                  >1099-                    <span class="peach-label">tags</span>1100-                    <div class="peach-tag-input-wrap">1101-                      <For each={tagsDraft()}>1102-                        {(tag) => (1103-                          <span class="peach-tag-chip">1104-                            {tag}1105-                            <button1106-                              class="peach-tag-chip-remove"1107-                              onMouseDown={(e) => e.preventDefault()}1108-                              onClick={() => setTagsDraft(tagsDraft().filter(t => t !== tag))}1109-                              aria-label={`Remove ${tag}`}1110-                            >×</button>1111-                          </span>1112-                        )}1113-                      </For>1114-                      <input1115-                        class="peach-tag-input"1116-                        placeholder={tagsDraft().length === 0 ? "type and press Enter…" : ""}1117-                        value={tagInput()}1118-                        onInput={(e) => setTagInput(e.currentTarget.value)}1119-                        onKeyDown={(e) => {1120-                          if (e.key === "Enter" || e.key === ",") {1121-                            e.preventDefault();1122-                            addTag(tagInput());1123-                          } else if (e.key === "Backspace" && !tagInput() && tagsDraft().length > 0) {1124-                            setTagsDraft(tagsDraft().slice(0, -1));1125-                          } else if (e.key === "Escape") {1126-                            e.preventDefault();1127-                            setEditingTags(false);1128-                            setTagInput("");1129-                          }1130-                        }}1131-                        ref={(el) => requestAnimationFrame(() => el?.focus())}1132-                        style="flex:1;min-width:80px;background:transparent;border:none;outline:none;color:var(--t1);font-size:12px;padding:2px;"1133-                      />1134-                    </div>1135-                  </div>1136-                </Show>11371135               </Show>11381136 11391137               {/* ── Bottom spacer ─────────────────────────────────── */}

view file