02cdb92bcff6

restore active settings card on return from Codex/Chirp, catch expired SW session on duress setup (v3.6.26)

time
2026-05-19 15:24 UTC · 5 months ago
machine
koh2
parent
a669788a81dd
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.ts+4 −0src/popup/App.tsx+4 −2src/popup/views/Settings.tsx+8 −1
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",5-  "version": "3.6.25",5+  "version": "3.6.26",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",4-  "version": "3.6.25",4+  "version": "3.6.26",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.6.25",3+  "version": "3.6.26",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/background/messages.ts+4 −0
@@ -760,6 +760,10 @@760760         // but server-side is the actual security boundary.761761         const gate = await requirePro("duress_vault");762762         if (!gate.allowed) return { ok: false, error: "pro_required" };763+        // MV3 service workers restart after inactivity, losing the in-memory764+        // derived key. Detect this before calling setupDuress so the popup765+        // can show a meaningful prompt to re-lock and re-unlock.766+        if (!vault.isUnlocked()) return { ok: false, error: "session_expired" };763767         const { duressPassword } = msg.payload as { duressPassword: string };764768         await vault.setupDuress(duressPassword);765769         return { ok: true };

view file

src/popup/App.tsx+4 −2
@@ -151,6 +151,7 @@151151   // re-opens onto a stored license that has changed via another window.152152   const [proStatus, setProStatus] = createSignal(false);153153   const [settingsInitialSection, setSettingsInitialSection] = createSignal<string | null>(null);154+  const [settingsActiveCard, setSettingsActiveCard] = createSignal<string>("protection");154155   const [upgradeHighlight, setUpgradeHighlight] = createSignal<ProFeature | null>(null);155156   const [sortOrder, setSortOrder] = createSignal<SortOrder>("url-first");156157   const [listSearchSeed, setListSearchSeed] = createSignal("");@@ -1379,11 +1380,12 @@13791380                   onExport={() => setView("export")}13801381                   onBreachCheckDone={loadEntries}13811382                   onOpenHealth={openHealth}1382-                  onOpenSyncPairing={() => setView("chirp-sync")}1383+                  onOpenSyncPairing={() => { setSettingsInitialSection(settingsActiveCard()); setView("chirp-sync"); }}13831384                   onOpenFileImport={handleOpenFileImport}13841385                   onOpenPanic={() => setView("panic")}1385-                  onOpenCodexGenerate={() => setView("codex-generate")}1386+                  onOpenCodexGenerate={() => { setSettingsInitialSection(settingsActiveCard()); setView("codex-generate"); }}13861387                   onOpenCodexRestore={() => setView("codex-restore")}1388+                  onCardChange={setSettingsActiveCard}13871389                   onProGate={(feature) => {13881390                     setUpgradeHighlight(feature);13891391                     setSettingsInitialSection("license");

view file

src/popup/views/Settings.tsx+8 −1
@@ -68,6 +68,7 @@6868    *  free tier. App owns the upgrade navigation + highlight state, so this6969    *  callback is the single way for Settings to request that navigation. */7070   onProGate?: (feature: ProFeature) => void;71+  onCardChange?: (id: CardId) => void;7172 }7273 7374 const FEEDBACK_CATEGORIES = ["Bug report", "Feature request", "Question", "Other"];@@ -109,6 +110,8 @@109110     props.upgradeHighlight ? "pro" : initialSectionToCard(initial) ?? "protection"110111   );111112 113+  createEffect(() => { props.onCardChange?.(activeCard()); });114+112115   const [checkingBreaches, setCheckingBreaches] = createSignal(false);113116   const [breachResult, setBreachResult] = createSignal<string | null>(null);114117   const [licenseInfo, setLicenseInfo] = createSignal<LicenseInfo | null>(null);@@ -514,7 +517,11 @@514517       setDuressSuccess("Duress password set. If entered, a decoy vault will be shown.");515518       setTimeout(() => setDuressSuccess(null), 5000);516519     } else {517-      setDuressError(res.error ?? "failed to set duress password");520+      setDuressError(521+        res.error === "session_expired"522+          ? "Vault session expired — lock and unlock Peach, then try again"523+          : res.error ?? "failed to set duress password"524+      );518525     }519526   }520527 

view file