44a2d34bbddd

fix: contain device-trust alert banners inside the 320px shell (v4.8.7)

The revoked/rotated-device banners embed the peer-supplied device name in a nowrap line; without min-width containment the banner sized the shell grid item to its min-content (measured 1105px scrollWidth in a 320px popup), clipping and distorting the whole UI. Add min-width: 0 / max-width: 100% containment so the existing single-line ellipsis engages. Regression spec tests/sync-alerts.spec.ts SA01 verified red pre-fix (1105 > 320), green post-fix; full vitest 3687 passed / 0 failed.

time
2026-08-07 22:08 UTC · 2 months ago
machine
koh2
parent
65feee9c1752
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+7 −0tests/sync-alerts.spec.ts+71 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.8.6",4+  "version": "4.8.7",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.8.6",5+  "version": "4.8.7",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.8.6",3+  "version": "4.8.7",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/styles/popup.css+7 −0
@@ -671,6 +671,10 @@671671   flex-direction: column;672672   gap: 6px;673673   padding: 8px 12px 0;674+  /* Grid item of .peach-shell: without a zero min-width the nowrap alert text675+     sizes this item to its min-content, blowing the fixed-width shell past676+     320px and clipping/distorting everything else in it. */677+  min-width: 0;674678 }675679 .peach-sync-alert {676680   display: flex;@@ -678,11 +682,14 @@678682   gap: 8px;679683   padding: 8px 10px;680684   border-radius: 7px;685+  min-width: 0;686+  max-width: 100%;681687   background: color-mix(in srgb, #C8A840 14%, transparent);682688   border: 1px solid color-mix(in srgb, #C8A840 38%, transparent);683689 }684690 .peach-sync-alert-text {685691   flex: 1;692+  min-width: 0;686693   font-family: "IBM Plex Mono", "SFMono-Regular", monospace;687694   font-size: 10px;688695   line-height: 1.45;

view file

tests/sync-alerts.spec.ts+71 −0
@@ -0,0 +1,71 @@1+import { test, expect, BrowserContext } from "@playwright/test";2+import { makeContext, setupVault, waitForSW } from "./helpers/vault";3+4+// Device-trust alerts (DEVICE_REVOKED / DEVICE_KEY_ROTATED) carry peer-supplied5+// device names. A long or unbreakable name must never widen the fixed-width6+// extension shell — that regressed as a >1000px scroll surface in a 320px7+// popup, clipping and distorting everything else in the shell.8+test.describe.configure({ timeout: 120_000 });9+10+test.describe("Sync Alerts", () => {11+  let ctx: BrowserContext;12+  test.afterEach(async () => { await ctx.close(); });13+14+  test("SA01 - long device names stay contained inside the shell", async () => {15+    ctx = await makeContext();16+    const { popup } = await setupVault(ctx);17+    await expect(popup.locator(".peach-lock-btn[aria-label='Lock vault']")).toBeVisible();18+19+    const alerts = [20+      {21+        type: "DEVICE_REVOKED",22+        entryId: "",23+        entryTitle: "",24+        deviceName: "June's MacBook Pro (16-inch, M4 Max, 2024, Work Profile)",25+        timestamp: Date.now(),26+      },27+      {28+        type: "DEVICE_KEY_ROTATED",29+        entryId: "",30+        entryTitle: "",31+        deviceName: "Android-Backup-Device-With-An-Extremely-Long-Unbreakable-Identifier-0123456789",32+        timestamp: Date.now(),33+      },34+    ];35+    await popup.evaluate((a) => chrome.storage.local.set({ peach_pending_sync_alerts_v1: a }), alerts);36+37+    // A fresh popup boots unlocked and consumes the pending alert queue.38+    const extId = await waitForSW(ctx);39+    const p2 = await ctx.newPage();40+    await p2.goto(`chrome-extension://${extId}/src/popup/index.html`);41+    await expect(p2.locator(".peach-sync-alert")).toHaveCount(2, { timeout: 20000 });42+43+    const geo = await p2.evaluate(() => {44+      const shell = document.querySelector(".peach-shell")!;45+      const shellRect = shell.getBoundingClientRect();46+      const rows = [...document.querySelectorAll(".peach-sync-alert")].map((r) => {47+        const rrect = r.getBoundingClientRect();48+        const text = r.querySelector(".peach-sync-alert-text") as HTMLElement;49+        return {50+          rowLeftDelta: rrect.left - shellRect.left,51+          rowRightDelta: rrect.right - shellRect.right,52+          textScrollW: text.scrollWidth,53+          textClientW: text.clientWidth,54+        };55+      });56+      return { shellScrollW: shell.scrollWidth, rows };57+    });58+59+    // The shell is designed at 320px; the alert blowout stretched its scroll60+    // surface past 1000px. It must never exceed the design width.61+    expect(geo.shellScrollW).toBeLessThanOrEqual(320);62+63+    // Every alert row stays inside the shell, and the nowrap text truncates64+    // (ellipsis) instead of widening its row.65+    for (const row of geo.rows) {66+      expect(row.rowLeftDelta).toBeGreaterThanOrEqual(-1);67+      expect(row.rowRightDelta).toBeLessThanOrEqual(1);68+      expect(row.textScrollW).toBeGreaterThan(row.textClientW);69+    }70+  });71+});

view file