4d2b19516c66
fix: Update onboarding features and Codex restore (v3.6.64)
- time
- 2026-05-20 12:02 UTC · 5 months ago
- machine
- koh2
- parent
- d99308b4fb2a
↓ peach-browser-4d2b1951.tar.gzatlas at this save
files changed9 files
manifest.firefox.json+1 −1manifest.json+1 −1package-lock.json+2 −2package.json+1 −1src/popup/components/RuneBackground.tsx+6 −1src/popup/styles/popup.css+27 −1src/popup/views/Lock.tsx+29 −3src/popup/views/Onboarding.tsx+2 −3src/popup/views/contracts.test.tsx+38 −0manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach",5- "version": "3.6.63",5+ "version": "3.6.64",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "3.6.63",4+ "version": "3.6.64",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package-lock.json+2 −2
@@ -1,12 +1,12 @@11 {22 "name": "peach",3- "version": "3.6.63",3+ "version": "3.6.64",44 "lockfileVersion": 3,55 "requires": true,66 "packages": {77 "": {88 "name": "peach",9- "version": "3.6.63",9+ "version": "3.6.64",1010 "dependencies": {1111 "@bokuweb/zstd-wasm": "^0.0.27",1212 "@stablelib/blake2s": "^2.0.1",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.6.63",3+ "version": "3.6.64",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/popup/components/RuneBackground.tsx+6 −1
@@ -96,7 +96,12 @@9696 };9797 document.addEventListener("visibilitychange", onVisibility);9898 99- document.fonts.load(`${FONT_SIZE}px PeachScript`).finally(start);99+ const fonts = document.fonts;100+ if (fonts) {101+ fonts.load(`${FONT_SIZE}px PeachScript`).finally(start);102+ } else {103+ start();104+ }100105 101106 onCleanup(() => {102107 stop();src/popup/styles/popup.css+27 −1
@@ -3777,11 +3777,37 @@37773777 37783778 .peach-rt-symbols,37793779 .peach-rt-csv,3780-.peach-rt-phone {3780+.peach-rt-phone,3781+.peach-restore-codex {37813782 display: flex;37823783 flex-direction: column;37833784 gap: 8px;37843785 overflow: hidden;3786+}3787+3788+.peach-restore-codex {3789+ min-height: 0;3790+ justify-content: center;3791+}3792+3793+.peach-restore-codex-title {3794+ margin: 0;3795+ font-family: var(--font-display);3796+ font-size: 1rem;3797+ font-style: italic;3798+ color: var(--peach-t0);3799+}3800+3801+.peach-restore-codex-copy {3802+ margin: 0;3803+ font-size: 0.75rem;3804+ line-height: 1.5;3805+ color: var(--peach-t1);3806+}3807+3808+.peach-restore-codex-button {3809+ width: 100%;3810+ justify-content: center;37853811 }37863812 37873813 .peach-rt-submit {src/popup/views/Lock.tsx+29 −3
@@ -256,6 +256,7 @@256256 <Match when={restoreSurface() === "restore"}>257257 <RestoreTabView258258 hasVault={props.hasVault}259+ onOpenCodexRestore={props.onOpenCodexRestore}259260 onBack={() => setRestoreSurface(null)}260261 onUnlock={async () => {261262 setRestoreSurface(null);@@ -534,16 +535,17 @@534535 535536 // ── Restore tab view ───────────────────────────────────────────────────────536537 537-type RestoreTab = "symbols" | "csv";538+type RestoreTab = "symbols" | "codex" | "csv";538539 539540 function tabLabel(tab: RestoreTab): string {540541 return (541- { symbols: "PeachScript", csv: "CSV" }[tab]542+ { symbols: "PeachScript", codex: "Codex", csv: "CSV" }[tab]542543 );543544 }544545 545546 function RestoreTabView(props: {546547 hasVault: boolean;548+ onOpenCodexRestore?: () => void;547549 onBack: () => void;548550 onUnlock: () => void | Promise<void>;549551 }) {@@ -582,7 +584,7 @@582584 583585 <div class="peach-newvault-card peach-restore-tabcard">584586 <div class="peach-restore-tabs" role="tablist">585- <For each={["symbols", "csv"] as RestoreTab[]}>587+ <For each={["symbols", "codex", "csv"] as RestoreTab[]}>586588 {(tab) => (587589 <button588590 class="peach-restore-tab"@@ -600,6 +602,9 @@600602 <Show when={activeTab() === "symbols"}>601603 <RestoreSymbols hasVault={props.hasVault} onSuccess={handleRestoreSuccess} />602604 </Show>605+ <Show when={activeTab() === "codex"}>606+ <RestoreCodex onOpen={() => props.onOpenCodexRestore?.()} />607+ </Show>603608 <Show when={activeTab() === "csv"}>604609 <RestoreCSV hasVault={props.hasVault} onSuccess={handleRestoreSuccess} />605610 </Show>@@ -627,6 +632,27 @@627632 </div>628633 </Show>629634 </section>635+ );636+}637+638+// ── Peach Codex tab ────────────────────────────────────────────────────────639+640+function RestoreCodex(props: { onOpen: () => void }) {641+ return (642+ <div class="peach-restore-codex">643+ <p class="peach-restore-codex-title">Restore from Peach Codex</p>644+ <p class="peach-restore-codex-copy">645+ Scan printed Codex pages or import the Codex PDF. Codex restore is646+ always available, even before you create a vault.647+ </p>648+ <button649+ type="button"650+ class="peach-btn peach-btn--accent peach-btn--md peach-restore-codex-button"651+ onClick={props.onOpen}652+ >653+ open Codex restore →654+ </button>655+ </div>630656 );631657 }632658 src/popup/views/Onboarding.tsx+2 −3
@@ -28,9 +28,8 @@2828 {2929 headline: "Everything\nyou need.",3030 body: [31- "Passwords, passkeys, cards, secure notes, two-factor codes.",32- "Autofills on every site. Generates strong passwords. Detects breaches.",33- "Free forever. Pro adds sync, passkeys, and advanced vault health.",31+ "Free includes passwords, cards, secure notes, two-factor codes, autofill, strong passwords, and breach checks.",32+ "Pro adds encrypted sync, Peach Codex paper restore, passkeys, mobile apps, advanced vault health, canary entries, and duress tools.",3433 ],3534 warning: null,3635 },src/popup/views/contracts.test.tsx+38 −0
@@ -257,6 +257,44 @@257257 expect(container.textContent).toContain("CSV");258258 });259259 260+ it("Lock onboarding restore options include Peach Codex", async () => {261+ const onOpenCodexRestore = vi.fn();262+ const container = mount(263+ <LockView264+ hasVault={false}265+ ready={true}266+ onUnlock={vi.fn()}267+ onOpenCodexRestore={onOpenCodexRestore}268+ />269+ );270+271+ const restoreButton = Array.from(container.querySelectorAll("button")).find(272+ (candidate) => candidate.textContent?.includes("restore from backup")273+ ) as HTMLButtonElement;274+ restoreButton.click();275+ await flushView();276+277+ expect(container.textContent).toContain("PeachScript");278+ expect(container.textContent).toContain("Codex");279+ expect(container.textContent).toContain("CSV");280+281+ const codexTab = Array.from(container.querySelectorAll("button")).find(282+ (candidate) => candidate.textContent?.trim() === "Codex"283+ ) as HTMLButtonElement;284+ codexTab.click();285+ await flushView();286+287+ expect(container.textContent).toContain("Restore from Peach Codex");288+ expect(container.textContent).toContain("Scan printed Codex pages");289+290+ const openCodexButton = Array.from(container.querySelectorAll("button")).find(291+ (candidate) => candidate.textContent?.trim() === "open Codex restore →"292+ ) as HTMLButtonElement;293+ openCodexButton.click();294+295+ expect(onOpenCodexRestore).toHaveBeenCalledTimes(1);296+ });297+260298 it("Settings requires current, new, and confirm passwords before enabling save", async () => {261299 sendMessageMock.mockResolvedValue({ ok: true, data: null });262300 const container = mount(