Older posts

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. The number was never the decision

    A PvP host can now pick a turn time limit from 30 seconds to 3 minutes, for accessibility. Almost everywhere the old fixed 60 seconds showed up, the actual design underneath it never depended on that number at all.

  6. The opposite tradeoff

    The AI never volunteering a profanity was a free filter. Filtering what a player types into their own initials is the same list applied to the opposite tradeoff.

  7. The second time needed no spike

    AI duel gets the same injectable-factory treatment the PvP store just proved out. With the pattern and the risk already settled, the ticket for this one only had to say what was smaller.

  8. Silence meant leave it

    The Norwegian UI shipped in one evening and stayed unreviewed by a native speaker ever since. Closing that gap meant building a doc that could only ask, never answer.

  9. The store that had never been tested

    pvpGameState.svelte.ts had never had a single unit test of its own. Turning it into an injectable factory meant answering, before touching it, whether Svelte 5 runes would even tolerate the shape.

  10. 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.