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

view file

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",

view file

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",

view file

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}

view file

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 {

view file

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 

view file

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.

view file

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>

view file

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

view file