- Replace Create+Get+Update with UpsertGame/UpsertSnakeGame queries
- Extract free functions (saveGame, loadGame, etc.) from duplicated
receiver methods on Store and Instance types
- Remove duplicate generateID from snake package, reuse game.GenerateID
- Throttle snake game DB writes to every 2s instead of every tick
- Fix double-lock in c4game chat handler
- Update all code for sqlc pointer types (*string instead of sql.NullString)
- Add brotli compression (level 5) to long-lived SSE event streams
(HandleGameEvents, HandleSnakeEvents) to reduce wire payload
- Fix all errcheck violations with nolint annotations for best-effort calls
- Fix goimports: separate stdlib, third-party, and local import groups
- Fix staticcheck: add package comments, use tagged switch
- Zero lint issues remaining
Inline persistence logic directly into game stores and handlers:
- game/persist.go: DB mapping methods on GameStore and GameInstance
- snake/persist.go: DB mapping methods on SnakeStore and SnakeGameInstance
- Chat persistence inlined into c4game handlers
- Delete db/persister.go (GamePersister, SnakePersister, ChatPersister)
- Stores now take *repository.Queries directly instead of Persister interface
Migrate from the via meta-framework to direct dependencies:
- chi for routing, templ for HTML templates, datastar for SSE/reactivity
- Feature-sliced architecture (features/{auth,lobby,c4game,snakegame}/)
- Shared layouts and components (features/common/)
- Handler factory pattern (HandleX(deps) http.HandlerFunc)
- Embedded NATS server (nats/), SCS sessions (sessions/), chi router wiring (router/)
- Move ChatMessage domain type from ui package to game package
- Remove old ui/ package (gomponents-based via/h views)
- Remove via dependency from go.mod entirely
v.PubSub() was captured at startup before v.Start() initialized NATS,
so both stores held nil and notify() silently no-oped. Replace the
PubSub interface with a callback that evaluates v.PubSub() lazily at
call time.
Use via's embedded NATS server to notify players of state changes
instead of a 100ms polling ticker. Each player subscribes to
"game.<id>" on page load; via auto-cleans subscriptions on disconnect,
eliminating the need for manual player tracking and RegisterSync.
When a game finishes (win or draw), players see a "Play again" button.
Clicking it creates a new game and the opponent sees a "Join Rematch"
link to join the same game.
- Add Delete method to GameStore and Persister interface
- Add delete button to game list on home page
- Verify user owns game before allowing deletion
- Use status constants instead of magic numbers
- Remove unused variable in persister
- User registration/login with bcrypt password hashing
- SQLite database with goose migrations and sqlc-generated queries
- Games and players persisted to database, resumable after restart
- Guest play still supported alongside authenticated users
- Auth UI components (login/register forms, auth header, guest banner)
Invitees no longer need to enter a nickname - they automatically
join with a random name like "Swift Tiger" or "Happy Falcon".
Game creators still enter their nickname manually.
- Enter key now triggers createGame action on home page
- Remove redundant setNickname action from home page
- Remove unused code: join channel, Leave(), Stop() methods
- Consolidate ID generation into game.GenerateID()
- Remove unused CreatedAt field from Game struct
Real-time two-player Connect 4 using Via framework with:
- Game creation and invite links
- SSE-based live updates for both players
- Win detection with animated highlighting
- Session-based nickname persistence