All projects
2026 Design + engineering

Password generator

Local-only password generator with rejection-sampled Web Crypto, lazy-loaded zxcvbn strength scoring, and a 100/100/100/100 Lighthouse profile.

  • Astro
  • TypeScript
  • Web Crypto
  • zxcvbn
  • Vitest
  • Playwright

Overview

A local-only password generator that never sends a password over the wire. Generation, strength scoring, and clipboard copy all run in the user’s browser; the page itself is a static Astro build that hydrates a single vanilla-TS island.

Goals

  • Unbiased random output (no modulo bias on small alphabets).
  • Guarantee at least one character from every enabled class.
  • Honest strength signal without paying the cost of zxcvbn on the critical path.
  • Pass WCAG 2.0 AA including color contrast in real Chromium, not just headless DOM.
  • Lighthouse 100/100/100/100 on both mobile and desktop.

Approach

Rejection-sampled RNG

randomIntBelow(n, crypto) draws a 32-bit value from crypto.getRandomValues and rejects draws above the largest multiple of n that fits in 2^32. No modulo bias on small alphabets like the 19-symbol pool or the 8-digit “avoid ambiguous” set. The rejection branch is unit-tested with a stub Crypto that queues specific u32 values.

Class-presence guarantee

The generator seeds the buffer with one character from each enabled class pool, fills the remainder from the merged pool, then Fisher–Yates shuffles. A 100-trial test loop locks in the contract so a future refactor to a single merged-pool loop fails loudly.

Lazy strength

zxcvbn is ~700 KB minified. It loads via a dynamic import('zxcvbn') on the first strength update, with a .catch that surfaces “strength meter unavailable” through the page’s live region rather than hanging the UI on chunk-load failures.

Accessibility

The DOM is built imperatively in mount-app.ts with semantic landmarks, aria-labelledby on every section, a real <button> for copy, and a live region for strength + error feedback. Two axe gates: happy-dom for structure, real Chromium for color contrast.

Outcomes

  • 19 unit tests covering the RNG rejection branch, class-presence guarantee across 100 trials, subset coverage, MAX_LEN bound, and the avoidAmbiguous invariant (never emits 0, O, 1, I, l).
  • 2 Playwright axe tests gate WCAG A/AA including color contrast.
  • Lighthouse mobile + desktop: 100/100/100/100.

What’s next

  • CSP headers via Hostinger .htaccess (Phase 7 deploy).
  • A 301 from the legacy /pw-gen/ path to /password-generator at cutover.