d02401fe200a

refresh open popup after remote sync merges (v5.2.56)

time
2026-09-12 13:42 UTC · 1 month ago
machine
koh2
agent
camel-stream/auto
session
refresh sync list
parent
19c8b3dce8ef
9 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/message-policy.test.ts+1 −1src/background/message-policy.ts+1 −0src/background/sync-vault-adapter.test.ts+23 −0src/background/vault.ts+11 −0src/popup/App.tsx+6 −0src/shared/types.ts+1 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.2.55",4+  "version": "5.2.56",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "5.2.55",5+  "version": "5.2.56",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "5.2.55",3+  "version": "5.2.56",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/message-policy.test.ts+1 −1
@@ -35,7 +35,7 @@3535       "PASSKEY_AUTH_RESPONSE", "VALIDATE_RECOVERY_WORDS", "RECOVER_WITH_WORDS", "GET_RECOVERY_WORDS",3636       "GET_RECOVERY_CONFIRM_PENDING", "SET_RECOVERY_CONFIRM_PENDING", "CLEAR_RECOVERY_CONFIRM_PENDING",3737       "GET_IDENTITY_SUGGESTIONS", "TOUCH_AUTO_LOCK", "SAVE_NOTE", "DELETE_NOTE", "GET_NOTES", "SEARCH_VAULT",38-      "ML_INIT", "ML_PREDICT_BATCH", "PING", "SUMMON_AUTOFILL", "VAULT_LOCKED", "VAULT_UNLOCKED",38+      "ML_INIT", "ML_PREDICT_BATCH", "PING", "SUMMON_AUTOFILL", "VAULT_LOCKED", "VAULT_UNLOCKED", "VAULT_ENTRIES_CHANGED",3939       "PRO_GATE_TRIGGERED", "PASSKEY_PRO_GATE", "OPEN_PRO_GATE", "OPEN_POPUP", "OPEN_DETACHED_POPUP",4040       "SHOW_PASSKEY_TOAST", "DISMISS_PASSKEY_TOAST", "SHOULD_PLAY_CELEBRATION", "MARK_CELEBRATION_PLAYED",4141       "GET_CODEX_VAULT_DATA", "ESTIMATE_CODEX_PAGES", "GENERATE_CODEX", "RESTORE_FROM_CODEX",

view file

src/background/message-policy.ts+1 −0
@@ -146,6 +146,7 @@146146   SUMMON_AUTOFILL: "background-internal",147147   VAULT_LOCKED: "background-internal",148148   VAULT_UNLOCKED: "background-internal",149+  VAULT_ENTRIES_CHANGED: "background-internal",149150   PRO_GATE_TRIGGERED: "background-internal",150151   PASSKEY_PRO_GATE: "background-internal",151152   SHOW_PASSKEY_TOAST: "background-internal",

view file

src/background/sync-vault-adapter.test.ts+23 −0
@@ -200,6 +200,29 @@200200     expect(vault.getEntries().find((e) => e.id === local.id)?.title).toBe("Updated");201201   });202202 203+  it("tells an open popup to reload when a round merges remote entries, and stays silent otherwise", async () => {204+    const { vault, adapter } = await setupVault();205+    // The user's case: a note authored on the phone. An open popup keeps the206+    // list it loaded at mount and polls nothing, so a merged note would never207+    // appear without this broadcast.208+    const note = await vault.addEntry({ type: "note", title: "From phone", notes: "body" });209+    await vault.updateEntry(note.id, { notes: "edited on the peer" });210+    const envelopes = await adapter.getEncryptedEntries([note.id]);211+212+    const sendMessage = vi.mocked(chrome.runtime.sendMessage);213+    await vault.restoreEntries([{ ...note, modifiedAt: note.modifiedAt - 1000, notes: "stale" }], "replace");214+215+    sendMessage.mockClear();216+    await adapter.applyEntries(envelopes);217+    expect(sendMessage).toHaveBeenCalledWith({ type: "VAULT_ENTRIES_CHANGED" });218+219+    // The 10-minute dial sees equal mtimes most of the time; a no-op round must220+    // not churn the popup list.221+    sendMessage.mockClear();222+    await adapter.applyEntries(envelopes);223+    expect(sendMessage).not.toHaveBeenCalled();224+  });225+203226   it("applyEntries rejects envelopes whose decrypted id differs from the map key", async () => {204227     const { vault, adapter } = await setupVault();205228     const a = await vault.addEntry({ type: "login", title: "Alpha", password: "pw" });

view file

src/background/vault.ts+11 −0
@@ -2810,6 +2810,17 @@28102810   if (applied > 0 && vaultMode === "real") {28112811     await persistEntries("sync_received");28122812     buildDomainIndexSync(entries);2813+    // An open popup holds the entries it loaded at mount and polls nothing, so2814+    // a background round (the relay listener responding to a peer's push, or the2815+    // alarm-driven dial) otherwise lands silently and the user sees stale data2816+    // until they reopen. Best-effort: no popup listening is the normal case.2817+    try {2818+      void chrome.runtime2819+        .sendMessage({ type: "VAULT_ENTRIES_CHANGED" })2820+        .catch(() => undefined);2821+    } catch {2822+      // messaging unavailable2823+    }28132824   }28142825   return { received, applied, conflicts };28152826 }

view file

src/popup/App.tsx+6 −0
@@ -794,6 +794,12 @@794794       if (msg?.type === "SYNC_STATE_CHANGED") {795795         setSyncSummary((msg.payload ?? {}) as SyncOutcomeSummary);796796       }797+      // A background sync merged entries the user never asked for (a peer's798+      // push, or the periodic dial). Pull the fresh list so the vault view is799+      // not stale — loadEntries() preserves the current selection by id.800+      if (msg?.type === "VAULT_ENTRIES_CHANGED" && view() !== "lock") {801+        void loadEntries();802+      }797803       if (msg?.type === "PRO_GATE_TRIGGERED") {798804         setUpgradeHighlight(msg.payload?.feature ?? null);799805         setSettingsInitialSection("license");

view file

src/shared/types.ts+1 −0
@@ -321,6 +321,7 @@321321   | "SUMMON_AUTOFILL" // background → content: open the in-page palette322322   | "VAULT_LOCKED"    // background → content/popup: vault just locked323323   | "VAULT_UNLOCKED"  // background → content/popup: vault just unlocked324+  | "VAULT_ENTRIES_CHANGED" // background → popup: a sync round merged remote entries324325   | "PRO_GATE_TRIGGERED" // background → popup: a Pro feature was attempted on free325326   | "PASSKEY_PRO_GATE" // background → content: show passkey Pro gate in inline popup326327   | "OPEN_PRO_GATE" // content → background: open popup and navigate to License section

view file