a86783166653
fix email field dropped in sanitizeEntryDraft; identity suggestions include entry.email; aztec retry on content mismatch with diagnostic logging; live Aztec gallery on generating screen (v3.6.56)
- time
- 2026-05-20 02:55 UTC · 5 months ago
- machine
- koh2
- parent
- 3d77e1828fa3
↓ peach-browser-a8678316.tar.gzatlas at this save
files changed8 files
manifest.firefox.json+1 −1manifest.json+1 −1src/background/vault.ts+17 −8src/popup/codex/aztec.ts+26 −9src/popup/codex/generate.ts+10 −1src/popup/styles/codex.css+23 −0src/popup/views/CodexGenerateView.tsx+35 −2src/shared/codex-types.ts+3 −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.55",5+ "version": "3.6.56",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.55",4+ "version": "3.6.56",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",src/background/vault.ts+17 −8
@@ -1336,17 +1336,25 @@13361336 const usernameMap = new Map<string, { lastUsed: number; count: number; domain: string }>();13371337 13381338 for (const entry of active) {1339- const value = entry.username?.trim();1340- if (!value) continue;1341- const map = EMAIL_RE.test(value) ? emailMap : usernameMap;1342- const existing = map.get(value);13431339 const domain = extractDomain(entry.url ?? "") ?? "";13441340 const modifiedAt = entry.modifiedAt ?? entry.createdAt ?? 0;1345- if (!existing || modifiedAt > existing.lastUsed) {1346- map.set(value, { lastUsed: modifiedAt, count: (existing?.count ?? 0) + 1, domain });1347- } else {1348- map.set(value, { ...existing, count: existing.count + 1 });1341+1342+ function tally(map: Map<string, { lastUsed: number; count: number; domain: string }>, v: string) {1343+ const existing = map.get(v);1344+ if (!existing || modifiedAt > existing.lastUsed) {1345+ map.set(v, { lastUsed: modifiedAt, count: (existing?.count ?? 0) + 1, domain });1346+ } else {1347+ map.set(v, { ...existing, count: existing.count + 1 });1348+ }13491349 }1350+1351+ const uVal = entry.username?.trim();1352+ if (uVal) tally(EMAIL_RE.test(uVal) ? emailMap : usernameMap, uVal);1353+1354+ // Also count the dedicated email field — entries created via the inline1355+ // popup new-entry form store email here rather than in username.1356+ const eVal = entry.email?.trim();1357+ if (eVal) tally(emailMap, eVal);13501358 }13511359 13521360 const toIdentity = (type: "email" | "username") =>@@ -1798,6 +1806,7 @@17981806 type,17991807 title,18001808 username: type === "login" ? entry.username?.trim() ?? "" : undefined,1809+ email: type === "login" ? entry.email?.trim() || undefined : undefined,18011810 password: type === "login" ? entry.password : undefined,18021811 url: type === "login" ? entry.url?.trim() ?? "" : undefined,18031812 additionalUrls: type === "login" ? cleanAdditionalUrls(entry.additionalUrls) : undefined,src/popup/codex/aztec.ts+26 −9
@@ -121,6 +121,11 @@121121 // image edge with no quiet zone, causing spurious AZTEC_ROUNDTRIP_FAILED.122122 padding: 2,123123 monochrome: true,124+ // Explicit white background so the PNG is fully opaque. Without this,125+ // bwip-js clears the canvas with clearRect (transparent), and although126+ // decodeAztecFromPng fills white before drawing, the alpha compositing127+ // path is one more source of pixel-level variance on some runtimes.128+ backgroundcolor: "ffffff",124129 } as const;125130 126131 try {@@ -254,24 +259,36 @@254259 return image;255260 }256261 257- // @zxing occasionally returns null on a valid barcode due to transient258- // rasterisation issues. Five attempts avoids aborting a full Codex build259- // for a momentary DOM hiccup. Content-mismatch is never retried.262+ // @zxing occasionally returns null or wrong content on a valid barcode due263+ // to transient rasterisation variance. Retry up to 5 times on any failure —264+ // break immediately on a confirmed correct decode. Track the last non-null265+ // wrong result so we can surface diagnostic detail if all attempts fail.260266 let decoded: string | null = null;267+ let lastMismatch: string | null = null;261268 for (let attempt = 0; attempt < 5; attempt++) {262- decoded = await decodeAztecFromPng(image.pngBytes);263- if (decoded !== null) break;269+ const candidate = await decodeAztecFromPng(image.pngBytes);270+ if (candidate === envelopeJson) { decoded = candidate; break; }271+ if (candidate !== null) lastMismatch = candidate;264272 }265- if (decoded === null) {273+ if (decoded === null && lastMismatch === null) {266274 throw new CodexError(267275 "AZTEC_ROUNDTRIP_FAILED",268- `Chunk ${envelope.i} generated but cannot be decoded back after 3 attempts. This Codex cannot be trusted.`,276+ `Chunk ${envelope.i} generated but cannot be decoded back after 5 attempts. This Codex cannot be trusted.`,269277 );270278 }271- if (decoded !== envelopeJson) {279+ if (decoded === null) {280+ // lastMismatch is non-null — decoded something but wrong content.281+ const el = envelopeJson.length;282+ const dl = lastMismatch!.length;283+ let diffAt = -1;284+ for (let k = 0; k < Math.min(el, dl); k++) {285+ if (envelopeJson[k] !== lastMismatch![k]) { diffAt = k; break; }286+ }287+ const ctx = (s: string) => JSON.stringify(s.slice(Math.max(0, diffAt - 8), diffAt + 20));272288 throw new CodexError(273289 "AZTEC_CONTENT_MISMATCH",274- `Chunk ${envelope.i} decoded content does not match original envelope. Possible encoding corruption.`,290+ `Chunk ${envelope.i}: expected len=${el} decoded len=${dl} first diff at pos ${diffAt}. ` +291+ `expected: ${ctx(envelopeJson)} decoded: ${ctx(lastMismatch!)}`,275292 );276293 }277294 return image;src/popup/codex/generate.ts+10 −1
@@ -134,8 +134,17 @@134134 onProgress?.("aztec", i, envelopes.length);135135 const img = await generateVerifiedAztec(envelopes[i]!);136136 aztecPngs.push(img.pngBytes);137+ // Pass a blob URL so the view can render a live gallery of real codes.138+ // Only created when OffscreenCanvas is available (same guard as round-trip).139+ // Copy into a fresh Uint8Array to satisfy Blob's ArrayBuffer type constraint140+ // (img.pngBytes may be backed by a SharedArrayBuffer in some type environments).141+ let blobUrl: string | undefined;142+ if (typeof URL !== "undefined" && typeof Blob !== "undefined") {143+ const owned = new Uint8Array(img.pngBytes);144+ blobUrl = URL.createObjectURL(new Blob([owned], { type: "image/png" }));145+ }146+ onProgress?.("aztec", i + 1, envelopes.length, blobUrl);137147 }138- onProgress?.("aztec", envelopes.length, envelopes.length);139148 140149 // STEP 7 — full reconstruction simulation (lose the maximum tolerated141150 // number of envelopes per stripe, then rebuild end-to-end)src/popup/styles/codex.css+23 −0
@@ -432,3 +432,26 @@432432 padding: 16px 0;433433 align-items: stretch;434434 }435+436+/* ── Live Aztec gallery (generating step) ─────────────────────────────────── */437+438+.peach-codex-aztec-gallery {439+ display: grid;440+ grid-template-columns: repeat(6, 1fr);441+ gap: 4px;442+ padding: 12px 12px 0;443+}444+445+.peach-codex-aztec-tile {446+ width: 100%;447+ aspect-ratio: 1;448+ display: block;449+ border-radius: 2px;450+ image-rendering: pixelated;451+ animation: codex-tile-in 280ms cubic-bezier(0.16, 1, 0.3, 1) both;452+}453+454+@keyframes codex-tile-in {455+ from { opacity: 0; transform: scale(0.82); }456+ to { opacity: 1; transform: scale(1); }457+}src/popup/views/CodexGenerateView.tsx+35 −2
@@ -14,7 +14,7 @@1414 // Restoration (CodexRestoreView) stays free — a printed Codex must restore1515 // regardless of license state.1616 17-import { createSignal, createMemo, For, onMount, Show } from "solid-js";17+import { createSignal, createMemo, For, onMount, onCleanup, Show } from "solid-js";1818 import { sendMessage } from "../../shared/bridge";1919 import { isNativePlatform } from "../../shared/native";2020 import { RecoveryRuneBackground } from "../components/RecoveryRuneBackground";@@ -73,7 +73,15 @@7373 7474 const [genError, setGenError] = createSignal<string | null>(null);7575 const [completed, setCompleted] = createSignal<CodexManifest | null>(null);76+ // Live gallery of real Aztec PNGs as they're generated — keep the last 3677+ // (one page worth) so the display fills without using excessive memory.78+ const [aztecGallery, setAztecGallery] = createSignal<string[]>([]);79+ const GALLERY_MAX = 36;7680 81+ onCleanup(() => {82+ for (const url of aztecGallery()) URL.revokeObjectURL(url);83+ });84+7785 onMount(async () => {7886 if (!props.isPro) {7987 props.onUpgradePro();@@ -244,10 +252,19 @@244252 }245253 }246254 247- const onProgress: CodexProgress = (s, current, total) => {255+ const onProgress: CodexProgress = (s, current, total, imageUrl) => {248256 setProgressStep(s);249257 setProgressCurrent(current ?? 0);250258 setProgressTotal(total ?? 0);259+ if (s === "aztec" && imageUrl) {260+ setAztecGallery(prev => {261+ if (prev.length >= GALLERY_MAX) {262+ URL.revokeObjectURL(prev[0]!);263+ return [...prev.slice(1), imageUrl];264+ }265+ return [...prev, imageUrl];266+ });267+ }251268 };252269 253270 async function handleGenerate() {@@ -255,6 +272,7 @@255272 if (!snap || !allCorrect() || step() === "generating") return;256273 257274 setGenError(null);275+ setAztecGallery([]);258276 setStep("generating");259277 setProgressStep("compress");260278 @@ -501,6 +519,21 @@501519 />502520 </div>503521 </div>522+523+ <Show when={aztecGallery().length > 0}>524+ <div class="peach-codex-aztec-gallery">525+ <For each={aztecGallery()}>526+ {(url) => (527+ <img528+ class="peach-codex-aztec-tile"529+ src={url}530+ alt=""531+ aria-hidden="true"532+ />533+ )}534+ </For>535+ </div>536+ </Show>504537 </Show>505538 506539 <Show when={step() === "done"}>src/shared/codex-types.ts+3 −0
@@ -106,11 +106,14 @@106106 /**107107 * Progress reporter for the generator. Called at named milestones; for the108108 * Aztec step `current`/`total` advance per code so the UI can render a bar.109+ * `imageUrl` is a blob URL for the just-generated Aztec PNG — only present110+ * during the "aztec" step so the UI can show a live gallery of real codes.109111 */110112 export type CodexProgress = (111113 step: "compress" | "erasure" | "aztec" | "verify" | "pdf",112114 current?: number,113115 total?: number,116+ imageUrl?: string,114117 ) => void;115118 116119 /** Payload sent from the popup to the SW to perform the actual vault restoration. */