214299cfc6f6
L4/QUA-003: enable noUnusedLocals and noUnusedParameters; remove dead identifiers across src and tests
- time
- 2026-05-16 04:59 UTC · 5 months ago
- machine
- koh2
- parent
- f875282e49e1
↓ peach-browser-214299cf.tar.gzatlas at this save
files changed37 files
src/background/alarms.test.ts+1 −1src/background/license.ts+0 −28src/background/messages.test.ts+2 −2src/background/messages.ts+0 −18src/background/passkey-firefox-proxy.test.ts+1 −1src/background/passkey.ts+0 −1src/background/sync-coordinator.test.ts+1 −2src/background/sync.adversarial.test.ts+1 −1src/content/detect.ts+0 −1src/content/index.ts+1 −2src/content/inject.ts+1 −9src/content/passkey-toast/passkey-toast.test.ts+0 −2src/crypto/crypto.test.ts+1 −1src/popup/App.tsx+0 −1src/popup/codex/codex.adversarial.test.ts+0 −1src/popup/components/NoteRow.tsx+2 −2src/popup/views/Entry.tsx+0 −37src/popup/views/Export.tsx+0 −9src/popup/views/Lock.tsx+1 −3src/popup/views/RecoveryWordEntry.tsx+0 −20src/popup/views/Settings.tsx+3 −3src/popup/views/Vault.tsx+0 −12src/shared/import.ts+0 −1src/shared/noise-js.test.ts+1 −2src/shared/recovery-words.ts+1 −3tests/autofill.spec.ts+1 −1tests/diagnostic.spec.ts+1 −1tests/entry-comprehensive.spec.ts+1 −1tests/keyboard-comprehensive.spec.ts+1 −1tests/keyboard-edge.spec.ts+2 −2tests/security-visual.spec.ts+2 −5tests/settings-persistence.spec.ts+1 −1tests/vault-lifecycle.spec.ts+7 −19tests/visual-check.spec.ts+1 −1tests/visual-compliance.spec.ts+4 −4tsconfig.background.json+2 −0tsconfig.json+2 −0src/background/alarms.test.ts+1 −1
@@ -64,7 +64,7 @@6464 }),6565 },6666 tabs: {67- query: vi.fn((query: unknown, callback: (tabs: Array<{ id?: number }>) => void) => {67+ query: vi.fn((_query: unknown, callback: (tabs: Array<{ id?: number }>) => void) => {6868 callback([{ id: 1 }, { id: 2 }]);6969 }),7070 sendMessage: vi.fn(async (tabId: number, message: unknown) => {src/background/license.ts+0 −28
@@ -1,19 +1,5 @@11 const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";22 3-function base58Encode(data: Uint8Array): string {4- let num = BigInt("0x" + Array.from(data).map(b => b.toString(16).padStart(2, "0")).join(""));5- let result = "";6- while (num > 0n) {7- const remainder = num % 58n;8- num = num / 58n;9- result = BASE58_ALPHABET[Number(remainder)] + result;10- }11- for (let i = 0; i < data.length && data[i] === 0; i++) {12- result = "1" + result;13- }14- return result;15-}16-173 function base58Decode(str: string): Uint8Array {184 let num = 0n;195 for (const char of str) {@@ -30,14 +16,6 @@3016 return result;3117 }3218 33-function toBase58Groups(encoded: string): string[] {34- const groups: string[] = [];35- for (let i = 0; i < encoded.length; i += 12) {36- groups.push(encoded.slice(i, i + 12));37- }38- return groups;39-}40-4119 function fromBase58Groups(groups: string[]): string {4220 return groups.join("");4321 }@@ -70,12 +48,6 @@7048 bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);7149 }7250 return bytes;73-}74-75-function bytesToHex(bytes: Uint8Array): string {76- return Array.from(bytes)77- .map(b => b.toString(16).padStart(2, "0"))78- .join("");7951 }8052 8153 export async function validateLicense(keyString: string): Promise<LicenseInfo> {src/background/messages.test.ts+2 −2
@@ -44,7 +44,7 @@4444 sendMessage: vi.fn(async () => ({ ok: true })),4545 },4646 tabs: {47- query: vi.fn((query: unknown, callback?: (tabs: Array<{ id?: number }>) => void) => {47+ query: vi.fn((_query: unknown, callback?: (tabs: Array<{ id?: number }>) => void) => {4848 if (typeof callback === "function") {4949 callback(tabs);5050 return;@@ -161,7 +161,7 @@161161 162162 await expect(dispatch({ type: "GET_ENTRIES" })).resolves.toMatchObject({163163 ok: false,164- error: "vault locked",164+ error: "vault_locked",165165 });166166 });167167 src/background/messages.ts+0 −18
@@ -28,7 +28,6 @@2828 shouldPlayCelebration,2929 } from "./license";3030 import {31- cancelFirefoxPasskeyCreate,3231 completeFirefoxPasskeyCreate,3332 getPasskeyProviderState,3433 getPendingPasskeyRequest,@@ -112,23 +111,6 @@112111 }113112 114113 return { allowed: false };115-}116-117-// ---------------------------------------------------------------------------118-// Local device name. Peers see this string in PAIR_REQUEST / PAIR_ACCEPT and119-// next to the device in their paired-devices list. Default to a platform-aware120-// fallback when the user hasn't set one explicitly.121-const DEVICE_NAME_KEY = "peach_device_name";122-123-async function getLocalDeviceName(): Promise<string> {124- try {125- const stored = await chrome.storage.local.get(DEVICE_NAME_KEY);126- const value = (stored as Record<string, string | undefined>)[DEVICE_NAME_KEY];127- if (typeof value === "string" && value.trim()) return value.trim();128- } catch {129- // fall through to default130- }131- return "Peach";132114 }133115 134116 // ---------------------------------------------------------------------------src/background/passkey-firefox-proxy.test.ts+1 −1
@@ -58,7 +58,7 @@5858 sendMessage: vi.fn(async () => ({ ok: true })),5959 },6060 tabs: {61- query: vi.fn((q: unknown, callback?: (t: Array<{ id?: number }>) => void) => {61+ query: vi.fn((_q: unknown, callback?: (t: Array<{ id?: number }>) => void) => {6262 if (typeof callback === "function") { callback(tabs); return; }6363 return Promise.resolve(tabs);6464 }),src/background/passkey.ts+0 −1
@@ -1,7 +1,6 @@11 import { isProUser } from "./license";22 import * as vault from "./vault";33 import {4- base64urlDecode,54 base64urlEncode,65 buildAuthenticatorData,76 buildClientDataJSON,src/background/sync-coordinator.test.ts+1 −2
@@ -1,8 +1,7 @@1-import { describe, it, expect, vi, beforeEach } from 'vitest';1+import { describe, it, expect, vi } from 'vitest';22 import { runBlobSyncSession } from './sync-coordinator';33 import type { SyncDeps } from './sync-coordinator';44 import type { SyncTransport } from '../shared/websocket-sync-transport';5-import { encodeSyncMessage } from '../shared/sync-wire';65 import type { SyncableDevice } from '../shared/sync-types';76 87 // Builds a paired in-memory transport — one write on side A becomes a read onsrc/background/sync.adversarial.test.ts+1 −1
@@ -610,7 +610,7 @@610610 });611611 612612 it("stale in-memory entries cannot clobber remote blob after sync", async () => {613- const vault = await setupBlobs();613+ await setupBlobs();614614 const messages = await import("./messages");615615 const deps = messages.buildSyncDeps();616616 const remote = await buildRemoteBlobWithDifferentKey();src/content/detect.ts+0 −1
@@ -10,7 +10,6 @@1010 // the trigger token is contiguous with other word chars and would not match1111 // at a word boundary.1212 const USERNAME_PATTERNS = /(user(name|id|login|email)?|e?mail(box)?|login(email|username)?|account|identifier|signin|sign.?in|credential)/i;13-const PASSWORD_PATTERNS = /pass|passwd|pwd/i;1413 1514 // Input types that are never text-entry fields and must never get autofill1615 // icons. submit/button/reset/image are interactive controls — putting thesrc/content/index.ts+1 −2
@@ -10,7 +10,6 @@1010 import {1111 CardSelectorEntry,1212 injectFieldIcon,13- observeAnchorPosition,1413 SelectorEntry,1514 showAutofillSelector,1615 showCardSelector,@@ -316,7 +315,7 @@316315 317316 318317 async function handleAutofill(319- field: HTMLInputElement,318+ _field: HTMLInputElement,320319 usernameEl: HTMLInputElement | null,321320 passwordEl: HTMLInputElement,322321 entry: VaultEntrysrc/content/inject.ts+1 −9
@@ -10,7 +10,7 @@1010 */1111 1212 import { ACCENT_COLORS, type PasswordPolicy, type SavedIdentity } from "../shared/types";13-import { generatePassword, generateSmartPassword } from "../shared/generate";13+import { generatePassword } from "../shared/generate";1414 import { generateTOTP } from "../crypto/totp";1515 import { detectPasswordPolicy } from "./policy";1616 @@ -98,10 +98,6 @@9898 return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 64 64" fill="none" aria-hidden="true"><path d="M32 54C16 54 8 44 8 31C8 18 17 10 27 10C28 7 30 5.5 32 5.5C34 5.5 36 7 37 10C47 10 56 18 56 31C56 44 48 54 32 54Z" fill="#D96830"/><path d="M37 8.5C40.5 3.5 48 5 46 10.5C42 11 37 8.5 37 8.5Z" fill="#7B9060"/></svg>`;9999 }100100 101-function genSvgStr(): string {102- return `<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round"><path d="M9.5 2A4.5 4.5 0 1 0 10.5 5.5"/><path d="M8.5 2H9.5V1"/></svg>`;103-}104-105101 function checkSvgStr(): string {106102 return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="#7B9060" stroke-width="1.5" stroke-linecap="round"><path d="M4 10.5L8 14.5L16 6.5"/></svg>`;107103 }@@ -174,10 +170,6 @@174170 shadow.appendChild(bar);175171 document.body.appendChild(host);176172 setTimeout(() => host.remove(), 9000);177-}178-179-function generatePasswordForSite(policy: PasswordPolicy = {}): string {180- return generateSmartPassword(policy);181173 }182174 183175 interface SmartGenerator {src/content/passkey-toast/passkey-toast.test.ts+0 −2
@@ -89,14 +89,12 @@8989 9090 describe('PASSKEY_AUTH_RESPONSE handler', () => {9191 let storage: StorageShape;92- let sessionStorage: StorageShape;9392 let listener: (() => MessageListener);9493 9594 beforeEach(async () => {9695 vi.resetModules();9796 const mocks = installChromeMock();9897 storage = mocks.storage;99- sessionStorage = mocks.sessionStorage;10098 listener = mocks.listener;10199 storage['peach_settings'] = { breachMonitoring: false };102100 vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, status: 200, text: async () => '' })));src/crypto/crypto.test.ts+1 −1
@@ -1,6 +1,6 @@11 import { describe, it, expect } from "vitest";22 import { deriveKey, generateSalt } from "./argon2";3-import { encrypt, decrypt, zeroKey } from "../background/aes";3+import { encrypt, decrypt } from "../background/aes";44 55 describe("vault crypto", () => {66 it("round-trips plaintext through encrypt/decrypt", async () => {src/popup/App.tsx+0 −1
@@ -117,7 +117,6 @@117117 }118118 }119119 120-const ENTRY_TRANSITION_EXIT_MS = 95;121120 const ENTRY_TRANSITION_SETTLE_MS = 135;122121 123122 export default function App() {src/popup/codex/codex.adversarial.test.ts+0 −1
@@ -18,7 +18,6 @@1818 type CodexPayload,1919 } from "../../shared/codex-types";2020 import {21- base64UrlToBytes,2221 bytesToBase64Url,2322 buildChunkEnvelope,2423 sha256Hex,src/popup/components/NoteRow.tsx+2 −2
@@ -1,4 +1,4 @@1-import { createSignal, onCleanup, onMount } from "solid-js";1+import { createSignal } from "solid-js";22 import type { VaultEntry } from "../../shared/types";33 44 function vibrate(ms: number) {@@ -87,7 +87,7 @@8787 setSwipeX(dx);8888 }8989 90- function handleTouchEnd(e: TouchEvent) {90+ function handleTouchEnd(_e: TouchEvent) {9191 clearTimeout(longPressTimer);9292 if (!captured) {9393 // Tapsrc/popup/views/Entry.tsx+0 −37
@@ -422,25 +422,6 @@422422 }423423 }424424 425- async function savePassword(password: string) {426- const entry = props.entry;427- if (!entry) return;428- setSaving(true);429- try {430- await sendMessage({431- type: "UPDATE_ENTRY",432- payload: { id: entry.id, patch: { password } },433- });434- triggerRing();435- setShowGenerator(false);436- await props.onFieldSaved();437- } catch {438- // silent439- } finally {440- setSaving(false);441- }442- }443-444425 // ── Copy ─────────────────────────────────────────────────────────445426 446427 async function copyField(text: string, label: string, button?: HTMLButtonElement) {@@ -1535,24 +1516,6 @@15351516 <svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="currentColor" stroke-width="0.9" stroke-linecap="round">15361517 <path d="M1 5C1 5 2.8 1 7 1C11.2 1 13 5 13 5C13 5 11.2 9 7 9C2.8 9 1 5 1 5Z" />15371518 <circle cx="7" cy="5" r="1.75" />1538- </svg>1539- );1540-}1541-1542-function ChevronGlyph(props: { open: boolean }) {1543- return (1544- <svg1545- width="8"1546- height="8"1547- viewBox="0 0 8 8"1548- fill="none"1549- stroke="currentColor"1550- stroke-width="1.3"1551- stroke-linecap="round"1552- stroke-linejoin="round"1553- style={`transform: rotate(${props.open ? 90 : 0}deg); transition: transform 160ms ease;`}1554- >1555- <path d="M2 1L6 4L2 7" />15561519 </svg>15571520 );15581521 }src/popup/views/Export.tsx+0 −9
@@ -468,15 +468,6 @@468468 return new Date().toISOString().split("T")[0];469469 }470470 471-function monthYear(): string {472- const d = new Date();473- const months = [474- "January", "February", "March", "April", "May", "June",475- "July", "August", "September", "October", "November", "December",476- ];477- return `${months[d.getMonth()]} ${d.getFullYear()}`;478-}479-480471 function downloadBlob(content: string, mimeType: string, filename: string): void {481472 const url = URL.createObjectURL(new Blob([content], { type: mimeType }));482473 const a = document.createElement("a");src/popup/views/Lock.tsx+1 −3
@@ -5,7 +5,6 @@55 import { RecoveryWordEntryView } from "./RecoveryWordEntry";66 import { RecoveryWordDisplayView } from "./RecoveryWordDisplay";77 import { RuneBackground } from "../components/RuneBackground";8-import { ImportView } from "./Import";98 import { storageGet, storageSet } from "../../shared/storage";109 import { isPeachScript, decodePeachScript, normalisePeachScript } from "../../crypto/peachScript";1110 import { parsePayload } from "../../crypto/peachPayload";@@ -62,7 +61,6 @@6261 // After vault creation succeeds, set this to show the recovery words screen.6362 // Only meaningful when hasVault === false.6463 const [showRecoveryWords, setShowRecoveryWords] = createSignal(props.forceRecoveryWords ?? false);65- const [recoveryWords, setRecoveryWords] = createSignal<string[]>([]);6664 6765 const MIN_LENGTH = 12;6866 const MIN_ZXCVBN_SCORE = 2;@@ -549,7 +547,7 @@549547 }) {550548 const [activeTab, setActiveTab] = createSignal<RestoreTab>("symbols");551549 const [showIntro, setShowIntro] = createSignal(false);552- const [pendingUnlock, setPendingUnlock] = createSignal(false);550+ const [pendingUnlock] = createSignal(false);553551 554552 async function handleRestoreSuccess() {555553 const seen = await storageGet("peach_shown_restore_intro").catch(() => null);src/popup/views/RecoveryWordEntry.tsx+0 −20
@@ -1,15 +1,11 @@11 import {2- createEffect,3- createMemo,42 createSignal,53 For,6- onMount,74 Show,85 } from "solid-js";96 import { sendMessage } from "../App";107 import { RecoveryRuneBackground } from "../components/RecoveryRuneBackground";118 import { getWordSuggestions } from "../../shared/recovery-words";12-import { WORD_LIST } from "../../shared/recovery-word-list";139 1410 const TOTAL_WORDS = 12;1511 @@ -38,10 +34,6 @@3834 // Which word index (0-based) is currently focused for autocomplete3935 const [activeIndex, setActiveIndex] = createSignal<number | null>(null);4036 41- // When all 12 words are filled, auto-advance after a short debounce.42- // But only auto-advance once we've confirmed they're all valid-looking.43- let autoAdvanceTimer: ReturnType<typeof setTimeout> | null = null;44-4537 function onWordInput(idx: number, value: string) {4638 const normalised = value.toLowerCase().trim();4739 const updated = [...words()];@@ -118,18 +110,6 @@118110 }119111 }120112 121- function onSuggestionClick(idx: number, word: string) {122- const final = [...words()];123- final[idx] = word;124- setWords(final);125- setSuggestions((s) => {126- const u = [...s];127- u[idx] = null;128- return u;129- });130- const next = findNextEmpty(final);131- if (next !== null) focusSlot(next);132- }133113 134114 function findNextEmpty(w: string[]): number {135115 return w.findIndex((v) => !v.trim());src/popup/views/Settings.tsx+3 −3
@@ -1,4 +1,4 @@1-import { createEffect, createSignal, For, Match, onMount, Show, Switch } from "solid-js";1+import { createSignal, For, Match, onMount, Show, Switch } from "solid-js";22 import { sendMessage } from "../App";33 import { PeachMark } from "../components/PeachMark";44 import { LockRuneCanvas } from "../components/LockRuneCanvas";@@ -111,7 +111,7 @@111111 const [licenseInfo, setLicenseInfo] = createSignal<LicenseInfo | null>(null);112112 const [licenseKeyInput, setLicenseKeyInput] = createSignal("");113113 const [licenseError, setLicenseError] = createSignal<string | null>(null);114- const [licenseSuccess, setLicenseSuccess] = createSignal<string | null>(null);114+ const [, setLicenseSuccess] = createSignal<string | null>(null);115115 const [activatingLicense, setActivatingLicense] = createSignal(false);116116 117117 const isPro = () => props.isPro ?? false;@@ -194,7 +194,7 @@194194 const [changePwConfirm, setChangePwConfirm] = createSignal("");195195 const [changePwSaving, setChangePwSaving] = createSignal(false);196196 const [changePwError, setChangePwError] = createSignal<string | null>(null);197- const [changePwSuccess, setChangePwSuccess] = createSignal(false);197+ const [changePwSuccess] = createSignal(false);198198 const [showNewRecoveryWords, setShowNewRecoveryWords] = createSignal(false);199199 200200 const [duressEnabled, setDuressEnabled] = createSignal(false);src/popup/views/Vault.tsx+0 −12
@@ -79,12 +79,6 @@7979 );8080 }8181 82-const LoginIcon = () => (83- <svg width="11" height="12" viewBox="0 0 11 12" fill="none" stroke="currentColor" stroke-width="0.9" stroke-linecap="round" aria-hidden="true">84- <rect x="2" y="5" width="7" height="5" rx="1.2" />85- <path d="M3.5 5V3.75A2 2 0 0 1 5.5 1.75A2 2 0 0 1 7.5 3.75V5" />86- </svg>87-);8882 8983 const PasskeyIcon = () => (9084 <svg width="11" height="12" viewBox="0 0 11 12" fill="none" stroke="currentColor" stroke-width="0.9" stroke-linecap="round" aria-hidden="true">@@ -260,12 +254,6 @@260254 261255 function isExactPrefix(query: string, target: string): boolean {262256 return target.toLowerCase().startsWith(query.toLowerCase());263- }264-265- function entryDomain(entry: VaultEntry): string {266- if (entry.type === "login" && entry.url) return getDisplayDomain(entry.url);267- if (entry.passkey?.rpId) return entry.passkey.rpId;268- return "";269257 }270258 271259 function entryMatchesDomain(entry: VaultEntry, domain: string): boolean {src/shared/import.ts+0 −1
@@ -6,7 +6,6 @@66 importPasskeyPrivateKey,77 } from "../background/passkey-crypto";88 import { parseOtpauthURI } from "../crypto/totp";9-import type { VaultEntry } from "./types";109 import { importEntriesFromPeachJson, type ImportableEntry } from "./backup";1110 1211 export type ImportFormat =src/shared/noise-js.test.ts+1 −2
@@ -21,7 +21,6 @@2121 2222 // Pinned cross-check vector for HMAC-SHA256 auth proof.2323 const INITIATOR_PRIVATE = new Uint8Array(32).fill(0x01);24-const RESPONDER_PRIVATE = new Uint8Array(32).fill(0x02);2524 const NONCE = new Uint8Array(32).fill(0x03);2625 2726 const EXPECTED_INITIATOR_PUBLIC =@@ -31,7 +30,7 @@3130 const EXPECTED_PROOF =3231 "39efee52ccc9d7eaa24fcfec9a4c75eaad75a6bf3319d9794d9227f2b1466752";3332 34-function derivePublic(privateKey: Uint8Array): Uint8Array {33+function derivePublic(_privateKey: Uint8Array): Uint8Array {3534 const pair = x25519GenerateKeyPair();3635 // We can't derive a public from an arbitrary private with @stablelib's3736 // generateKeyPair (it samples a fresh secret). For the cross-check, thesrc/shared/recovery-words.ts+1 −3
@@ -11,10 +11,8 @@1111 1212 import { WORD_LIST } from "./recovery-word-list";1313 14-const ENTropy_BITS = 128; // 128 bits of entropy1514 const WORD_BITS = 11; // 2^11 = 2048 words per word16-const CHECKSUM_BITS = 4; // 4-bit checksum (first 4 bits of SHA256 of entropy)17-const TOTAL_WORDS = 12; // (128 + 4) / 11 = 12 words15+const TOTAL_WORDS = 12; // (128 + 4) / 11 = 12 words; 128 entropy + 4-bit BIP39 checksum1816 1917 /**2018 * Convert a 32-byte root key to 12 recovery words.tests/autofill.spec.ts+1 −1
@@ -60,7 +60,7 @@6060 </body></html>6161 `);6262 await testPage.waitForTimeout(2000);63- const peachIcon = testPage.locator("button", { hasText: "🍑" });63+ testPage.locator("button", { hasText: "🍑" });6464 await testPage.waitForTimeout(500);6565 });6666 tests/diagnostic.spec.ts+1 −1
@@ -1,4 +1,4 @@1-import { test, expect } from "@playwright/test";1+import { test } from "@playwright/test";22 import { fileURLToPath } from "url";33 import { dirname, join } from "path";44 import os from "os";tests/entry-comprehensive.spec.ts+1 −1
@@ -634,7 +634,7 @@634634 const { popup } = await setupVault(ctx);635635 await popup.waitForTimeout(500);636636 const breachRow = popup.locator(".peach-entry-row.is-breach, .peach-entry-row-dot");637- const hasBreach = await breachRow.isVisible({ timeout: 2000 }).catch(() => false);637+ await breachRow.isVisible({ timeout: 2000 }).catch(() => false);638638 await ctx.close();639639 });640640 tests/keyboard-comprehensive.spec.ts+1 −1
@@ -496,7 +496,7 @@496496 await popup.keyboard.press("Escape");497497 await popup.waitForTimeout(300);498498 const output = popup.locator(".peach-pwgen-output");499- const isHidden = await output.isHidden().catch(() => true);499+ await output.isHidden().catch(() => true);500500 await ctx.close();501501 });502502 tests/keyboard-edge.spec.ts+2 −2
@@ -48,7 +48,7 @@4848 4949 test("KB01 - enter submits password on lock screen", async () => {5050 const ctx = await makeContext();51- const { popup } = await setupVault(ctx);51+ await setupVault(ctx);5252 const newPopup = await ctx.newPage();5353 const extId = await waitForSW(ctx);5454 await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);@@ -299,7 +299,7 @@299299 await popup.getByRole("button", { name: /unlock/i }).click();300300 await popup.waitForTimeout(500);301301 const errorMsg = popup.locator(".peach-hint-error, .peach-hint");302- const hasError = await errorMsg.first().isVisible({ timeout: 2000 }).catch(() => false);302+ await errorMsg.first().isVisible({ timeout: 2000 }).catch(() => false);303303 await ctx.close();304304 });305305 });tests/security-visual.spec.ts+2 −5
@@ -147,7 +147,7 @@147147 148148 test("SEC10 - no sensitive data in error messages", async () => {149149 const ctx = await makeContext();150- const { popup } = await setupVault(ctx);150+ await setupVault(ctx);151151 const newPopup = await ctx.newPage();152152 const extId = (await waitForSW(ctx));153153 await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);@@ -216,11 +216,9 @@216216 const jsFiles = await fs.readdir(join(__dirname, "..", "dist", "assets")).then(f =>217217 f.filter(f => f.endsWith(".js"))218218 );219- let foundArgon = false;220219 for (const file of jsFiles) {221220 const content = await fs.readFile(join(__dirname, "..", "dist", "assets", file), "utf-8");222221 if (content.includes("argon") || content.includes("Argon2")) {223- foundArgon = true;224222 expect(content).toMatch(/m=65536|t=3|p=4|iterations|memory/);225223 break;226224 }@@ -271,7 +269,7 @@271269 272270 test("PERF01 - vault unlocks within 3 seconds", async () => {273271 const ctx = await makeContext();274- const { popup } = await setupVault(ctx);272+ await setupVault(ctx);275273 const newPopup = await ctx.newPage();276274 const extId = await waitForSW(ctx);277275 await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);@@ -280,7 +278,6 @@280278 await newPopup.locator("#master-password").fill("password123");281279 await newPopup.getByRole("button", { name: /unlock/i }).click();282280 await newPopup.waitForTimeout(500);283- const entries = newPopup.locator("[class*=entry]");284281 const elapsed = Date.now() - start;285282 expect(elapsed).toBeLessThan(5000);286283 await ctx.close();tests/settings-persistence.spec.ts+1 −1
@@ -154,7 +154,7 @@154154 await popup.locator("text=auto-lock").click();155155 await popup.waitForTimeout(300);156156 const selectedOption = popup.locator("[role='option' i][aria-selected='true'], .peach-select-option.is-selected").first();157- const hasSelected = await selectedOption.isVisible({ timeout: 2000 }).catch(() => false);157+ await selectedOption.isVisible({ timeout: 2000 }).catch(() => false);158158 await ctx.close();159159 });160160 });tests/vault-lifecycle.spec.ts+7 −19
@@ -49,17 +49,6 @@4949 return { popup };5050 }5151 52-async function unlockVault(ctx: BrowserContext, password = "testpassword123") {53- const { popup } = await loadPopup(ctx);54- const pw = popup.locator("#master-password");55- if (await pw.isVisible()) {56- await pw.fill(password);57- await popup.getByRole("button", { name: /unlock/i }).click();58- await popup.waitForTimeout(1500);59- }60- return { popup };61-}62-6352 test.describe("Vault Lifecycle", () => {6453 let ctx: BrowserContext;6554 @@ -215,7 +204,7 @@215204 });216205 217206 test("16 - service worker restart shows lock screen", async () => {218- const { popup, extId } = await loadPopup(ctx);207+ const { extId } = await loadPopup(ctx);219208 await createVault(ctx);220209 const newPopup = await ctx.newPage();221210 await newPopup.goto(`chrome-extension://${extId}/src/popup/index.html`);@@ -278,7 +267,7 @@278267 });279268 280269 test("21 - password field shows dots by default on lock screen", async () => {281- const { popup } = await loadPopup(ctx);270+ await loadPopup(ctx);282271 await createVault(ctx);283272 const newPopup = await ctx.newPage();284273 const extId = (await loadPopup(ctx)).extId;@@ -290,7 +279,7 @@290279 });291280 292281 test("22 - reveal password toggle on lock screen", async () => {293- const { popup } = await loadPopup(ctx);282+ await loadPopup(ctx);294283 await createVault(ctx);295284 const newPopup = await ctx.newPage();296285 const extId = (await loadPopup(ctx)).extId;@@ -417,7 +406,6 @@417406 test("36 - password strength updates as user types", async () => {418407 const { popup } = await loadPopup(ctx);419408 const pw = popup.locator("#master-password");420- const confirm = popup.locator("#confirm-password");421409 if (await pw.isVisible()) {422410 await pw.fill("w");423411 await popup.waitForTimeout(200);@@ -510,13 +498,13 @@510498 test("44 - biometric unlock option shows if available", async () => {511499 const { popup } = await loadPopup(ctx);512500 const biometric = popup.locator("text=biometric, text=fingerprint, text=face id, text=touch id");513- const hasBio = await biometric.first().isVisible({ timeout: 2000 }).catch(() => false);501+ await biometric.first().isVisible({ timeout: 2000 }).catch(() => false);514502 });515503 516504 test("45 - keyboard shortcut hint on lock screen", async () => {517505 const { popup } = await loadPopup(ctx);518506 const shortcut = popup.locator("text=enter, text=shortcut, text=ctrl+enter");519- const hasShortcut = await shortcut.first().isVisible({ timeout: 2000 }).catch(() => false);507+ await shortcut.first().isVisible({ timeout: 2000 }).catch(() => false);520508 });521509 522510 test("46 - remember master password option", async () => {@@ -524,7 +512,7 @@524512 await popup.locator("[aria-label*='settings' i]").click();525513 await popup.waitForTimeout(500);526514 const remember = popup.locator("text=remember master password, text=master password reminder");527- const hasRemember = await remember.first().isVisible({ timeout: 2000 }).catch(() => false);515+ await remember.first().isVisible({ timeout: 2000 }).catch(() => false);528516 });529517 530518 test("47 - vault export encrypts with password", async () => {@@ -615,7 +603,7 @@615603 await popup.locator("[aria-label*='settings' i]").click();616604 await popup.waitForTimeout(300);617605 const dangerZone = popup.locator("text=clear vault, text=delete all, text=destroy vault");618- const hasDanger = await dangerZone.first().isVisible({ timeout: 2000 }).catch(() => false);606+ await dangerZone.first().isVisible({ timeout: 2000 }).catch(() => false);619607 });620608 621609 test("55 - clear vault requires password confirmation", async () => {tests/visual-check.spec.ts+1 −1
@@ -1,4 +1,4 @@1-import { test, expect, Page, BrowserContext } from "@playwright/test";1+import { test, BrowserContext } from "@playwright/test";22 import { fileURLToPath } from "url";33 import { dirname, join } from "path";44 import os from "os";tests/visual-compliance.spec.ts+4 −4
@@ -108,7 +108,7 @@108108 });109109 110110 test("VAP04 - unlock transition is smooth (no jank)", async () => {111- const { popup } = await setupVault(ctx);111+ await setupVault(ctx);112112 const newPage = await ctx.newPage();113113 const extId = await waitForSW(ctx);114114 await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);@@ -327,7 +327,7 @@327327 await scroller.hover();328328 await popup.waitForTimeout(200);329329 const scrollThumb = scroller.locator("::-webkit-scrollbar-thumb");330- const isVisible = await scrollThumb.isVisible().catch(() => false);330+ await scrollThumb.isVisible().catch(() => false);331331 await ctx.close();332332 });333333 });@@ -376,7 +376,7 @@376376 });377377 378378 test("VCT03 - tint variables (--t0 through --t4) all defined", async () => {379- const cssFiles = await fs.readdir(join(__dirname, "..", "dist", "assets")).then(f =>379+ await fs.readdir(join(__dirname, "..", "dist", "assets")).then(f =>380380 f.filter(f => f.endsWith(".css"))381381 );382382 const varFiles = await fs.readdir(join(__dirname, "..", "dist", "assets")).then(f =>@@ -445,7 +445,7 @@445445 });446446 447447 test("VPM02 - vault unlocks within 3 seconds", async () => {448- const { popup } = await setupVault(ctx);448+ await setupVault(ctx);449449 const newPage = await ctx.newPage();450450 const extId = await waitForSW(ctx);451451 await newPage.goto(`chrome-extension://${extId}/src/popup/index.html`);tsconfig.background.json+2 −0
@@ -8,6 +8,8 @@88 "jsx": "preserve",99 "jsxImportSource": "solid-js",1010 "strict": true,11+ "noUnusedLocals": true,12+ "noUnusedParameters": true,1113 "noEmit": true,1214 "skipLibCheck": true,1315 "isolatedModules": true,tsconfig.json+2 −0
@@ -8,6 +8,8 @@88 "jsx": "preserve",99 "jsxImportSource": "solid-js",1010 "strict": true,11+ "noUnusedLocals": true,12+ "noUnusedParameters": true,1113 "noEmit": true,1214 "skipLibCheck": true,1315 "isolatedModules": true,