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
7 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 −1
manifest.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": {

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.8",5+  "version": "4.10.9",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.8",3+  "version": "4.10.9",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

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");

view file

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 {

view file

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", () => {

view file

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 

view file