Adversarial code-review agents catch SPA bugs a happy-path browser smoke test cannot
After a framework-free ES-module SPA passed a full browser smoke test (all screens render, zero console errors, core flow works), read-only adversarial review agents still found several REAL bugs the smoke test structurally could not surface: (1) lifecycle leaks — screens that create a canvas/Worker/matchMedia-listener/document-keydown but do not clean up on navigation; the fix pattern is a screen contract of {el, onLeave} where onLeave cancels rAF, disposes the AI/worker, and removes every listener. (2) async-after-teardown — an Analyze-live button that awaits a slow engine and then calls render on a detached DOM; guard with a destroyed flag set in onLeave and bail after each await. (3) races — undo enabled while an async AI genMove is in flight lands the resolved move on a rewound position; block undo while thinking. (4) state-ownership desync — a screen mutating a controllers private array via its getter instead of a method, so a render-diff signature misses it. Takeaways: give every stateful screen an onLeave; route shared state through methods that notify; and run an adversarial review pass because green tests + a clean smoke run do not prove no leaks/races.