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
↓ peach-browser-48bbe718.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+45 −0src/popup/views/Add.tsx+36 −1manifest.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": {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",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": {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);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 */}