The expensive lesson was not writing features. It was finding out that something marked done did not work.
The account lockout could never fire. LOCKOUT_THRESHOLD was 10, and the login rate limiter's bucket capacity was 5 — so the sixth attempt got a 429 and the counter that locks an account was never reached. Four rounds of testing on a real deployment went past it, each time reporting "no lockout email arrived", before I read the two constants next to each other. It is LIMITS.login.capacity now.
An uncaught throw from a third-party script took the whole page down. Turnstile's reset() raises when the widget is not where it expects — already unmounted, or never rendered because the domain was refused. Inside a React effect that reached the root error boundary and replaced the page with "core could not start". It surfaced as intermittent 500 screens in the test suite with no matching 500 in the server log, because the server was never involved. 2,399 lines of dev-server output, zero errors — the answer was in a Playwright trace.
Feature detection by looking for a symbol is wrong. `typeof BarcodeDetector === 'function'` is true on Chrome for Windows and Linux, where the platform service behind it does not exist. So the QR scan buttons rendered, the camera opened onto nothing, and a chosen image came back "no QR code found" — which reads as a bad photograph rather than a browser that was never going to work. The real question is getSupportedFormats(), which is a promise, which is why the shortcut was tempting.
A test can hold a bug in place. The Permissions-Policy header said camera=(), which blocked the scanner entirely, and csp.spec.ts asserted toContain('camera=()'). It passed for as long as the feature was broken.
A store with a local cache has three places a change can land. "Delete forever" removed an item from the screen and from the server, and wrote it back into IndexedDB with deletedAt: null — so permanently deleted notes reappeared, restored, on the next load. Six tests covered that code path and all six looked at the screen or the server within one page.
Platform limits, found by hitting them: crypto.getRandomValues refuses a single request over 65,536 bytes; the OpenNext build does not run on Windows (pnpm symlinks and esbuild), so deploying happens from CI; `next start` does not work against an OpenNext build at all; Lighthouse v12 removed the PWA category, so the "PWA score 100" requirement was measuring something that no longer exists.
And mutation testing earns its keep. Several tests passed against deliberately broken code — including one asserting a security property that was checking prelogin, which by design answers 200 with a decoy salt for addresses it has never seen, so it would have passed against a server that created accounts for anybody.