baaa5658dee6

fix data loss in +add migration: source migration patch from saved entry value not draft; only commit drafts that are non-empty and changed (v2.33.4)

time
2026-05-07 01:10 UTC · 5 months ago
machine
koh2
parent
4c8f4b2cdb09
3 files manifest.firefox.json+1 −1manifest.json+1 −1src/popup/views/Entry.tsx+27 −19
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.33.3",4+  "version": "2.33.4",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.33.3",4+  "version": "2.33.4",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

src/popup/views/Entry.tsx+27 −19
@@ -267,58 +267,66 @@267267     setShowGenerator(false);268268   }269269 270-  // Switch from one identity slot to the other while editing.271-  // If we're in the username slot with an email-shaped draft and no separate272-  // email yet, migrate that value into entry.email so the now-empty username273-  // slot can hold a plain username — this is the legacy-data fix-up path.270+  // Switch from one identity slot to the other.271+  //272+  // Migration source is the SAVED entry value, never the live draft — using273+  // the draft caused data loss when the user typed or cleared the input274+  // before clicking +add (the migration would skip and a later Save would275+  // overwrite the original email-shape value with whatever was in the input).276+  //277+  // Drafts only get committed when they are non-empty AND actually changed,278+  // so an empty input never accidentally clears a saved field.274279   async function addOtherIdentity(target: "username" | "email") {275280     const entry = props.entry;276281     if (!entry) return;277282     const current = editingField();278-    const value = draft().trim();279-    if (current === "username" && target === "email") {280-      // Commit current username draft (if changed), then open email editor.281-      if (value !== (entry.username ?? "")) {283+    const draftValue = draft().trim();284+    const savedUsername = entry.username ?? "";285+    const savedEmail = entry.email ?? "";286+287+    if (current === "username" && target === "username") {288+      // Legacy fix-up: saved username is email-shaped and no separate email289+      // exists yet. Move it to entry.email and free the username slot.290+      if (savedUsername.includes("@") && !entry.email) {282291         setSaving(true);283292         try {284293           await sendMessage({285294             type: "UPDATE_ENTRY",286-            payload: { id: entry.id, patch: { username: value } },295+            payload: { id: entry.id, patch: { email: savedUsername, username: "" } },287296           });288297           await props.onFieldSaved();289298         } finally {290299           setSaving(false);291300         }292301       }293-      startEdit("email", "");302+      startEdit("username", "");294303       return;295304     }296-    if (current === "username" && target === "username") {297-      // Already editing username — only meaningful when the saved username298-      // is email-shaped. Migrate it to entry.email and reset the slot.299-      if (value.includes("@") && !entry.email) {305+306+    if (current === "username" && target === "email") {307+      if (draftValue && draftValue !== savedUsername) {300308         setSaving(true);301309         try {302310           await sendMessage({303311             type: "UPDATE_ENTRY",304-            payload: { id: entry.id, patch: { email: value, username: "" } },312+            payload: { id: entry.id, patch: { username: draftValue } },305313           });306314           await props.onFieldSaved();307315         } finally {308316           setSaving(false);309317         }310318       }311-      startEdit("username", "");319+      startEdit("email", "");312320       return;313321     }322+314323     if (current === "email" && target === "username") {315-      // Commit current email draft (if changed), then open username editor.316-      if (value !== (entry.email ?? "")) {324+      if (draftValue && draftValue !== savedEmail) {317325         setSaving(true);318326         try {319327           await sendMessage({320328             type: "UPDATE_ENTRY",321-            payload: { id: entry.id, patch: { email: value } },329+            payload: { id: entry.id, patch: { email: draftValue } },322330           });323331           await props.onFieldSaved();324332         } finally {

view file