Commit Graph

567 Commits

Author SHA1 Message Date
9619825605 feat(frontend): lay out the home page in the new design
The game in progress comes first with a large mini board; live,
popular and recent games are grids of cards with the score, a mini
board and a status pill. Live and popular games are shown together;
live games are hidden while nothing is being played.
2026-10-16 10:21:46 +03:00
ce27c71176 feat(frontend): lay out the replay page in the new design
A summary card with the result, date, duration, move count, players and
score; players and the move number beside the board; a controls card
with the transport buttons, speed, timeline and the shown move with its
tags; the landing pit of the shown move is ringed.
2026-10-16 10:19:57 +03:00
efe97da0b7 feat(frontend): lay out the game page in the new design
Players beside the board with avatar, online status, store count and
clock, the turn between them, a game-over card over the board, and a
move list with the newest move first and tags for extra turns,
captures and store gains. On phones the bottom player is shown below
the board.
2026-10-16 09:41:47 +03:00
e3e7916265 feat(frontend): draw the new board
Pits and stores are wells with scattered stones and count badges, the
viewer's store on the right. Below 600 px the board stands upright.
Playable pits preview their move on hover, animated pits are ringed and
the pit of the last move has a dashed ring.
2026-10-16 09:37:50 +03:00
6279b5a820 feat(frontend): add the new themes and header
Ceviz, Gece and Yeşim themes as CSS variables on <html>, Bricolage
Grotesque, shared styles, and one header for every page with theme
swatches, the account menu and the page's main action. The lobby,
privacy page and status panels use the new look.
2026-10-16 09:35:39 +03:00
e0c66a1265 fix(core): save the chosen theme
ThemeManager called storage.set, which neither app's storage has, so
choosing a theme threw and the choice was lost on reload.
2026-10-16 09:32:10 +03:00
193efd1458 feat(backend): list popular finished games while games are live
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.
2026-10-16 09:31:44 +03:00
8ad833cdbb feat(core): add what the web redesign needs
ThemeManager takes its list of themes, PitAnimator marks animated pits
with PitViewModel.highlight, getSowingPath lists the pits a move sows
into, and new texts in English and Turkish.
2026-10-16 09:31:28 +03:00
61d169ecdc docs(web-redesign): add the spec
Requirements, design and tasks for implementing the new web design
(themes, board, game, replay and home pages).
2026-10-16 09:30:28 +03:00
3cf789fd5d chore(mobile): bump version to 0.5.1 (9) 2026-10-16 09:26:02 +03:00
08fc5c8b7b fix(frontend): confirm leaving a game with Yes and go home
The dialog's buttons were in the wrong order and its result was read inverted; leaving now needs Yes and returns to the home page.
2026-10-16 09:26:02 +03:00
b472c2813c docs: document the game clock
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.
2026-10-16 01:54:01 +03:00
f9ea00d138 feat(mobile): show the game clocks
Both clocks next to the players, counting down in server time with the same running and low-time styles as the web app. The game screen now handles on_game_end, so a game that ends on time ends on the phone too and says why.
2026-10-16 01:50:39 +03:00
8671bb2553 feat(frontend): show the clocks in replays
Each player's clock after the shown move, from the saved move times; games saved before the game clock show none.
2026-10-16 01:40:05 +03:00
20359b7a8f feat(frontend): show the game clocks in live games
Both clocks sit next to the players' names and count down in server time; the running one is highlighted and turns red under 30 seconds. They replace the offline countdown, so core drops TURN_TIMEOUT_MS, INACTIVE_TIMEOUT_MS, offlineSince and its text. Long info texts wrap on phones.
2026-10-16 01:39:36 +03:00
c610844e26 feat(backend): keep the game clock across restarts and in saved games
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.
2026-10-16 01:30:39 +03:00
3d4f5d7d59 feat(backend): end games on time with a scheduled service
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.
2026-10-16 01:29:22 +03:00
999ae79279 feat(backend): run a game clock for each player
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.
2026-10-16 01:27:28 +03:00
03e1543d5d feat(core): add game clock types and helpers
GameClock and optional clock fields on live games, end messages, move records and finished games; getClockTimes, ClockSync, getClockAtMove and formatClock with tests.
2026-10-16 01:25:15 +03:00
cb34c03246 docs: add game clock spec
Each player has 5 minutes; the server keeps the time and a scheduled service ends games whose running clock reaches zero. Replaces the offline turn and inactivity rules.
2026-10-16 01:23:32 +03:00
6e6d37650d feat(frontend): open Keycloak pages in the app's language
Login and sign-up redirects pass the current language as ui_locales.
2026-10-16 01:13:20 +03:00
d52960fa0a feat(keycloak): add a login theme in the game's look
The mancala login theme extends keycloak.v2 with one stylesheet: the game's
app bar and logo, the board's colors, pit-shaped inputs, pill buttons and a
board with stones on top of the card. Dark colors follow the system color
scheme. The realm turns the theme on and offers English and Turkish.

