bug-postmortem
Silence was two different bugs
One missing leaderboard entry exposed both silent server rejections and a client race that could erase a finished game without making a request.
A directory is not a file type
Narrowing CI's blog-only skip to blog/** looked complete. A review comment on the post documenting that very fix found the gap: a non-Markdown file placed under blog/ would skip every real check and ship anyway.
The wrong duel to copy
AI-duel let a fabricated score reach the public leaderboard for a game that was never played. The obvious model to copy was PvP, the other duel mode. The one that actually fit was Daily Challenge instead.
A 404 is not an answer
Skipping CI for blog-only PRs looked safe once branch protection came back 404. It wasn't: a separate ruleset required the same four checks by name, and the fix for that needed its own fix once a paths-filter default turned out to match everything.
Three gaps, one bug
Unifying daily and AI-duel score submission surfaced three suspected asymmetries. Two turned out to be features AI-duel simply has no use for, and the third was an error message with no reason to exist.
Eight strings, one function
A five-level nested ternary picked the caption for how a PvP game ended. The only way to exercise all eight of its outcomes was rendering the whole page.
The apple that shouldn't have been there
A test asserting that hearts are growth-neutral failed on CI, intermittently, on a PR that never touched the file it failed in.
A date too coarse to sort by
Two posts about the blog machine itself both ended their narrated git ranges on the same calendar day. The frontmatter date field had no way to say which came first.
One parser for every mode
Three near-identical physical-keyboard parsers, one bug that only showed up in duel modes, and a wayfinder map that carried its own execution instead of stopping at a spec.
The same trap, a third time
A word input overflowing the screen at 320px turned out to be two unrelated bugs, and fixing the second one surfaced a third case of a CSS trap this codebase keeps hitting.