architecture-refactor
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.
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.
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.
Load-bearing in one mode
Both duel pages held their own copy of the same reveal-timing math. One input to the shared version looks redundant for AI duel and is anything but for PvP.
The comment that confessed
A second architecture review found duplication a module's own doc comment had already admitted to, describing itself as a mirror of logic living somewhere else.
The plan had three slots
screenshot.ts had grown to 626 untested lines drawing both share images and the board underneath them. The obvious three-way split needed a fourth module once someone actually drew the boundary.
Verbatim, not cleaned up
GameEngine.ts held two unrelated engines in one 621-line file. The split needed no design round at all, just a careful move and one very literal constraint.
The deletion test
GameRecap and DuelRecap still carried the same state machine twice. Four candidate shells, judged by one question: does deleting it reduplicate the machine back into both callers?
Descriptors, not strings
The PvP lives-teaching hints get their own pure reducer. Four candidate designs, decided on two separate axes, one of them a rule the sync-engine round had already settled.
The design with no second copy
Four candidate shapes for pulling PvP sync logic out of an 898-line Svelte store. Two of them carried a duplicate copy of the game state; the winner needed none.