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
↓ peach-browser-d02401fe.tar.gzatlas at this save
files changed9 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 −0manifest.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": {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",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",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",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",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" });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 }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");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