c700871e2778
feat: Clean up existing-user Sync onboarding (v4.3.1)
- time
- 2026-07-27 20:01 UTC · 3 months ago
- machine
- koh2
- parent
- daf6d2a04ad2
↓ peach-browser-c700871e.tar.gzatlas at this save
files changed9 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+15 −3src/popup/styles/popup.css+23 −2src/popup/views/AdoptVaultView.test.tsx+6 −4src/popup/views/Lock.tsx+5 −1src/popup/views/Onboarding.tsx+14 −7src/popup/views/contracts.test.tsx+18 −1manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.3.0",4+ "version": "4.3.1",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.3.0",4+ "version": "4.3.1",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": "4.3.0",3+ "version": "4.3.1",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/popup/App.tsx+15 −3
@@ -144,6 +144,7 @@144144 const [contentTransitioning, setContentTransitioning] = createSignal(false);145145 const [hasVault, setHasVault] = createSignal(false);146146 const [onboarded, setOnboarded] = createSignal(true);147+ const [startOnboardingRestore, setStartOnboardingRestore] = createSignal(false);147148 const [entries, setEntries] = createSignal<VaultEntry[]>([]);148149 const [selectedEntry, setSelectedEntry] = createSignal<VaultEntry | null>(null);149150 const [editEntry, setEditEntry] = createSignal<VaultEntry | undefined>(undefined);@@ -1389,9 +1390,16 @@13891390 when={onboarded() || hasVault()}13901391 fallback={13911392 <Onboarding1392- onComplete={() => { setOnboarded(true); }}1393- onRestore={() => { setOnboarded(true); }}1393+ onComplete={() => {1394+ setStartOnboardingRestore(false);1395+ setOnboarded(true);1396+ }}1397+ onRestore={() => {1398+ setStartOnboardingRestore(true);1399+ setOnboarded(true);1400+ }}13941401 onAdopt={() => {1402+ setStartOnboardingRestore(false);13951403 setOnboarded(true);13961404 setView("adopting");13971405 }}@@ -1403,6 +1411,7 @@14031411 ready={bootState() === "ready"}14041412 isPro={proStatus()}14051413 forceRecoveryWords={forceRecoveryWords()}1414+ initialRestore={startOnboardingRestore()}14061415 onUnlock={() => {14071416 // RecoveryWordDisplay clears the storage flag before14081417 // calling onDone, so handleUnlock will see no pending@@ -1412,7 +1421,10 @@14121421 }}14131422 onBackToOnboarding={14141423 !hasVault()1415- ? () => setOnboarded(false)1424+ ? () => {1425+ setStartOnboardingRestore(false);1426+ setOnboarded(false);1427+ }14161428 : undefined14171429 }14181430 onOpenCodexRestore={!hasVault() ? () => setView("codex-restore") : undefined}src/popup/styles/popup.css+23 −2
@@ -6935,11 +6935,19 @@69356935 display: grid;69366936 grid-template-columns: 1fr;69376937 gap: 8px;6938- margin-top: 14px;69396938 }69406939 6940+.peach-onboarding-choice-heading {6941+ margin: 8px 0 -2px;6942+ color: var(--peach-t1);6943+ font-family: "IBM Plex Mono", monospace;6944+ font-size: 0.6875rem;6945+ letter-spacing: 0.04em;6946+ text-transform: uppercase;6947+}6948+69416949 .peach-onboarding-choice {6942- min-height: 34px;6950+ min-height: 48px;69436951 border: 1px solid color-mix(in srgb, var(--peach-t0) 16%, transparent);69446952 border-radius: 8px;69456953 padding: 8px 10px;@@ -6948,6 +6956,19 @@69486956 font-size: 0.76rem;69496957 font-weight: 700;69506958 text-align: left;6959+}6960+6961+.peach-onboarding-choice-label,6962+.peach-onboarding-choice-detail {6963+ display: block;6964+}6965+6966+.peach-onboarding-choice-detail {6967+ margin-top: 2px;6968+ color: var(--peach-t1);6969+ font-size: 0.625rem;6970+ font-weight: 400;6971+ line-height: 1.35;69516972 }69526973 69536974 .peach-onboarding-choice--adopt {src/popup/views/AdoptVaultView.test.tsx+6 −4
@@ -66,7 +66,7 @@6666 return rendered;6767 }6868 69- it("T-A21: Onboarding offers Create vault, Restore from backup, and I already have Peach", () => {69+ it("T-A21: Onboarding separates pairing from backup restore", () => {7070 vi.useFakeTimers();7171 const onComplete = vi.fn();7272 const onRestore = vi.fn();@@ -79,9 +79,11 @@7979 vi.advanceTimersByTime(220);8080 8181 expect(view.container.textContent).toContain("Create vault");82- expect(view.container.textContent).toContain("Restore from backup");83- expect(view.container.textContent).toContain("I already have Peach");84- button(view.container, "I already have Peach").click();82+ expect(view.container.textContent).toContain("Already using Peach?");83+ expect(view.container.textContent).toContain("Pair with another device");84+ expect(view.container.textContent).toContain("Scan a QR and confirm one 8-digit code.");85+ expect(view.container.textContent).toContain("Restore a backup");86+ button(view.container, "Pair with another device").click();8587 expect(onAdopt).toHaveBeenCalledTimes(1);8688 });8789 src/popup/views/Lock.tsx+5 −1
@@ -58,6 +58,8 @@5858 /** Optional: open the Peach Codex restore flow (camera scan of printed5959 * Codex pages). Surfaced as a small footer link on the new-vault screen. */6060 onOpenCodexRestore?: () => void;61+ /** Open directly on backup restore when onboarding routed the user there. */62+ initialRestore?: boolean;6163 }6264 6365 type RestoreSurface = "restore" | null;@@ -68,7 +70,9 @@6870 const [confirmPassword, setConfirmPassword] = createSignal("");6971 const [error, setError] = createSignal("");7072 const [loading, setLoading] = createSignal(false);71- const [restoreSurface, setRestoreSurface] = createSignal<RestoreSurface>(null);73+ const [restoreSurface, setRestoreSurface] = createSignal<RestoreSurface>(74+ props.initialRestore ? "restore" : null,75+ );7276 const [showPassword, setShowPassword] = createSignal(false);7377 const [recoverSurface, setRecoverSurface] = createSignal<RecoverSurface>(null);7478 // After vault creation succeeds, set this to show the recovery words screen.src/popup/views/Onboarding.tsx+14 −7
@@ -12,7 +12,7 @@1212 {1313 headline: "Your passwords\nlive here.\nNowhere else.",1414 body: [15- "No account. No server. No one but you can ever see your vault.",15+ "No account. No server ever accesses your vault. No one but you can see it.",1616 "Everything is encrypted on your device with a password only you know.",1717 ],1818 warning: null,@@ -119,26 +119,33 @@119119 </p>120120 </Show>121121 <Show when={card() === CARDS.length - 1}>122+ <p class="peach-onboarding-choice-heading">Already using Peach?</p>122123 <div class="peach-onboarding-choice-row">123124 <button124125 type="button"125- class="peach-onboarding-choice"126+ class="peach-onboarding-choice peach-onboarding-choice--adopt"126127 onClick={(e) => {127128 e.stopPropagation();128- void props.onRestore?.();129+ void props.onAdopt?.();129130 }}130131 >131- Restore from backup132+ <span class="peach-onboarding-choice-label">Pair with another device</span>133+ <span class="peach-onboarding-choice-detail">134+ Scan a QR and confirm one 8-digit code.135+ </span>132136 </button>133137 <button134138 type="button"135- class="peach-onboarding-choice peach-onboarding-choice--adopt"139+ class="peach-onboarding-choice"136140 onClick={(e) => {137141 e.stopPropagation();138- void props.onAdopt?.();142+ void props.onRestore?.();139143 }}140144 >141- I already have Peach145+ <span class="peach-onboarding-choice-label">Restore a backup</span>146+ <span class="peach-onboarding-choice-detail">147+ Use PeachScript, a .peach file, CSV, or Codex.148+ </span>142149 </button>143150 </div>144151 </Show>src/popup/views/contracts.test.tsx+18 −1
@@ -108,7 +108,7 @@108108 vi.unstubAllGlobals();109109 });110110 111- function mount(view: JSX.Element) {111+ function mount(view: JSX.Element | (() => JSX.Element)) {112112 const mounted = renderView(view);113113 disposers.push(mounted.dispose);114114 return mounted.container;@@ -404,6 +404,23 @@404404 openCodexButton.click();405405 406406 expect(onOpenCodexRestore).toHaveBeenCalledTimes(1);407+ });408+409+ it("Lock can open directly on backup restore from onboarding", async () => {410+ const container = mount(() => (411+ <LockView412+ hasVault={false}413+ ready={true}414+ onUnlock={vi.fn()}415+ initialRestore={true}416+ />417+ ));418+ await flushView();419+420+ expect(container.textContent).toContain("PeachScript");421+ expect(container.textContent).toContain("Codex");422+ expect(container.textContent).toContain("CSV");423+ expect(container.querySelector("#master-password")).toBeNull();407424 });408425 409426 it("HOLE 4: removing a device discloses that recovery words survive revocation", async () => {