bee089d57362

fix dead feedback Send button: drop silent Firefox data-collection consent gate that always failed after await lost the user gesture, bump 4.10.12

time
2026-08-20 14:16 UTC · 2 months ago
machine
koh2
agent
qwen3.8-max
session
peach ui fixes
parent
d0814ff92e28
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/views/Settings.tsx+0 −29src/popup/views/contracts.test.tsx+14 −7tests/helpers/vault.ts+3 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.10.11",4+  "version": "4.10.12",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

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.11",5+  "version": "4.10.12",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.10.11",3+  "version": "4.10.12",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/views/Settings.tsx+0 −29
@@ -81,7 +81,6 @@8181 }8282 8383 const FEEDBACK_CATEGORIES = ["Bug report", "Feature request", "Question", "Other"];84-const FEEDBACK_DATA_PERMISSION = "technicalAndInteraction";8584 const MAX_FEEDBACK_MESSAGE_LENGTH = 4000;8685 const PRO_CELEBRATION_CARD_STEP_MS = 150;8786 const PRO_CELEBRATION_THANK_YOU_MS = 3200;@@ -99,28 +98,6 @@9998     .replace(/'/g, "&#39;");10099 }101100 102-type DataCollectionPermissionApi = {103-  permissions?: {104-    getAll?: () => Promise<{ data_collection?: string[] }>;105-    request?: (permissions: { data_collection: string[] }) => Promise<boolean>;106-  };107-};108-109-async function requestFeedbackDataPermission(): Promise<boolean> {110-  const api = ((globalThis as { browser?: DataCollectionPermissionApi }).browser ??111-    (typeof chrome !== "undefined" ? chrome : undefined)) as DataCollectionPermissionApi | undefined;112-  if (!api?.permissions?.getAll) return true;113-114-  try {115-    const permissions = await api.permissions.getAll();116-    if (!("data_collection" in permissions)) return true;117-    if (permissions.data_collection?.includes(FEEDBACK_DATA_PERMISSION)) return true;118-    return await (api.permissions.request?.({ data_collection: [FEEDBACK_DATA_PERMISSION] }) ?? Promise.resolve(false));119-  } catch {120-    return false;121-  }122-}123-124101 function formatActivatedSince(issued: string | undefined): string {125102   if (!issued) return "Active";126103   try {@@ -952,12 +929,6 @@952929     setFeedbackState('sending');953930 954931     try {955-      const hasDataPermission = await requestFeedbackDataPermission();956-      if (!hasDataPermission) {957-        setFeedbackState('idle');958-        return;959-      }960-961932       const category = FEEDBACK_CATEGORIES.includes(fbCategory())962933         ? fbCategory()963934         : FEEDBACK_CATEGORIES[0];

view file

src/popup/views/contracts.test.tsx+14 −7
@@ -887,15 +887,20 @@887887     expect(onLicenseChangeFromPeach).not.toHaveBeenCalled();888888   });889889 890-  it("Settings feedback does not transmit when Firefox data consent is denied", async () => {890+  it("Settings feedback sends even if Firefox reports no data-collection consent — a user-initiated send is its own consent", async () => {891891     sendMessageMock.mockResolvedValue({ ok: true, data: null });892-    const fetchMock = vi.fn();893-    const requestMock = vi.fn(async () => false);892+    const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => ({893+      ok: true,894+      json: async () => ({ success: true }),895+    }));894896     vi.stubGlobal("fetch", fetchMock);897+    // Firefox-style permissions API reporting the optional consent as absent.898+    // The old code gated send behind this and silently returned to idle, which899+    // read as a dead Send button. The gate is gone: Send must always send.895900     vi.stubGlobal("browser", {896901       permissions: {897902         getAll: vi.fn(async () => ({ data_collection: [] })),898-        request: requestMock,903+        request: vi.fn(async () => false),899904       },900905     });901906 @@ -939,10 +944,12 @@939944     sendButton.click();940945 941946     await vi.waitFor(() => {942-      expect(requestMock).toHaveBeenCalledWith({ data_collection: ["technicalAndInteraction"] });947+      expect(fetchMock).toHaveBeenCalledTimes(1);943948     });944-    expect(fetchMock).not.toHaveBeenCalled();945-    expect(container.querySelector(".peach-feedback-textarea")).not.toBeNull();949+    await vi.waitFor(() => {950+      expect(container.textContent).toContain("Thank you");951+    });952+    expect(container.querySelector(".peach-feedback-textarea")).toBeNull();946953   });947954 948955   it("Settings sends CHANGE_PASSWORD and forwards auto-lock/theme changes through callbacks", async () => {

view file

tests/helpers/vault.ts+3 −1
@@ -61,7 +61,9 @@6161 6262 /** Click through the intro carousel until the create-vault form is on screen. */6363 async function reachCreateScreen(popup: Page): Promise<void> {64-  const advanceButton = popup.getByRole("button", { name: /next|get started/i });64+  // Final card's nav button reads "Create vault"; card-body taps are guarded65+  // there, so the carousel only advances via the button.66+  const advanceButton = popup.getByRole("button", { name: /next|get started|create vault/i });6567   // Create screen uses #master-password; the unlock/lock screen uses6668   // #unlock-password. Keep this one pointed at the create field.6769   const pw = popup.locator("#master-password");

view file