mancala/AGENTS.md

142 lines
10 KiB
Markdown
Raw Normal View History

# AGENTS.md
Guidance for coding agents working in this repository. Keep it current: when you learn something that would have
saved time, add it here.
## Project
Online Mancala (the Turkish *Mangala* rules) with a web app, an Android/iOS app and a Java (Quarkus) backend.
| Path | What |
|---|---|
| `packages/mancala.js` | Game engine used by the apps; rules are plug-ins in `src/common/game_rules`. The backend has a Java port |
| `packages/core` | Code shared by the apps: models, WebSocket client (`RTMTWS`), channel names, history helpers, themes, `PitAnimator`, translations (en, tr) |
| `apps/backend` | Java 25 + Quarkus: matchmaking, games, auth, saved games, HTTP API, the Java port of the engine |
| `apps/frontend` | React web app, built with Parcel |
| `apps/mobile` | React Native app |
| `apps/keycloak` | Keycloak image with the realm, the login theme and a Java extension for guest login |
| `apps/postgres` | Init script creating the `keycloak` and `mancala` databases |
| `apps/compose.yaml` | Postgres, Valkey, Keycloak, backend, frontend |
| `config/nginx.conf` | The production server's nginx config (Certbot managed) |
| `docs/specs/<feature>` | Specs: `requirements.md`, `design.md`, `tasks.md` |
| `TODO.md` | Open work, known bugs and deployment steps; read it before starting |
Specs that exist: `keycloak-auth` (login, guests, tokens), `game-history` (saved games, live games, replay),
`game-clock` (5 minutes per player, ending games on time), `web-redesign` (the web app's look: themes, board,
pages), `quarkus-backend` (the backend's move from Node to Java and Quarkus), `game-bot` (playing against a bot
at four levels) and `tutorial` (rules page, lessons, hints in bot games).
## Running locally
```sh
yarn build:packages # repo root; builds mancala.js and core
cd apps && docker compose up -d --build postgres valkey keycloak # needs apps/.env, see README.md
cd apps/backend && ./mvnw quarkus:dev # http://localhost:5005, needs a JDK 25
cd apps/frontend && yarn dev # http://localhost:1234 (must be 1234, Keycloak only allows that)
```
Postgres is on `localhost:5433` (5432 is often taken), Valkey on `6379`, Keycloak on `8080`
(admin console `/admin`). The backend's and frontend's defaults point at these. The mobile app points at production
unless `useLocalServer` is set in `apps/mobile/src/const/config.ts`.
## Checks
| What | Command | Notes |
|---|---|---|
| Engine | `cd packages/mancala.js && npx jest` | After changing the rules, change the Java port too and record the parity games again (`apps/backend/README.md`) |
| Core | `cd packages/core && npx jest` | |
| Backend | `cd apps/backend && ./mvnw test` | Needs a JDK 25 and Docker: Postgres and Valkey run with Quarkus Dev Services. The bot's calibration tournament is tagged `calibration` and skipped; see `apps/backend/README.md` |
| Keycloak extension | `cd apps/keycloak/extension && mvn test` | Needs Docker; starts Keycloak with the real realm file |
| Web app | `cd apps/frontend && yarn build` | There is no `tsconfig.json`, so nothing type-checks it; use a temporary one when needed (with `"types": ["styled-jsx", "react", "react-dom"]`). 1 type error (`react-dom/client`) is known |
| Mobile | `cd apps/mobile && npx tsc --noEmit -p .` and `npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output /tmp/x.bundle` | 11 type errors are known and older than recent work |
There is no automated end-to-end suite. Changes to the game flow were checked by driving Chrome with
`playwright-core` (installed outside the repo, e.g. `npm install playwright-core@1.48.2` in a scratch folder;
`executablePath: /usr/bin/google-chrome`) against the local stack. A live game is `GET /game/:id` on the backend,
which helps to check what the server holds.
## How we work
- The user writes in Turkish; code, comments, commit messages and specs are in English.
- Larger features start with a spec in `docs/specs/<feature>/` (requirements with acceptance criteria, design with
decisions and reasons, tasks as a checklist). Implement the tasks in order, one commit each, and tick them. Small
fixes need no spec. When resuming a feature, read its spec and `tasks.md` first.
- Work on feature branches; commit messages follow `type(scope): summary` with a short body.
- Never add AI attribution (Co-Authored-By lines, "Generated with …") to commits or PRs.
- Do not commit files the user changed but did not ask to commit; stage only your own changes (hunks if needed).
- New dependencies get exact versions.
## Things that are easy to get wrong
**Packages and builds**
- The web and mobile apps use `packages/core` and `packages/mancala.js` through `link:` dependencies, i.e. their built
`dist`. After changing a package, run `yarn build` in it (or `yarn dev` to rebuild on change), or the apps use stale
code.
Running dev servers do not notice a rebuilt package: restart Parcel after deleting `apps/frontend/.parcel-cache`.
The backend does not use the packages.
- Backend and frontend Docker images are built from the repo root: `docker build -f apps/backend/Dockerfile .`.
- The mobile app uses `RTMTWS` from core (its own `src/rtmt` copy is unused), so changing the core WebSocket client
changes mobile too. Metro needs `watchFolders` and `nodeModulesPaths` (already in `metro.config.js`).
**Backend**
- The game rules exist twice: `packages/mancala.js` for the clients and `app.playmancala.engine` in the backend.
Change both; `EngineParityTest` replays games recorded with the TypeScript engine and fails if they differ.
- All live state (games, matchmaking, sockets, previews) belongs to the game loop thread (`GameLoop`). Touch it only
in tasks running there; never block the loop on Postgres, Valkey or HTTP. Other threads use `GameLoop.call`.
- The JSON must stay what the Node backend sent and stored: TypeScript leaves out `undefined` but keeps `null`, so only
optional fields get `@JsonInclude(NON_NULL)` (e.g. `result: null` stays). Valkey snapshots and keys, the tables and
`schema_migrations` are shared with the Node backend's format; tests check data the Node backend wrote.
- User ids: Keycloak users are `kc:<sub>`; legacy mobile users are their `userKey`, which is a secret. Anything
saved or listed publicly goes through `PublicIds.publicUserId`; clients get `toClientGame()`, never spectator ids.
- Live games: memory is the source of truth, Valkey is a write-through copy restored on startup; the backend
assumes a single instance. Finished games are written to Postgres once, with retry when it is down.
- Postgres or Valkey being down must not stop games; reads fall back, writes are retried.
- The game clock is stored as each player's time at the start of the turn plus `turnStartedAt`; compute the time
left with `GameClocks.getClockTimes`, never store a ticking value. `GAME_CLOCK_MS=40000` makes games short for
manual tests.
- `Rtmt.listenOnClientConnectionChange` adds a listener (several are allowed).
- The bot plays as `bot:<level>` (`BotLevel.isBotId`) in many games at once: `GameStore` does not map it to a game,
nothing is sent to it, it is always online and `PublicIds` keeps its id. The bot's search must never run on the
game loop; `BotPlayer` runs it on its own pool and plays the move back on the loop.
- `-Dgroups=...` runs only the tests with that tag; run the untagged tests separately too.
- Unit tests build the logic with fakes (`FakeRtmt`, in-memory stores, `ManualScheduler` instead of timers and
direct executors); only storage, HTTP and WebSocket tests are `@QuarkusTest`s. `FakeTokenVerifier` replaces
Keycloak in all `@QuarkusTest`s.
- Quarkus dev mode's debugger is moved to port 5006 (`pom.xml`), because 5005 is the backend's port.
- The Maven wrapper checks the `.zip` it downloads; without `unzip` it fetches a `.tar.gz` and fails the check (the
Dockerfile installs `unzip`).
**Web app**
- styled-jsx only scopes JSX written directly in a component's render; elements built in helper functions or
`map` callbacks need `.parent :global(.child)` rules.
- Pages are full-height flex columns: a block with `overflow: hidden` needs `flex-shrink: 0` or it gets squeezed.
- Colours come from CSS variables of the theme class on `<html>` (`styles/app.css`); the web app's themes
(`theme/themes.ts`) are passed to core's `ThemeManager`, which the mobile app uses with its own default themes.
- Styles that need container queries (`@container`, `cqw`), such as the board's, are plain `.css` files imported by the
component; styled-jsx 5.0 does not handle them. `BoardView` fills its container and stands upright below 600 px.
- `PitAnimator` sends the same `BoardViewModel` object, changed, at every animation step. Put a new object (or a new
id in its own state, as `GamePage` does) into React state, or React skips the update and only the first step shows.
- The rules, lessons and move explanations live in `packages/core/src/tutorial`; their boards after a move come from
the engine (`getMoveOutcome`). After a rule change, run core's tests: they fail if a lesson no longer works, and
the rule texts in `en.ts`/`tr.ts` may need updating.
- When a script drives the app in Chrome, wait for the header (`.hdr`) and a few seconds after the first load before
navigating; navigating during the guest login leaves an invalid token. Open the lobby with the "New Game" button,
not by URL, or the matchmaking message goes out before the WebSocket is open.
- The WebSocket client emits `open` after authentication; anything the server must know about a connection
(spectating a game, live game previews) is sent on `open`, because the server forgets it when a user goes offline.
**Keycloak**
- The realm file is imported only when the realm does not exist; changes to it must also be made in a running
Keycloak (or recreate the database).
- Listing some `requiredActions` in the realm file replaces Keycloak's whole default list, so the file contains all
of them.
- The guest authenticator uses an internal Keycloak SPI: keep the version in `extension/pom.xml` and `Dockerfile` in
sync and run `mvn test` when upgrading.
- Keycloak sets `Secure` cookies; browsers send them to `http://localhost`, simple HTTP clients do not.
- The login theme (`apps/keycloak/themes/mancala`) is only CSS on top of `keycloak.v2`. Keycloak in `start` mode caches
themes and keeps gzipped resources in `data/tmp/kc-gzip-cache` across restarts; see `apps/keycloak/README.md` for
reloading it in a running container.
**Mobile**
- `apps/mobile/release_android.sh` contains signing passwords (see `TODO.md`); do not copy them anywhere.