The backend README, how to run and test it, what is easy to get wrong
with the game loop and the shared data formats, and the deployment note.
The web app was played against it in Chrome: a full game with a
spectator, a reload, a restart mid-game, the replay and a game lost on
time.
The Quarkus project moves to apps/backend and the Express sources go.
The image is built from the repo root as before, with Temurin 25, and
runs as a non-root user on port 6000. Compose passes the database as a
JDBC URL with the same MANCALA_DB_PASSWORD.
GET /games/popular?kind=finished returns the most watched finished
games even when games are being played, so the web app can show live
and popular games together.
Backend README (GAME_CLOCK_MS), AGENTS.md notes, TODO items for the open points and the mobile release order; game-history R3.3 and R3.4 marked as replaced. Checked end to end in Chrome: moves, a backend restart, a timeout and the replay.
Live game snapshots become version 2 with the clock and each move's time left. On restore the running clock goes on from the start of the turn, so downtime is not charged; version 1 snapshots get a new clock. Saved games keep the initial time (migration 002_clock) and each move's time left.
GameClockScheduler checks every live game once a second and ends those whose running clock has reached zero, whether or not anyone is connected. The clock replaces the offline turn timeout and the inactivity rule, so both are removed with the offline times they used.
Each player starts with GAME_CLOCK_MS (5 minutes). A move deducts the turn's time from the mover; a move that arrives after the time has run out is not played and the opponent wins on time. Every ending stops the clock, and clients get the clock and the server time with each game message.
- Backend README: DATABASE_URL, VALKEY_URL, the new routes and how the
backend behaves when Postgres or Valkey is down
- TODO: drop the fixed spectator and memory bugs, add game history on
mobile, a page with all games, single-instance limits, backups and the
new .env variables
- Spec marked as implemented, all tasks ticked
- Banner for the user's game in progress with a Continue button
- Live games as cards with the real board, updated over WebSocket; the
most watched finished games when nothing is live
- The user's last 3 games, from their side of the board, with the result
as they would say it
- The lobby offers to continue the game in progress when a new one is
refused
- Spectators register once connected and identified, and again after a
reconnect, so they are counted
- The backend includes games that ended but are not saved yet in /game/:id
and the lists
- GET /games/me/active and /games/me (Bearer token), /games/popular (live
games, or the most spectated finished ones), and /game/:id now also
returns saved records of finished games
- Lists are cached in Valkey for 10 seconds, cleared when a game is saved,
and empty instead of failing while the database is down
- Live game previews over WebSocket: subscribers get the top 5 on
subscribe and on change, at most once per second, without counting as
spectators; ids are public ids
- RTMT supports several connection listeners
- Games record start and move times, spectator peak and distinct count
- Games end as completed, left (the leaver loses), timeout (5 minutes
offline on your turn while the opponent is online) or inactive (both
offline for 30 minutes); everyone gets on_game_end
- Connection info carries offlineSince for the countdown
- A new game is rejected while the user has one in progress
- Live games are written to Valkey after every change and restored on
startup; finished games are saved to Postgres with public ids, and
saves that fail are kept and retried every 30 seconds
- Clients get a trimmed game without spectator ids
- Startup waits for Valkey, restores games, then accepts connections;
migrations are retried while Postgres is unreachable
- Valkey client that fails commands at once while Valkey is down instead
of queueing them, and logs connection errors once
- ValkeyLiveGameStore: live game snapshots and finished games waiting to
be saved, loaded with SCAN on startup
- ValkeyCache: JSON values with a TTL; errors count as cache misses
- Integration tests against a real Valkey, including an unreachable one
- Connection pool and a small migration runner (schema_migrations, one
transaction per migration, advisory lock)
- games and game_players tables with indexes for a user's games and for
popular games
- PostgresGameHistoryRepository: idempotent save, get, recent games of a
user, popular games
- Integration tests against a real Postgres via testcontainers; a custom
Jest environment gives tests Node's web globals, which Jest 27 leaves out
- GameRecord carries the number of distinct spectators
- Spectators are removed from games when they disconnect; the lookup
iterated an iterator with for...in and never found anything
- Finished and left games are removed from memory, not only from the
player mappings
- The connection info is sent once when a game starts
- First visit logs in as a new guest through Keycloak without a form;
the session, including an offline refresh token, is kept in local storage
- Account menu on the home page: guests can log in or sign up, users can
log out (which starts a new guest session)
- The WebSocket client in core authenticates with the access token after
connecting, reports "connected" once the server accepts it, and re-sends
renewed tokens on the open connection
- Game page shows player names; guests appear as "Guest 4F2A"
- Auth channels, AuthUser and player names in UserConnectionInfo move to
core and the backend uses them from there
- Mobile keeps using userKey login for now
- Apps and @mancala/core depend on the packages in this repo through
link: dependencies instead of the npm releases, so changes no longer
need a publish
- Root package.json gets a build:packages script that installs and
builds both packages
- Backend and frontend Docker images are built from the repo root so
they can include the packages; compose builds them that way
- Metro watches the packages folder and resolves their imports from the
app's node_modules
- New auth module verifies Keycloak access tokens offline against the
realm's signing keys (issuer, RS256, typ=Bearer, allowed azp); guests
are recognised by the guest realm role
- Clients send an auth message after connecting and get on_auth or
on_auth_error; unauthenticated sockets are closed after 5 seconds and
their messages ignored; a socket can refresh its token but not switch user
- A user can be connected from several sockets; a late close of an old
socket no longer drops the new one
- Malformed messages are ignored instead of throwing
- Games store player names and guest flags and send them with the
connection info
- Legacy ?userKey= clients and /register stay available behind
LEGACY_USERKEY_AUTH; legacy keys must be UUIDs so they cannot take a
Keycloak user's kc: id
- TypeScript target raised to es2020 for async/await