Appearance
BratPokerTable decomposed — the most-visible file made the easiest to change
2026-07-16
packages/game-ui/src/BratPokerTable.tsx was 1,588 lines — the default poker felt every player and every agent-owner looks at, ~4× the project's 400-LOC cap, with eleven distinct units sharing one module scope. Two of its internal units each exceeded the whole-file cap: the Seat component (~403 lines) and the orchestrator body (~650). The cost was not aesthetic — it was modification risk on the highest-visibility surface with zero safety net: game-ui had no render-test harness at all, so nothing pinned the felt's behavior. Every change shipped unverified, caught only by a human watching a live table.
This was a pure structural refactor — zero behavior change, zero pixels moved. The order was load-bearing: a characterization harness and tests came FIRST, then the decomposition proceeded under green.
What shipped
- A reusable
game-uiRTL/jsdom render harness (vitest.config.ts+ a manualexpect.extendsetup mirroringapps/poker— dodging the multi-vitest-version jest-dom matcher maze, codebase-velocity-audit F1). It retroactively covers the bust-countdown / waiting-felt port that had shipped without agame-uirender test. - An 11-assertion characterization suite (
BratPokerTable.characterization.test.tsx, A1–A11) pinning the felt's render across representative states — live hand, showdown ceremony, bust rebuy countdown (fake timers), waiting-for-players, acting seat, reserved/empty seats, all-in, folded. Authored once against the monolith, green before the first extraction and unchanged and green after the last — that frozen-suite-still-green property IS the proof of zero drift (A12). - The monolith decomposed into a cohesive file tree, mirroring the
compact/precedent:types.ts/constants.ts/cards.ts/hooks.ts, leaf components (CardTile,MarkerBadge,ActionBar,DealerChips), a top-levelSeat.tsxsplit into private sub-parts (SeatCard,EmptySeat,SeatCards,SeatBadges) each ≤ cap, the felt (TableFelt+BetLayer), and auseBratTableStatehook holding all derived + animation state. BratPokerTable.tsxwent 1,588 → 213 LOC — orchestration only (call the hook, render connect-hint + header +<TableFelt>+ controls). No file in the tree exceeds 400 LOC.
Result
The felt every player looks at is now the easiest file to change safely instead of the hardest — any future brat-felt change extends the characterization suite (never edits its assertions) and lands under a render-test safety net game-ui did not have before.
See the plan at .indusk/planning/brat-poker-table-refactor/.