f6e79034f2cd
add Download .peach button to PeachScript export, accept .peach file in PeachScript import (works in both merge and replace modes since Lock-screen restore uses the same view), bump v2.11.4
- time
- 2026-04-29 00:44 UTC · 6 months ago
- machine
- koh2
- parent
- 6dd10798c332
↓ peach-browser-f6e79034.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/Export.tsx+39 −8src/popup/views/Import.tsx+26 −2manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.11.3",4+ "version": "2.11.4",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",4- "version": "2.11.3",4+ "version": "2.11.4",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": "2.11.3",3+ "version": "2.11.4",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/popup/views/Export.tsx+39 −8
@@ -154,6 +154,27 @@154154 }155155 }156156 157+ function handleDownload() {158+ const s = script();159+ if (!s) return;160+ // .peach file = the same PeachScript text, in a single file. The PUA161+ // codepoints render as runes only when paired with PeachScript-Regular.otf;162+ // outside Peach the file looks like opaque bytes, which is the point.163+ const blob = new Blob([s], { type: "application/x-peach;charset=utf-8" });164+ const url = URL.createObjectURL(blob);165+ const stamp = new Date()166+ .toISOString()167+ .replace(/[:T]/g, "-")168+ .replace(/\..*$/, "");169+ const a = document.createElement("a");170+ a.href = url;171+ a.download = `peach-vault-${stamp}.peach`;172+ document.body.appendChild(a);173+ a.click();174+ document.body.removeChild(a);175+ setTimeout(() => URL.revokeObjectURL(url), 5000);176+ }177+157178 return (158179 <>159180 <OverlayHeader title="peachscript" onBack={props.onBack} />@@ -225,15 +246,25 @@225246 {stats().passkeys > 0 ? ` · ${stats().passkeys} passkey${stats().passkeys === 1 ? "" : "s"}` : ""}226247 {stats().notes > 0 ? ` · ${stats().notes} note${stats().notes === 1 ? "" : "s"}` : ""}227248 </p>228- <button229- class="peach-btn peach-btn--subtle peach-btn--sm"230- onClick={() => void handleCopy()}231- >232- {copied() ? "copied ✓" : "Copy PeachScript"}233- </button>249+ <div style="display:flex;gap:8px;">250+ <button251+ class="peach-btn peach-btn--subtle peach-btn--sm"252+ style="flex:1;"253+ onClick={() => void handleCopy()}254+ >255+ {copied() ? "copied ✓" : "Copy"}256+ </button>257+ <button258+ class="peach-btn peach-btn--subtle peach-btn--sm"259+ style="flex:1;"260+ onClick={handleDownload}261+ >262+ Download .peach263+ </button>264+ </div>234265 <p class="peach-export-card-copy" style="margin-top:8px">235- Paste this into Peach on your other device to restore your236- vault. It won't work anywhere else.266+ Paste the script — or import the .peach file — into Peach on267+ your other device to restore your vault. Won't work anywhere else.237268 </p>238269 </div>239270 </Show>src/popup/views/Import.tsx+26 −2
@@ -229,8 +229,8 @@229229 <p class="peach-settings-label">paste peachscript</p>230230 <p class="peach-export-card-copy">231231 {isOnboarding232- ? "Paste a PeachScript backup from another Peach device to restore your vault."233- : "We'll merge the entries into your existing vault."}232+ ? "Paste a PeachScript backup from another Peach device to restore your vault — or load a .peach file from disk."233+ : "We'll merge the entries into your existing vault. You can paste PeachScript or load a .peach file."}234234 </p>235235 <textarea236236 class="peach-script-input"@@ -238,6 +238,30 @@238238 value={scriptInput()}239239 onInput={(e) => setScriptInput((e.target as HTMLTextAreaElement).value)}240240 />241+ <label242+ class="peach-btn peach-btn--ghost peach-btn--sm"243+ style="display:inline-flex;align-items:center;justify-content:center;cursor:pointer;"244+ >245+ Load .peach file246+ <input247+ type="file"248+ accept=".peach,application/x-peach,text/plain"249+ style="display:none;"250+ onChange={async (event) => {251+ const input = event.currentTarget;252+ const file = input.files?.[0];253+ if (!file) return;254+ try {255+ const text = await file.text();256+ setScriptInput(text);257+ } catch (err) {258+ setError(err instanceof Error ? err.message : "Couldn't read file.");259+ } finally {260+ input.value = "";261+ }262+ }}263+ />264+ </label>241265 <div class="peach-settings-stack peach-export-password-stack">242266 <input243267 class="peach-settings-input"