24cc275f410f
add password history reveal/copy/restore drawer to main popup edit mode with styling
- time
- 2026-06-06 19:27 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- password history
- parent
- 7e401ad4a9df
↓ peach-browser-24cc275f.tar.gzatlas at this save
files changed2 files
src/popup/styles/popup.css+65 −0src/popup/views/Entry.tsx+104 −11src/popup/styles/popup.css+65 −0
@@ -2359,6 +2359,71 @@23592359 color: var(--peach-accent);23602360 }23612361 2362+/* Holds the generate + history toggle links on one row under the editor. */2363+.peach-password-edit-links {2364+ display: flex;2365+ align-items: center;2366+ gap: 14px;2367+ flex-wrap: wrap;2368+}2369+2370+/* ── Past-password history list ── */2371+.peach-password-history {2372+ display: grid;2373+ gap: 4px;2374+ margin-top: 2px;2375+ max-height: 168px;2376+ overflow-y: auto;2377+ scrollbar-width: thin;2378+}2379+.peach-history-row {2380+ display: flex;2381+ align-items: center;2382+ justify-content: space-between;2383+ gap: 8px;2384+ padding: 5px 7px;2385+ border-radius: 7px;2386+ background: color-mix(in srgb, var(--peach-accent) 5%, transparent);2387+ border: 1px solid color-mix(in srgb, var(--peach-accent) 9%, transparent);2388+}2389+.peach-history-main {2390+ display: flex;2391+ flex-direction: column;2392+ gap: 1px;2393+ min-width: 0;2394+ flex: 1;2395+}2396+.peach-history-pw {2397+ font-family: "IBM Plex Mono", monospace;2398+ font-size: 0.6875rem;2399+ color: var(--peach-t1);2400+ white-space: nowrap;2401+ overflow: hidden;2402+ text-overflow: ellipsis;2403+}2404+.peach-history-pw .peach-password-mask {2405+ letter-spacing: 0.08em;2406+ color: var(--peach-t2);2407+}2408+.peach-history-age {2409+ font-family: "IBM Plex Mono", monospace;2410+ font-size: 0.5625rem;2411+ color: var(--peach-t3, var(--peach-t2));2412+ letter-spacing: 0.03em;2413+}2414+.peach-history-actions {2415+ display: flex;2416+ align-items: center;2417+ gap: 2px;2418+ flex-shrink: 0;2419+}2420+.peach-history-use {2421+ font-family: "IBM Plex Mono", monospace;2422+ font-size: 0.5625rem;2423+ letter-spacing: 0.05em;2424+ padding: 3px 7px;2425+}2426+23622427 /* ── New entry body (Add.tsx — mirrors .peach-stage) ─────────────────── */23632428 .peach-entry-body {23642429 flex: 1;src/popup/views/Entry.tsx+104 −11
@@ -13,6 +13,22 @@1313 1414 type EditableField = "title" | "url" | "username" | "email" | "password" | "notes" | "totp" | null;1515 16+// Compact "time since" label for the password-history list. Coarse on purpose —17+// the user wants "roughly when", not a precise duration.18+function timeSince(when: number): string {19+ const secs = Math.max(0, Math.floor((Date.now() - when) / 1000));20+ if (secs < 60) return "just now";21+ const mins = Math.floor(secs / 60);22+ if (mins < 60) return `${mins}m ago`;23+ const hours = Math.floor(mins / 60);24+ if (hours < 24) return `${hours}h ago`;25+ const days = Math.floor(hours / 24);26+ if (days < 30) return `${days}d ago`;27+ const months = Math.floor(days / 30);28+ if (months < 12) return `${months}mo ago`;29+ return `${Math.floor(months / 12)}y ago`;30+}31+1632 interface EntryProps {1733 entry: VaultEntry | null;1834 /**@@ -83,6 +99,8 @@8399 const [saving, setSaving] = createSignal(false);84100 const [showPassword, setShowPassword] = createSignal(false);85101 const [showGenerator, setShowGenerator] = createSignal(false);102+ const [showHistory, setShowHistory] = createSignal(false);103+ const [revealedHistory, setRevealedHistory] = createSignal<Set<number>>(new Set());86104 const [copied, setCopied] = createSignal<string | null>(null);87105 // Local draft for the additional URLs list, edited only while the URL field88106 // is in inline-edit mode. Saved alongside `url` when the user confirms.@@ -252,6 +270,8 @@252270 setDraft(value);253271 setIdentityHelperSuppressed(false);254272 setShowGenerator(false);273+ setShowHistory(false);274+ setRevealedHistory(new Set<number>());255275 setConfirmDelete(false);256276 if (field === "url") {257277 setAdditionalUrlsDraft([...(props.entry?.additionalUrls ?? [])]);@@ -305,6 +325,8 @@305325 triggerRing();306326 setEditingField(null);307327 setShowGenerator(false);328+ setShowHistory(false);329+ setRevealedHistory(new Set<number>());308330 await props.onFieldSaved();309331 } catch {310332 // silent — leave editing open@@ -351,6 +373,8 @@351373 setEditingField(null);352374 setDraft("");353375 setShowGenerator(false);376+ setShowHistory(false);377+ setRevealedHistory(new Set<number>());354378 }355379 356380 // Switch from one identity slot to the other.@@ -1139,23 +1163,92 @@11391163 </button>11401164 </div>11411165 <InlineSaveCancelRow onSave={saveField} onCancel={cancelEdit} saving={saving()} />1142- <button1143- class="peach-generate-toggle-link"1144- type="button"1145- onClick={() => setShowGenerator(!showGenerator())}1146- >1147- <svg width="10" height="10" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">1148- <path d="M11 2.5A5.5 5.5 0 1 0 12.5 7" />1149- <path d="M9.5 2.5H11V1" />1150- </svg>1151- {showGenerator() ? "hide generator" : "generate"}1152- </button>1166+ <div class="peach-password-edit-links">1167+ <button1168+ class="peach-generate-toggle-link"1169+ type="button"1170+ onClick={() => { setShowGenerator(!showGenerator()); if (!showGenerator()) setShowHistory(false); }}1171+ >1172+ <svg width="10" height="10" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">1173+ <path d="M11 2.5A5.5 5.5 0 1 0 12.5 7" />1174+ <path d="M9.5 2.5H11V1" />1175+ </svg>1176+ {showGenerator() ? "hide generator" : "generate"}1177+ </button>1178+ <Show when={(entry().passwordHistory?.length ?? 0) > 0}>1179+ <button1180+ class="peach-generate-toggle-link"1181+ type="button"1182+ onClick={() => { setShowHistory(!showHistory()); if (!showHistory()) setShowGenerator(false); }}1183+ >1184+ <svg width="10" height="10" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">1185+ <path d="M6.5 1a5.5 5.5 0 1 0 5.5 5.5" />1186+ <path d="M6.5 3.5V6.5L8.5 8" />1187+ <path d="M1 1.5V4H3.5" />1188+ </svg>1189+ {showHistory() ? "hide history" : `history (${entry().passwordHistory!.length})`}1190+ </button>1191+ </Show>1192+ </div>11531193 <Show when={showGenerator()}>11541194 <GeneratorDrawer11551195 open={true}11561196 compact={true}11571197 onUse={(pw) => { setDraft(pw); setShowGenerator(false); }}11581198 />1199+ </Show>1200+ <Show when={showHistory()}>1201+ <div class="peach-password-history">1202+ <For each={entry().passwordHistory ?? []}>1203+ {(item, i) => (1204+ <div class="peach-history-row">1205+ <div class="peach-history-main">1206+ <span class="peach-history-pw" classList={{ "is-revealed": revealedHistory().has(i()) }}>1207+ <Show1208+ when={revealedHistory().has(i())}1209+ fallback={<span class="peach-password-mask">••••••••••••</span>}1210+ >1211+ {item.password}1212+ </Show>1213+ </span>1214+ <span class="peach-history-age">{timeSince(item.changedAt)}</span>1215+ </div>1216+ <div class="peach-history-actions">1217+ <button1218+ class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"1219+ type="button"1220+ title={revealedHistory().has(i()) ? "Hide" : "Reveal"}1221+ onClick={() => setRevealedHistory((prev) => {1222+ const next = new Set(prev);1223+ if (next.has(i())) next.delete(i()); else next.add(i());1224+ return next;1225+ })}1226+ >1227+ <Show when={revealedHistory().has(i())} fallback={<EyeOpenGlyph />}>1228+ <EyeClosedGlyph />1229+ </Show>1230+ </button>1231+ <button1232+ class={`peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn peach-copy-btn ${copied() === `history-${i()}` ? "is-copied" : ""}`}1233+ type="button"1234+ title="Copy"1235+ onClick={(e) => void copyField(item.password, `history-${i()}`, e.currentTarget as HTMLButtonElement)}1236+ >1237+ <CopyGlyph /><span class="peach-copy-sheen" />1238+ </button>1239+ <button1240+ class="peach-btn peach-btn--ghost peach-btn--sm peach-history-use"1241+ type="button"1242+ title="Use this password again"1243+ onClick={() => { setDraft(item.password); setShowPassword(true); setShowHistory(false); }}1244+ >1245+ use1246+ </button>1247+ </div>1248+ </div>1249+ )}1250+ </For>1251+ </div>11591252 </Show>11601253 </div>11611254 </Show>