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
↓ peach-browser-02cdb92b.tar.gzatlas at this save
files changed6 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 −1manifest.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": {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",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": {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 };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");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