b83ac298112c
show animated RuneCanvas on the PeachScript import form when valid script is pasted, swapping out the textarea; bump 4.10.26
- time
- 2026-08-21 02:16 UTC · 2 months ago
- machine
- koh2
- agent
- qwen3.8-max
- session
- peachscript import rune canvas
- parent
- 9a5ff2570867
↓ peach-browser-b83ac298.tar.gzatlas at this save
files changed6 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+7 −0src/popup/views/Lock.tsx+57 −41tests/lock-screen.spec.ts+8 −4manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.10.25",4+ "version": "4.10.26",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -2,7 +2,7 @@22 "manifest_version": 3,33 "minimum_chrome_version": "116",44 "name": "Peach Password Manager",5- "version": "4.10.25",5+ "version": "4.10.26",66 "description": "Zero-knowledge password manager",77 "action": {88 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "4.10.25",3+ "version": "4.10.26",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/popup/styles/popup.css+7 −0
@@ -4083,6 +4083,13 @@40834083 display: block;40844084 }40854085 4086+/* Animated rune preview shown once valid PeachScript is pasted — mirrors the4087+ export screen's RuneCanvas. */4088+.peach-restore-rune-preview {4089+ overflow: hidden;4090+ border-radius: 8px;4091+}4092+40864093 /* ── Codex source ─────────────────────────────────────────────────────── */40874094 40884095 .peach-restore-codex-copy {src/popup/views/Lock.tsx+57 −41
@@ -4,6 +4,7 @@44 import { RecoveryWordEntryView } from "./RecoveryWordEntry";55 import { RecoveryWordDisplayView } from "./RecoveryWordDisplay";66 import { RuneBackground } from "../components/RuneBackground";7+import { RuneCanvas } from "../components/RuneCanvas";78 import { storageGet, storageRemove, storageSet } from "../../shared/storage";89 import { isPeachScript, decodePeachScript, normalisePeachScript } from "../../crypto/peachScript";910 import { parsePayload } from "../../crypto/peachPayload";@@ -1085,48 +1086,63 @@10851086 />10861087 </Show>10871088 <Show when={animating() === null}>1088- <textarea1089- class="peach-restore-script"1090- placeholder="paste peachscript…"1091- rows={3}1092- aria-label="Paste your PeachScript backup"1093- value={scriptInput()}1094- onInput={(e) => setScriptInput((e.target as HTMLTextAreaElement).value)}1095- />1096- <div class="peach-restore-or" aria-hidden="true">or</div>1097- <label1098- class="peach-btn peach-btn--ghost peach-btn--sm peach-restore-file-btn"1099- onClick={(e) => {1100- // In the detached window the dialog can open directly — let the1101- // label's real user gesture activate the input. In the action1102- // popup we must detach first (the dialog would dismiss the popup).1103- if (restoreDeepLinkActive(RESTORE_DEEP_LINKS.symbols)) return;1104- e.preventDefault();1105- void detachForFilePicker(RESTORE_DEEP_LINKS.symbols).then((detached) => {1106- if (!detached) peachFileRef?.click();1107- });1108- }}1089+ <Show1090+ when={scriptValid()}1091+ fallback={1092+ <>1093+ <textarea1094+ class="peach-restore-script"1095+ placeholder="paste peachscript…"1096+ rows={3}1097+ aria-label="Paste your PeachScript backup"1098+ value={scriptInput()}1099+ onInput={(e) => setScriptInput((e.target as HTMLTextAreaElement).value)}1100+ />1101+ <div class="peach-restore-or" aria-hidden="true">or</div>1102+ <label1103+ class="peach-btn peach-btn--ghost peach-btn--sm peach-restore-file-btn"1104+ onClick={(e) => {1105+ if (restoreDeepLinkActive(RESTORE_DEEP_LINKS.symbols)) return;1106+ e.preventDefault();1107+ void detachForFilePicker(RESTORE_DEEP_LINKS.symbols).then((detached) => {1108+ if (!detached) peachFileRef?.click();1109+ });1110+ }}1111+ >1112+ open .peach file1113+ <input1114+ ref={(el) => { peachFileRef = el; }}1115+ type="file"1116+ accept=".peach,text/plain"1117+ style="display:none"1118+ onChange={async (e) => {1119+ const file = (e.currentTarget as HTMLInputElement).files?.[0];1120+ if (!file) return;1121+ try {1122+ setScriptInput(await file.text());1123+ } catch {1124+ setError("Couldn't read file.");1125+ } finally {1126+ (e.currentTarget as HTMLInputElement).value = "";1127+ }1128+ }}1129+ />1130+ </label>1131+ <p class="peach-restore-drop-hint">or drop the file anywhere here</p>1132+ </>1133+ }11091134 >1110- open .peach file1111- <input1112- ref={(el) => { peachFileRef = el; }}1113- type="file"1114- accept=".peach,text/plain"1115- style="display:none"1116- onChange={async (e) => {1117- const file = (e.currentTarget as HTMLInputElement).files?.[0];1118- if (!file) return;1119- try {1120- setScriptInput(await file.text());1121- } catch {1122- setError("Couldn't read file.");1123- } finally {1124- (e.currentTarget as HTMLInputElement).value = "";1125- }1126- }}1127- />1128- </label>1129- <p class="peach-restore-drop-hint">or drop the file anywhere here</p>1135+ <div class="peach-restore-rune-preview">1136+ <RuneCanvas runeString={scriptInput()} width={256} height={120} />1137+ </div>1138+ <button1139+ type="button"1140+ class="peach-restore-picker-back"1141+ onClick={() => { setScriptInput(""); setError(null); }}1142+ >1143+ ‹ paste again1144+ </button>1145+ </Show>11301146 <div class="peach-newvault-field">11311147 <label class="peach-label" for="restore-backup-pass">11321148 backup passwordtests/lock-screen.spec.ts+8 −4
@@ -367,10 +367,14 @@367367 });368368 await detached.waitForSelector(".peach-restore-script", { timeout: 15000 });369369 expect(detached.url()).toContain("restore=peachscript");370- await detached.locator(".peach-restore-file-btn").click();371- // The runes land in the script field, pass PeachScript validation, and a372- // passphrase unlocks the restore button.373- await expect(detached.locator(".peach-restore-script")).not.toHaveValue("", { timeout: 15000 });370+ // autoPick (best-effort) opens the picker ~50ms after mount; if the371+ // platform blocks it, click the button with a real gesture instead.372+ try {373+ await expect(detached.locator(".peach-rune-canvas")).toBeVisible({ timeout: 3000 });374+ } catch {375+ try { await detached.locator(".peach-restore-file-btn").click({ timeout: 5000 }); } catch { /* autoPick raced */ }376+ }377+ await expect(detached.locator(".peach-rune-canvas")).toBeVisible({ timeout: 15000 });374378 await detached.locator("#restore-backup-pass").fill("backup-passphrase-probe");375379 await expect(detached.locator(".peach-restore-submit")).toBeEnabled({ timeout: 15000 });376380 await ctx.close();