d3759df043f0

persist onboarding exit flow so a popup close on the create-vault, restore, or adopt screen resumes that screen instead of rewinding to the carousel, bump 4.10.14

time
2026-08-20 14:46 UTC · 2 months ago
machine
koh2
agent
qwen3.8-max
session
peach ui fixes
parent
15d513af25f5
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+53 −14tests/lock-screen.spec.ts+19 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.10.13",4+  "version": "4.10.14",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": "4.10.13",5+  "version": "4.10.14",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": "4.10.13",3+  "version": "4.10.14",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/App.tsx+53 −14
@@ -16,6 +16,8 @@1616   ONBOARDING_LAST_CARD,1717   Onboarding,1818 } from "./views/Onboarding";19+20+const ONBOARDING_EXIT_STORAGE_KEY = "peach_onboarding_exit";1921 import { NotesMode } from "./views/NotesMode";2022 import { ProCelebration } from "./views/ProCelebration";2123 import { InitialSyncView } from "./views/InitialSyncView";@@ -506,6 +508,20 @@506508     });507509   }508510 511+  // Which onboarding exit flow the user is in (create-vault / restore /512+  // adopt). Persisted so closing the popup on one of those screens resumes513+  // it directly instead of rewinding to the last carousel card.514+  function markOnboardingExit(flow: "create" | "restore" | "adopt") {515+    setOnboardingCardAtExit(ONBOARDING_LAST_CARD);516+    void storageSet(ONBOARDING_EXIT_STORAGE_KEY, flow).catch(() => {});517+    // Backing out of the flow remounts the carousel at this card; keep the518+    // persisted card in sync so a later popup close resumes consistently.519+    void storageSet(ONBOARDING_CARD_STORAGE_KEY, String(ONBOARDING_LAST_CARD)).catch(() => {});520+  }521+  function clearOnboardingExit() {522+    void storageRemove(ONBOARDING_EXIT_STORAGE_KEY).catch(() => {});523+  }524+509525   onMount(async () => {510526     // Resolve the active tab in parallel with the unlock/has-vault queries —511527     // openVault()'s smart selection picks the entry matching currentTabDomain,@@ -537,21 +553,33 @@537553       if (vaultExists && !seen) {538554         await storageSet("peach_onboarded", "true");539555         await storageRemove(ONBOARDING_CARD_STORAGE_KEY).catch(() => {});556+        await storageRemove(ONBOARDING_EXIT_STORAGE_KEY).catch(() => {});540557         setOnboarded(true);541558       } else {542559         setOnboarded(seen);543560         if (!seen) {544-          // Resume mid-onboarding where the user left off instead of545-          // rewinding to the opening card after a popup close.546-          const stored = Number(547-            await storageGet(ONBOARDING_CARD_STORAGE_KEY).catch(() => null),548-          );549-          if (550-            Number.isInteger(stored) &&551-            stored > 0 &&552-            stored <= ONBOARDING_LAST_CARD553-          ) {554-            setOnboardingCardAtExit(stored);561+          // A popup close while the user is in an exit flow (create-vault /562+          // restore / adopt) resumes that flow directly — landing back on the563+          // carousel one screen earlier reads as losing progress.564+          const exit = await storageGet(ONBOARDING_EXIT_STORAGE_KEY).catch(() => null);565+          if (exit === "create" || exit === "restore" || exit === "adopt") {566+            setOnboardingCardAtExit(ONBOARDING_LAST_CARD);567+            setOnboarded(true);568+            setStartOnboardingRestore(exit === "restore");569+            if (exit === "adopt") setView("adopting");570+          } else {571+            // Otherwise resume mid-onboarding where the user left off instead572+            // of rewinding to the opening card.573+            const stored = Number(574+              await storageGet(ONBOARDING_CARD_STORAGE_KEY).catch(() => null),575+            );576+            if (577+              Number.isInteger(stored) &&578+              stored > 0 &&579+              stored <= ONBOARDING_LAST_CARD580+            ) {581+              setOnboardingCardAtExit(stored);582+            }555583           }556584         }557585       }@@ -627,6 +655,7 @@627655           "peach_onboarded",628656           "peach_vault_history",629657           ONBOARDING_CARD_STORAGE_KEY,658+          ONBOARDING_EXIT_STORAGE_KEY,630659         ]);631660       } catch { /* best-effort */ }632661       setHasVault(false);@@ -1423,6 +1452,7 @@14231452   async function handleAdoptionDone() {14241453     await storageSet("peach_onboarded", "true").catch(() => {});14251454     await storageRemove(ONBOARDING_CARD_STORAGE_KEY).catch(() => {});1455+    clearOnboardingExit();14261456     setOnboarded(true);14271457     setHasVault(true);14281458     setView("initial-sync");@@ -1505,6 +1535,7 @@15051535                   <Suspense fallback={<div class="peach-loading">loading...</div>}>15061536                     <AdoptVaultView15071537                       onCancel={() => {1538+                        clearOnboardingExit();15081539                         setOnboarded(false);15091540                         setView("lock");15101541                       }}@@ -1519,17 +1550,17 @@15191550                     <Onboarding15201551                       initialCard={onboardingCardAtExit()}15211552                       onComplete={() => {1522-                        setOnboardingCardAtExit(ONBOARDING_LAST_CARD);1553+                        markOnboardingExit("create");15231554                         setStartOnboardingRestore(false);15241555                         setOnboarded(true);15251556                       }}15261557                       onRestore={() => {1527-                        setOnboardingCardAtExit(ONBOARDING_LAST_CARD);1558+                        markOnboardingExit("restore");15281559                         setStartOnboardingRestore(true);15291560                         setOnboarded(true);15301561                       }}15311562                       onAdopt={() => {1532-                        setOnboardingCardAtExit(ONBOARDING_LAST_CARD);1563+                        markOnboardingExit("adopt");15331564                         setStartOnboardingRestore(false);15341565                         setOnboarded(true);15351566                         setView("adopting");@@ -1553,6 +1584,9 @@15531584                     onBackToOnboarding={15541585                       !hasVault()15551586                         ? () => {1587+                            // Deliberately rewinding to the carousel — the exit1588+                            // flow must not reclaim the next popup open.1589+                            clearOnboardingExit();15561590                             setStartOnboardingRestore(false);15571591                             setOnboarded(false);15581592                           }@@ -1966,6 +2000,11 @@19662000                   }}19672001                   onDestroyed={async () => {19682002                     clearCanarySuppressions();2003+                    // Fresh onboarding start after a destroy — don't let the2004+                    // resume markers land the user mid-carousel.2005+                    setOnboardingCardAtExit(0);2006+                    void storageRemove(ONBOARDING_CARD_STORAGE_KEY).catch(() => {});2007+                    clearOnboardingExit();19692008                     setEntries([]);19702009                     setSelectedEntry(null);19712010                     setEditEntry(undefined);

view file

tests/lock-screen.spec.ts+19 −0
@@ -275,6 +275,25 @@275275     });276276     await ctx.close();277277   });278+279+  test("LS22 - closing the popup from the create-vault screen resumes the create-vault screen", async () => {280+    // Fresh profile boots into the carousel; the nav button advances cards and281+    // doubles as "Create vault" on the final card.282+    const { popup } = await loadPopup(ctx);283+    const next = popup.locator(".peach-onboarding-next");284+    for (let i = 0; i < 3; i++) {285+      await next.click();286+      await popup.waitForTimeout(350);287+    }288+    await expect(popup.locator("#master-password")).toBeVisible({ timeout: 5000 });289+    await popup.close();290+291+    const { popup: reopened } = await loadPopup(ctx);292+    // Must land back on the create-vault form itself, not the carousel card before it.293+    await expect(reopened.locator("#master-password")).toBeVisible({ timeout: 5000 });294+    await expect(reopened.locator(".peach-onboarding-card")).toHaveCount(0);295+    await ctx.close();296+  });278297 });279298 280299 test.describe("Lock Screen - Auto-lock Configuration", () => {

view file