3e534fd912b8
fix: Restore inline entry editing and constraint legibility (v5.2.2)
- time
- 2026-09-06 19:05 UTC · 1 month ago
- machine
- koh2
- parent
- 3b0b39558887
↓ peach-browser-3e534fd9.tar.gzatlas at this save
files changed7 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/content.selector-ui.test.ts+36 −2src/content/inject.ts+150 −6src/popup/components/GeneratorDrawer.test.ts+7 −0src/popup/styles/popup.css+1 −1manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.2.1",4+ "version": "5.2.2",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": "5.2.1",5+ "version": "5.2.2",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": "5.2.1",3+ "version": "5.2.2",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/content/content.selector-ui.test.ts+36 −2
@@ -152,12 +152,25 @@152152 }153153 });154154 155- it("routes inline edit to the extension popup without requesting raw edit data", async () => {155+ it("opens the selected entry editor inside the inline helper", async () => {156156 const originalAttachShadow = Element.prototype.attachShadow;157157 const attachSpy = vi.spyOn(Element.prototype, "attachShadow").mockImplementation(function (this: Element) {158158 return originalAttachShadow.call(this, { mode: "open" });159159 });160160 const anchor = makeInput({ type: "password" });161+ setMessageResponder((message) =>162+ message.type === "GET_ENTRY_FOR_FILL"163+ ? {164+ ok: true,165+ data: {166+ title: "Acme",167+ username: "[email protected]",168+ password: "current-password",169+ url: "https://example.com",170+ },171+ }172+ : { ok: true, data: null },173+ );161174 162175 try {163176 const dismiss = showAutofillSelector(anchor, [ENTRIES[0]!], vi.fn(), { domain: "example.com" });@@ -167,9 +180,30 @@167180 edit?.click();168181 169182 await vi.waitFor(() => {170- expect(sentMessages).toContainEqual({ type: "OPEN_POPUP", payload: { entryId: "e1" } });183+ expect(host?.shadowRoot?.querySelector<HTMLInputElement>('.create-form-wrap input[type="password"]')?.value)184+ .toBe("current-password");171185 });186+ expect(sentMessages).toContainEqual({187+ type: "GET_ENTRY_FOR_FILL",188+ payload: { id: "e1", capability: undefined },189+ });190+ expect(sentMessages.some((message) => message.type === "OPEN_POPUP")).toBe(false);172191 expect(sentMessages.some((message) => message.type === "GET_ENTRY_FOR_EDIT")).toBe(false);192+193+ const title = host?.shadowRoot?.querySelector<HTMLInputElement>('.create-form-wrap input[type="text"]');194+ if (title) title.value = "Acme Updated";195+ host?.shadowRoot?.querySelector<HTMLButtonElement>(".create-form-wrap .save-btn")?.click();196+ await vi.waitFor(() => {197+ expect(sentMessages.find((message) => message.type === "UPDATE_ENTRY")).toMatchObject({198+ payload: {199+ id: "e1",200+ patch: { title: "Acme Updated", password: "current-password" },201+ },202+ });203+ });204+ const patch = sentMessages.find((message) => message.type === "UPDATE_ENTRY")?.payload?.patch;205+ expect(patch).not.toHaveProperty("totp");206+ expect(patch).not.toHaveProperty("passwordHistory");173207 dismiss();174208 } finally {175209 attachSpy.mockRestore();src/content/inject.ts+150 −6
@@ -2982,13 +2982,157 @@29822982 container.appendChild(platter);29832983 }29842984 2985- /** Raw TOTP seeds and password history stay inside the extension popup. */2985+ /** Raw TOTP seeds, password history, and passkeys stay inside extension-owned UI. */29862986 async function renderInlineEdit(summary: SelectorEntry) {2987- await chrome.runtime.sendMessage({2988- type: "OPEN_POPUP",2989- payload: { entryId: summary.id },2990- }).catch(() => null);2991- dismiss();2987+ currentMode = "edit";2988+ container.innerHTML = "";2989+2990+ const wrap = document.createElement("div");2991+ wrap.className = "create-form-wrap";2992+ const head = document.createElement("div");2993+ head.className = "create-head";2994+ const back = document.createElement("button");2995+ back.className = "back-btn";2996+ back.type = "button";2997+ back.title = "Cancel";2998+ back.innerHTML = `<svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"><path d="M7 2L3 5.5L7 9"/></svg>`;2999+ back.addEventListener("click", () => renderSilverPlatter(summary));3000+ const label = document.createElement("div");3001+ label.className = "create-label";3002+ label.textContent = "Edit entry";3003+ head.append(back, label);3004+ wrap.appendChild(head);3005+3006+ const body = document.createElement("div");3007+ body.className = "create-body";3008+ const loading = document.createElement("div");3009+ loading.className = "empty-hint";3010+ loading.textContent = "loading…";3011+ body.appendChild(loading);3012+ wrap.appendChild(body);3013+ container.appendChild(wrap);3014+3015+ const response = await chrome.runtime.sendMessage({3016+ type: "GET_ENTRY_FOR_FILL",3017+ payload: { id: summary.id, capability: options.capability },3018+ }).catch(() => null) as {3019+ ok?: boolean;3020+ data?: {3021+ title?: string;3022+ username?: string;3023+ email?: string;3024+ password?: string;3025+ url?: string;3026+ notes?: string;3027+ };3028+ } | null;3029+ if (!response?.ok || !response.data) {3030+ loading.textContent = "couldn't load this entry";3031+ return;3032+ }3033+ body.innerHTML = "";3034+3035+ function field(labelText: string, value: string, type: "text" | "password" = "text") {3036+ const group = document.createElement("div");3037+ group.className = "f-group";3038+ const fieldLabel = document.createElement("div");3039+ fieldLabel.className = "f-label";3040+ fieldLabel.textContent = labelText;3041+ const input = document.createElement("input");3042+ input.className = "f-input";3043+ input.type = type;3044+ input.value = value;3045+ input.autocomplete = type === "password" ? "new-password" : "off";3046+ group.append(fieldLabel, input);3047+ body.appendChild(group);3048+ return input;3049+ }3050+3051+ const entry = response.data;3052+ const titleInput = field("title", entry.title ?? summary.title);3053+ const urlInput = field("url", entry.url ?? summary.url ?? "");3054+ const usernameInput = field("username", entry.username ?? summary.username ?? "");3055+ const emailInput = field("email", entry.email ?? summary.email ?? "");3056+ const passwordInput = field("password", entry.password ?? "", "password");3057+3058+ const generator = createSmartGenerator({3059+ policy: detectPasswordPolicy(),3060+ initialPassword: entry.password,3061+ onChange: (password) => { passwordInput.value = password; },3062+ });3063+ generator.root.hidden = true;3064+ const generatorToggle = document.createElement("button");3065+ generatorToggle.className = "gen-toggle-link";3066+ generatorToggle.type = "button";3067+ generatorToggle.textContent = "⚙ generate";3068+ generatorToggle.addEventListener("click", () => {3069+ generator.root.hidden = !generator.root.hidden;3070+ generatorToggle.textContent = generator.root.hidden ? "⚙ generate" : "☰ hide generator";3071+ });3072+ body.append(generatorToggle, generator.root);3073+3074+ const notesGroup = document.createElement("div");3075+ notesGroup.className = "f-group";3076+ const notesLabel = document.createElement("div");3077+ notesLabel.className = "f-label";3078+ notesLabel.textContent = "notes";3079+ const notesInput = document.createElement("textarea");3080+ notesInput.className = "f-input";3081+ notesInput.rows = 3;3082+ notesInput.value = entry.notes ?? "";3083+ notesGroup.append(notesLabel, notesInput);3084+ body.appendChild(notesGroup);3085+3086+ const actions = document.createElement("div");3087+ actions.className = "f-row";3088+ const save = document.createElement("button");3089+ save.className = "save-btn";3090+ save.type = "button";3091+ save.textContent = "Save changes";3092+ save.style.flex = "1";3093+ const cancel = document.createElement("button");3094+ cancel.className = "gen-btn";3095+ cancel.type = "button";3096+ cancel.textContent = "cancel";3097+ cancel.addEventListener("click", () => renderSilverPlatter(summary));3098+ actions.append(save, cancel);3099+ body.appendChild(actions);3100+3101+ save.addEventListener("click", async () => {3102+ save.disabled = true;3103+ save.textContent = "Saving…";3104+ const patch = {3105+ title: titleInput.value.trim(),3106+ url: urlInput.value.trim(),3107+ username: usernameInput.value.trim(),3108+ email: emailInput.value.trim() || undefined,3109+ password: passwordInput.value,3110+ notes: notesInput.value,3111+ };3112+ const update = await chrome.runtime.sendMessage({3113+ type: "UPDATE_ENTRY",3114+ payload: { id: summary.id, patch },3115+ }).catch(() => null) as { ok?: boolean; error?: string } | null;3116+ if (!update?.ok) {3117+ save.disabled = false;3118+ save.textContent = "Save changes";3119+ const error = document.createElement("div");3120+ error.className = "empty-hint";3121+ error.textContent = update?.error ?? "couldn't save this entry";3122+ body.appendChild(error);3123+ setTimeout(() => error.remove(), 3000);3124+ return;3125+ }3126+ renderSilverPlatter({3127+ ...summary,3128+ title: patch.title,3129+ url: patch.url,3130+ username: patch.username,3131+ email: patch.email,3132+ });3133+ });3134+3135+ queueMicrotask(() => titleInput.focus());29923136 }29933137 29943138 function renderLocked() {src/popup/components/GeneratorDrawer.test.ts+7 −0
@@ -1,7 +1,14 @@1+import { readFileSync } from 'node:fs';12 import { describe, it, expect } from 'vitest';23 import { initFromPolicy, initGeneratorState, handleLengthChange } from '../generator-policy';34 45 describe('generator site policy constraints', () => {6+ it('renders the constraint note with a readable secondary-text token', () => {7+ const css = readFileSync('src/popup/styles/popup.css', 'utf8');8+ const rule = css.match(/\.peach-generator-constraint-note\s*\{([^}]*)\}/)?.[1];9+10+ expect(rule).toContain('color: var(--peach-t1)');11+ });512 613 describe('Case 1 — no policy', () => {714 it('defaults to 64 when unconstrained and fresh', () => {src/popup/styles/popup.css+1 −1
@@ -2764,7 +2764,7 @@27642764 .peach-generator-constraint-note {27652765 font-family: 'IBM Plex Mono', monospace;27662766 font-size: 10px;2767- color: var(--peach-t3);2767+ color: var(--peach-t1);27682768 letter-spacing: 0.04em;27692769 margin: 0;27702770 transition: opacity 200ms ease;