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
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/Export.tsx+39 −8src/popup/views/Import.tsx+26 −2
manifest.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": {

view file

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

view file

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": {

view file

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>

view file

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"

view file