48bbe718accd

identity suggestions dropdown on username field in Add.tsx — surfaces used usernames and emails from vault, filtered as you type, with usage count (v2.38.1)

time
2026-05-10 18:34 UTC · 5 months ago
machine
koh2
parent
dc4a623bf02c
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+45 −0src/popup/views/Add.tsx+36 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.38.0",4+  "version": "2.38.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.38.0",4+  "version": "2.38.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.37.1",3+  "version": "2.38.1",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/popup/styles/popup.css+45 −0
@@ -5111,6 +5111,51 @@51115111   background: color-mix(in srgb, var(--peach-accent) 8%, transparent);51125112 }51135113 5114+/* Identity suggestions — shown below username input in new-entry form */5115+.peach-identity-suggestions {5116+  display: flex;5117+  flex-direction: column;5118+  gap: 2px;5119+  margin-top: 4px;5120+}5121+5122+.peach-identity-suggestion {5123+  display: flex;5124+  align-items: center;5125+  justify-content: space-between;5126+  width: 100%;5127+  padding: 5px 8px;5128+  border-radius: 6px;5129+  border: none;5130+  background: transparent;5131+  cursor: pointer;5132+  text-align: left;5133+  transition: background 80ms ease;5134+}5135+5136+.peach-identity-suggestion:hover {5137+  background: color-mix(in srgb, var(--peach-accent) 8%, transparent);5138+}5139+5140+.peach-identity-suggestion-value {5141+  font-family: "IBM Plex Mono", monospace;5142+  font-size: 0.7rem;5143+  color: var(--peach-t1);5144+  overflow: hidden;5145+  text-overflow: ellipsis;5146+  white-space: nowrap;5147+  flex: 1;5148+  min-width: 0;5149+}5150+5151+.peach-identity-suggestion-meta {5152+  font-family: "IBM Plex Mono", monospace;5153+  font-size: 0.6rem;5154+  color: var(--peach-t3);5155+  flex-shrink: 0;5156+  margin-left: 8px;5157+}5158+51145159 /* Tag filter strip */51155160 .peach-tag-filter-strip {51165161   border-bottom: 1px solid var(--peach-t3);

view file

src/popup/views/Add.tsx+36 −1
@@ -31,7 +31,24 @@3131   const [showPassword, setShowPassword] = createSignal(false);3232   const [showTotp, setShowTotp] = createSignal(!!(props.entry?.totp));3333   const [showNotes, setShowNotes] = createSignal(!!(props.entry?.notes));34+  const [showIdentitySuggestions, setShowIdentitySuggestions] = createSignal(false);3435 36+  const identitySuggestions = createMemo(() => {37+    const input = username().toLowerCase();38+    const counts = new Map<string, number>();39+    for (const entry of props.entries ?? []) {40+      const uname = entry.username?.trim() ?? "";41+      const email = (entry as VaultEntry & { email?: string }).email?.trim() ?? "";42+      if (uname) counts.set(uname, (counts.get(uname) ?? 0) + 1);43+      if (email && email !== uname) counts.set(email, (counts.get(email) ?? 0) + 1);44+    }45+    return Array.from(counts.entries())46+      .filter(([val]) => !input || val.toLowerCase().includes(input))47+      .sort((a, b) => b[1] - a[1])48+      .slice(0, 6)49+      .map(([value, count]) => ({ value, count }));50+  });51+3552   const isNoteEntry = () => props.entry?.type === "note";3653   const isCardEntry = () => props.entry?.type === "card";3754   const [selectedType, setSelectedType] = createSignal<"login" | "note" | "card">(@@ -294,9 +311,27 @@294311                   placeholder="username or email"295312                   autocomplete="off"296313                   value={username()}297-                  onInput={(e) => setUsername((e.currentTarget as HTMLInputElement).value)}314+                  onInput={(e) => { setUsername((e.currentTarget as HTMLInputElement).value); setShowIdentitySuggestions(true); }}315+                  onFocus={() => setShowIdentitySuggestions(true)}316+                  onBlur={() => setTimeout(() => setShowIdentitySuggestions(false), 150)}298317                 />299318               </div>319+              <Show when={showIdentitySuggestions() && identitySuggestions().length > 0}>320+                <div class="peach-identity-suggestions">321+                  <For each={identitySuggestions()}>322+                    {(s) => (323+                      <button324+                        class="peach-identity-suggestion"325+                        type="button"326+                        onMouseDown={() => { setUsername(s.value); setShowIdentitySuggestions(false); }}327+                      >328+                        <span class="peach-identity-suggestion-value">{s.value}</span>329+                        <span class="peach-identity-suggestion-meta">{s.count}×</span>330+                      </button>331+                    )}332+                  </For>333+                </div>334+              </Show>300335             </div>301336 302337             {/* Password */}

view file