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
↓ peach-browser-d3759df0.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+53 −14tests/lock-screen.spec.ts+19 −0manifest.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": {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",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",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);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", () => {