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
37 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 −0
src/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) => {

view file

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> {

view file

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 

view file

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 // ---------------------------------------------------------------------------

view file

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         }),

view file

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,

view file

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 on

view file

src/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();

view file

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 the

view file

src/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: VaultEntry

view file

src/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 {

view file

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 () => '' })));

view file

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 () => {

view file

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() {

view file

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,

view file

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       // Tap

view file

src/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 }

view file

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");

view file

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);

view file

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());

view file

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);

view file

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 {

view file

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 =

view file

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, the

view file

src/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.

view file

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 

view file

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";

view file

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 

view file

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 

view file

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 });

view file

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();

view file

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 });

view file

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 () => {

view file

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";

view file

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`);

view file

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,

view file

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,

view file