c1f231e00063

always-show history toggle + empty state in both edit surfaces (v3.7.1); record outcome

time
2026-06-07 15:39 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
password history
parent
ba3bcf1cd545
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+21 −4src/popup/styles/popup.css+10 −0src/popup/views/Entry.tsx+19 −14
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 Password Manager",5-  "version": "3.7.0",5+  "version": "3.7.1",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 Password Manager",4-  "version": "3.7.0",4+  "version": "3.7.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": "3.7.0",3+  "version": "3.7.1",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/content/inject.ts+21 −4
@@ -823,6 +823,13 @@823823     color: var(--peach-accent);824824     background: color-mix(in srgb, var(--peach-accent) 12%, transparent);825825   }826+  .pw-history-empty {827+    font-family: "IBM Plex Mono", monospace; font-size: 10px; line-height: 1.45;828+    color: color-mix(in srgb, var(--peach-accent) 55%, transparent);829+    padding: 6px 8px; border-radius: 7px;830+    background: color-mix(in srgb, var(--peach-accent) 5%, transparent);831+    border: 1px solid color-mix(in srgb, var(--peach-accent) 9%, transparent);832+  }826833   .save-btn {827834     width: 100%; padding: 8px; border-radius: 8px; border: none;828835     background: var(--peach-accent); color: #fff; font-size: 12px;@@ -2907,23 +2914,33 @@29072914 29082915     // Past-password history: reveal, copy, or restore a previously-used value.29092916     // Rows are masked by default; "use" loads the old value into the editor so2910-    // the standard save flow records the swap (no separate write path).2917+    // the standard save flow records the swap (no separate write path). The2918+    // toggle is always present (matching the main popup) so the affordance is2919+    // discoverable — even before any history exists it explains how it fills.29112920     const history = entryRes.data.passwordHistory ?? [];2912-    if (history.length > 0) {2921+    {29132922       const histList = document.createElement("div");29142923       histList.className = "pw-history";29152924       histList.style.display = "none";29162925       let histVisible = false;29172926 2927+      const restLabel = history.length ? `↺ history (${history.length})` : "↺ history";29182928       const histToggle = document.createElement("button");29192929       histToggle.className = "gen-toggle-link";29202930       histToggle.type = "button";2921-      histToggle.textContent = `↺ history (${history.length})`;2931+      histToggle.textContent = restLabel;29222932       histToggle.addEventListener("click", () => {29232933         histVisible = !histVisible;29242934         histList.style.display = histVisible ? "" : "none";2925-        histToggle.textContent = histVisible ? "↺ hide history" : `↺ history (${history.length})`;2935+        histToggle.textContent = histVisible ? "↺ hide history" : restLabel;29262936       });2937+2938+      if (history.length === 0) {2939+        const empty = document.createElement("div");2940+        empty.className = "pw-history-empty";2941+        empty.textContent = "No earlier passwords yet. When you change this password, the old one is saved here so you can copy or restore it.";2942+        histList.appendChild(empty);2943+      }29272944 29282945       for (const item of history) {29292946         const row = document.createElement("div");

view file

src/popup/styles/popup.css+10 −0
@@ -2423,6 +2423,16 @@24232423   letter-spacing: 0.05em;24242424   padding: 3px 7px;24252425 }2426+.peach-history-empty {2427+  font-family: "IBM Plex Mono", monospace;2428+  font-size: 0.625rem;2429+  line-height: 1.45;2430+  color: var(--peach-t2);2431+  padding: 6px 8px;2432+  border-radius: 7px;2433+  background: color-mix(in srgb, var(--peach-accent) 4%, transparent);2434+  border: 1px solid color-mix(in srgb, var(--peach-accent) 8%, transparent);2435+}24262436 24272437 /* ── New entry body (Add.tsx — mirrors .peach-stage) ─────────────────── */24282438 .peach-entry-body {

view file

src/popup/views/Entry.tsx+19 −14
@@ -1175,20 +1175,20 @@11751175                             </svg>11761176                             {showGenerator() ? "hide generator" : "generate"}11771177                           </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>1178+                          <button1179+                            class="peach-generate-toggle-link"1180+                            type="button"1181+                            onClick={() => { setShowHistory(!showHistory()); if (!showHistory()) setShowGenerator(false); }}1182+                          >1183+                            <svg width="10" height="10" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">1184+                              <path d="M6.5 1a5.5 5.5 0 1 0 5.5 5.5" />1185+                              <path d="M6.5 3.5V6.5L8.5 8" />1186+                              <path d="M1 1.5V4H3.5" />1187+                            </svg>1188+                            {showHistory()1189+                              ? "hide history"1190+                              : (entry().passwordHistory?.length ? `history (${entry().passwordHistory!.length})` : "history")}1191+                          </button>11921192                         </div>11931193                         <Show when={showGenerator()}>11941194                           <GeneratorDrawer@@ -1199,6 +1199,11 @@11991199                         </Show>12001200                         <Show when={showHistory()}>12011201                           <div class="peach-password-history">1202+                            <Show when={(entry().passwordHistory?.length ?? 0) === 0}>1203+                              <div class="peach-history-empty">1204+                                No earlier passwords yet. When you change this password, the old one is saved here so you can copy or restore it.1205+                              </div>1206+                            </Show>12021207                             <For each={entry().passwordHistory ?? []}>12031208                               {(item, i) => (12041209                                 <div class="peach-history-row">

view file