The test container now loads the theme and checks that the login page uses
it in the requested language.
2026-10-16 01:13:20 +03:00
bb32989279 docs: add AGENTS.md with setup, checks and pitfalls, and CLAUDE.md pointing to it 2026-10-16 00:44:09 +03:00
862c57da5e Merge branch 'feature/keycloak-auth' into feature/game-history 2026-10-16 00:40:57 +03:00
ab2424b648 docs: add Keycloak login spec
Requirements, design decisions and tasks for the Keycloak login, written
after the implementation; mobile login and turning off legacy userKey
login remain open.
2026-10-16 00:40:57 +03:00
b94c9336dd docs: document game history and update the TODO
- 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
2026-10-16 00:36:48 +03:00
3bb151dbed feat(frontend): replay finished games move by move
- Opening a finished game shows its final position, result, score, date
  and duration, with Watch from start
- First / previous / play-pause / next / last, speed 1x, 2x, 4x, a
  timeline, the move list and arrow keys and space
- Stepping forward animates the move like a live game; jumps show the
  position directly
- ?move=n links to a move and follows the shown move
- Players see the board from their own side
- PitAnimator takes its step interval as a parameter
2026-10-16 00:34:49 +03:00
aa265f7e6c feat(frontend): show the moves of a live game and let players look back
- Move list under the board with player, pit and what happened (extra
  turn, capture, stones to the store, game over); collapsed on phones
- Selecting a move or using the arrow keys shows that position with a
  banner and Back to live; pits are locked meanwhile and the banner says
  when it is your turn
- Countdown while the opponent is offline on their turn
- on_game_end is handled, so games that end by timeout or inactivity end
  on the page with the reason
2026-10-16 00:31:43 +03:00
a65662f695 feat(frontend): show ongoing, live, popular and recent games on the home page
- 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
2026-10-16 00:27:06 +03:00
9278ed7447 feat(frontend): let the board render at any width
Board components size themselves from a --board-unit CSS variable instead
of vw. By default it is 1vw, so the game page looks the same; with the new
width prop the same board renders small, e.g. in previews.
2026-10-16 00:21:53 +03:00
85e3fb60f6 feat(backend): add game history API and live game previews
- 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
2026-10-16 00:21:09 +03:00
7239bbb6ed feat(backend): save every game and keep live games across restarts
- 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
2026-10-16 00:18:33 +03:00
f6136be007 feat(backend): keep live games and caches in Valkey
- 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
2026-10-16 00:14:07 +03:00
f404b92aaf feat(backend): save finished games in Postgres
- 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
2026-10-16 00:11:12 +03:00
ed2a909f16 fix(backend): clean up spectators and finished games
- 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
2026-10-16 00:08:38 +03:00
16026fb6cb feat(core): add game history types and move history helpers
- PlayerSummary, MoveRecord, live and finished game summaries, GameRecord,
  timeout constants, startedAt/moveTimes on Game and offlineSince on
  UserConnectionInfo
- Channels for live game previews and for rejecting a new game while one
  is in progress
- getInitialBoard, getBoardAfterMove, describeMove and createGameAtMove,
  with tests against games played with the engine
- English and Turkish strings for the new screens
2026-10-16 00:07:36 +03:00
29fa443b93 build: add shared Postgres and Valkey to compose
- postgres replaces keycloak-db and holds a keycloak and a mancala database,
  each with its own user, created by apps/postgres/init
- valkey with an append-only file for live game state and caches
- backend gets DATABASE_URL and VALKEY_URL
2026-10-16 00:05:21 +03:00
d0004742f5 docs: add game history and live games spec 2026-10-16 00:03:32 +03:00
20efa40e6c docs: add TODO with mobile login and open issues 2026-10-14 09:13:52 +03:00
f5dcf930fa feat(frontend): log in with Keycloak, as a guest or with an account
- 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
2026-10-14 02:22:13 +03:00
e165b0a0eb build: use local mancala.js and @mancala/core packages
- 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
2026-10-14 02:09:05 +03:00
30ad51c493 feat(backend): authenticate WebSocket clients with Keycloak tokens
- 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
2026-10-14 01:52:07 +03:00
7c99a76af7 feat(keycloak): add Keycloak with guest login extension
- Custom authenticator creates and logs in a guest user when the
  authorization request has guest=true; other requests get the login form
- Scheduled task removes guests with no remaining sessions
- Realm export with mancala-web PKCE client, guest role and guest-aware
  browser flow (verify profile disabled so guests are not asked for details)
- Dockerfile building the extension into a Keycloak 26.7.5 image
- Keycloak and Postgres in compose; nginx proxies auth.playmancala.app
  and rate limits the auth endpoint
- Integration tests run the extension and realm in a Keycloak container
2026-10-14 01:46:48 +03:00
69664796ce feat(mobile): implement "Rejoin Game" functionality and fix storage bugs 2026-07-16 02:00:25 +03:00
8a485e2db3 Merge pull request 'fix/stone-count-text' (#2) from fix/stone-count-text into main
Reviewed-on: #2
2026-07-04 11:55:32 +03:00
0be96a6229 fix: display stone count immediately on board pits 2026-07-04 11:54:31 +03:00
3efc7c5ffe fix(mobile): fix android build issue 2026-07-02 23:44:55 +03:00
0618527ac7 Merge pull request 'feature/stone-count-text' (#1) from feature/stone-count-text into main
Reviewed-on: #1
2026-07-02 21:12:44 +03:00
9c95d9dc44 feat(mobile): display stone count in pit 2026-05-26 00:32:20 +03:00
ca92b20b59 feat(frontend): display stone count in pit 2026-05-26 00:31:59 +03:00