89d46b400df2
resume onboarding at final card on back, center non-final card copy, bump 4.10.9
- time
- 2026-08-20 04:12 UTC · 2 months ago
- machine
- koh2
- agent
- deepseek-v4-flash-0731
- session
- onboarding back-nav + tidy layout
- parent
- f42d2f85a4b1
↓ peach-browser-89d46b40.tar.gzatlas at this save
files changed7 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+9 −1src/popup/styles/popup.css+4 −0src/popup/views/AdoptVaultView.test.tsx+21 −1src/popup/views/Onboarding.tsx+10 −1manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.10.8",4+ "version": "4.10.9",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.8",5+ "version": "4.10.9",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.8",3+ "version": "4.10.9",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/popup/App.tsx+9 −1
@@ -11,7 +11,7 @@1111 type InitialSyncDevice,1212 } from "../shared/initial-sync";1313 import { LockView } from "./views/Lock";14-import { Onboarding } from "./views/Onboarding";14+import { ONBOARDING_LAST_CARD, Onboarding } from "./views/Onboarding";1515 import { NotesMode } from "./views/NotesMode";1616 import { ProCelebration } from "./views/ProCelebration";1717 import { InitialSyncView } from "./views/InitialSyncView";@@ -146,6 +146,10 @@146146 const [hasVault, setHasVault] = createSignal(false);147147 const [onboarded, setOnboarded] = createSignal(true);148148 const [startOnboardingRestore, setStartOnboardingRestore] = createSignal(false);149+ // Onboarding remounts whenever it leaves (create/adopt/restore) and comes150+ // back. Those flows all exit from the final card, so resume there — a fresh151+ // install's first mount stays at 0.152+ const [onboardingCardAtExit, setOnboardingCardAtExit] = createSignal(0);149153 const [entries, setEntries] = createSignal<VaultEntry[]>([]);150154 const [selectedEntry, setSelectedEntry] = createSignal<VaultEntry | null>(null);151155 const [editEntry, setEditEntry] = createSignal<VaultEntry | undefined>(undefined);@@ -1492,15 +1496,19 @@14921496 when={onboarded() || hasVault()}14931497 fallback={14941498 <Onboarding1499+ initialCard={onboardingCardAtExit()}14951500 onComplete={() => {1501+ setOnboardingCardAtExit(ONBOARDING_LAST_CARD);14961502 setStartOnboardingRestore(false);14971503 setOnboarded(true);14981504 }}14991505 onRestore={() => {1506+ setOnboardingCardAtExit(ONBOARDING_LAST_CARD);15001507 setStartOnboardingRestore(true);15011508 setOnboarded(true);15021509 }}15031510 onAdopt={() => {1511+ setOnboardingCardAtExit(ONBOARDING_LAST_CARD);15041512 setStartOnboardingRestore(false);15051513 setOnboarded(true);15061514 setView("adopting");src/popup/styles/popup.css+4 −0
@@ -6933,6 +6933,7 @@69336933 flex-direction: column;69346934 gap: 10px;69356935 padding: 22px 20px;6936+ justify-content: center;69366937 border-radius: 16px;69376938 border: 1px solid var(--peach-t3);69386939 background:@@ -6966,6 +6967,9 @@69666967 gap: 6px;69676968 padding-top: 14px;69686969 padding-bottom: 12px;6970+ /* Final card content overflows the popup — keep it anchored at the top so6971+ the choice rows and nav stay reachable without clipping the headline. */6972+ justify-content: flex-start;69696973 }69706974 69716975 .peach-onboarding-card--final .peach-onboarding-body {src/popup/views/AdoptVaultView.test.tsx+21 −1
@@ -3,7 +3,7 @@33 import type { JSX } from "solid-js";44 import { readFileSync } from "node:fs";55 import { afterEach, describe, expect, it, vi } from "vitest";6-import { Onboarding } from "./Onboarding";6+import { ONBOARDING_LAST_CARD, Onboarding } from "./Onboarding";77 import { AdoptVaultView } from "./AdoptVaultView";88 import { flushView, inputText, renderView } from "./view-test-utils";99 @@ -67,6 +67,26 @@6767 expect(view.container.textContent).toContain("Restore a backup");6868 button(view.container, "Pair with another device").click();6969 expect(onAdopt).toHaveBeenCalledTimes(1);70+ });71+72+ it("T-A21c: resumes on the final card when re-entered from a leave flow", async () => {73+ const onComplete = vi.fn();74+ const view = mounted(() => (75+ <Onboarding onComplete={onComplete} onRestore={() => {}} onAdopt={() => {}} initialCard={ONBOARDING_LAST_CARD} />76+ ));77+78+ // Landing straight on the final card shows the exit actions, not card 0.79+ expect(view.container.textContent).toContain("Create vault");80+ expect(view.container.textContent).toContain("Already using Peach?");81+ expect(view.container.textContent).not.toContain("Two keys.");82+83+ // Back (matched by class — the "Restore a backup" label also contains the84+ // substring "back") still responds and steps to the previous card.85+ (view.container.querySelector(".peach-onboarding-back") as HTMLButtonElement).click();86+ expect(view.container.querySelector(".peach-onboarding-card")?.classList.contains("is-exiting")).toBe(true);87+ await new Promise((r) => setTimeout(r, 300));88+ expect(view.container.textContent).toContain("Two keys.");89+ expect(view.container.textContent).not.toContain("Create vault");7090 });7191 7292 it("T-A21b: create-vault is the nav call-to-action; a final-card tap does not silently create", () => {src/popup/views/Onboarding.tsx+10 −1
@@ -37,6 +37,11 @@3737 3838 const CARDS: OnboardingCard[] = BASE_CARDS;3939 40+/** Index of the card that carries the create/adopt/restore actions. Converges41+ * every path out of onboarding, so a "back" from those flows should resume42+ * here — not rewind to card 0. */43+export const ONBOARDING_LAST_CARD = CARDS.length - 1;44+4045 const CARD_EXIT_MS = 120;4146 const CARD_ENTER_GAP_MS = 60;4247 @@ -44,10 +49,14 @@4449 onComplete: () => void | Promise<void>;4550 onRestore?: () => void | Promise<void>;4651 onAdopt?: () => void | Promise<void>;52+ /** Card index to start on. App passes ONBOARDING_LAST_CARD when returning53+ * from the create-vault/adopt/restore flows so the user lands back where54+ * they left instead of restarting at the first card. */55+ initialCard?: number;4756 }4857 4958 export function Onboarding(props: OnboardingProps) {50- const [card, setCard] = createSignal(0);59+ const [card, setCard] = createSignal(props.initialCard ?? 0);5160 const [exiting, setExiting] = createSignal(false);5261 const [completing, setCompleting] = createSignal(false);5362