Robustness
----------
-[ ] Add basic state persistence (e.g., write game state to a JSON file
+[X] Add basic state persistence (e.g., write game state to a JSON file
on each move, reload on server startup) so games survive a server
restart.
Bugs and improvements from initial playtesting, roughly ordered by
simplicity/impact (simplest first).
-[ ] Fix bag count display: "N tiles remaining" never updates after
+[X] Fix bag count display: "N tiles remaining" never updates after
initial value. The server broadcasts 'dealt' events and the client
listens, but the count isn't changing — likely a wiring issue.
-[ ] Fix stuck button visibility: The button is rendered conditionally
+[X] Fix stuck button visibility: The button is rendered conditionally
but wasn't noticed during testing — may be scrolling off the page
or lost in the layout. Verify it's visible and working.
-[ ] Rename "Done" button to "Letter Rip". Move it to appear centered
+[X] Rename "Done" button to "Letter Rip". Move it to appear centered
in the tile rack area. Make it invisible (not grayed out) when
unavailable, and appear when all tiles are placed with valid words
— at which point the rack is empty so the button fills that space.
-[ ] Add click-to-select/click-to-place interaction as the primary
+[X] Add click-to-select/click-to-place interaction as the primary
input method (in addition to drag-and-drop). Long-press drag on
mobile is too slow. Tap a tile to select it (highlight it), then
tap a grid cell to place it. Tap a placed tile to pick it back up.
-[ ] Improve mobile drag-and-drop: investigate touch-event-based
+[X] Improve mobile drag-and-drop: investigate touch-event-based
dragging (touchstart/touchmove/touchend) instead of HTML5 DnD
which doesn't work well on mobile.
-[ ] Keep the tile rack fixed/visible at all times (sticky positioning
+[X] Keep the tile rack fixed/visible at all times (sticky positioning
at the bottom of the viewport) so it doesn't scroll off the page.
-[ ] Collapse empty grid rows/columns that scroll off the visible
+[X] Collapse empty grid rows/columns that scroll off the visible
viewport — remove rows/columns that are empty AND not adjacent to
any placed tile, so the grid doesn't grow unbounded with dead
space. Preserve empty rows/columns next to placed tiles so players
can extend the grid.
-[ ] Client sends tile placements to server: On every tile
+[X] Client sends tile placements to server: On every tile
place/move/remove, POST the move to the server so it tracks each
player's board state. Simple request: { tileIndex, r, c } for
placement, { tileIndex, r: null, c: null } for return-to-rack.
No synchronous reply needed.
-[ ] Server restores board state on reconnect: When a player reconnects
+[X] Server restores board state on reconnect: When a player reconnects
(handle_events), send their saved board layout so the client can
rebuild the grid. This fixes the frustrating loss-of-state when a
browser is accidentally closed.
-[ ] Server-side word validation on "Letter Rip": Load TWL dictionary
+[X] Server-side word validation on "Letter Rip": Load TWL dictionary
on the server. When a player POSTs /complete, validate that all
tiles are placed, all words are valid, and tiles form a single
connected component. Reject with an error if not.