diff --git a/.env.example b/.env.example index cf047bf..a8850ff 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # ================================================================= -# ChessKernel — Environment Variables +# ChessKernel: Environment Variables # Copy this file to .env and fill in your values before starting. # ================================================================= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba0a651..9e37fac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,15 +11,15 @@ Thank you for considering a contribution! ## What to Work On - Check open [GitHub Issues](https://github.com/mateuseap/chesskernel/issues). -- Feature requests are welcome — open an issue to discuss before implementing. +- Feature requests are welcome: open an issue to discuss before implementing. - Bug reports: please include steps to reproduce, expected vs. actual behaviour, and browser/OS. ## Code Style -- TypeScript strict mode — no `any` unless absolutely necessary. +- TypeScript strict mode, no `any` unless absolutely necessary. - No `console.log` in production paths. - Keep files under 800 lines; prefer many focused modules. -- No mutation — use spread / immutable patterns. +- No mutation: use spread / immutable patterns. - Conventional Commits format for all commit messages. ## Pull Request Process diff --git a/client/src/app/globals.css b/client/src/app/globals.css index 6823f06..dd8eea0 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -4,7 +4,7 @@ @layer base { :root { - /* Light — warm cream, chess.com-inspired */ + /* Light: warm cream, chess.com-inspired */ --background: 40 25% 93%; --foreground: 225 25% 13%; --card: 0 0% 100%; @@ -28,7 +28,7 @@ } .dark { - /* Dark — deep navy, chess.com dark-inspired */ + /* Dark: deep navy, chess.com dark-inspired */ --background: 225 16% 12%; --foreground: 220 14% 90%; --card: 224 16% 17%; @@ -70,7 +70,7 @@ } } -/* Illegal move — chess.com-style shake + red glow */ +/* Illegal move: chess.com-style shake + red glow */ @keyframes illegal-shake { 0% { transform: translateX(0); } 14% { transform: translateX(-6px); } @@ -99,7 +99,7 @@ 100% { opacity: 0; } } -/* Feature carousel — infinite horizontal scroll, pauses on hover */ +/* Feature carousel: infinite horizontal scroll, pauses on hover */ @keyframes carousel-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } diff --git a/client/src/components/chess/ChessBoard.tsx b/client/src/components/chess/ChessBoard.tsx index de6b357..853190d 100644 --- a/client/src/components/chess/ChessBoard.tsx +++ b/client/src/components/chess/ChessBoard.tsx @@ -17,7 +17,7 @@ const CLS: Record { const el = boardRef.current; if (!el) return; @@ -408,7 +408,7 @@ export function ChessBoard({ orientation={orientation} /> - {/* Classification badge — standalone SVG, not coupled to arrows */} + {/* Classification badge: standalone SVG, not coupled to arrows */} {lastMove?.to && moveClassification && ( = { p: 1, n: 3, b: 3, r: 5, q: 9 }; const STARTING: Record = { p: 8, n: 2, b: 2, r: 2, q: 1 }; -// Highest-value pieces first — chess.com display order +// Highest-value pieces first, chess.com display order const ORDER = ['q', 'r', 'b', 'n', 'p'] as const; export interface MaterialInfo { diff --git a/client/src/components/chess/PieceIcon.tsx b/client/src/components/chess/PieceIcon.tsx index 15ea6df..bbda871 100644 --- a/client/src/components/chess/PieceIcon.tsx +++ b/client/src/components/chess/PieceIcon.tsx @@ -1,4 +1,4 @@ -// Cburnett chess pieces (CC BY-SA 3.0, Wikimedia) — same set as react-chessboard. +// Cburnett chess pieces (CC BY-SA 3.0, Wikimedia), same set as react-chessboard. // Extracted from the react-chessboard bundle and rendered as inline SVG at any size. type PieceType = 'p' | 'n' | 'b' | 'r' | 'q'; diff --git a/client/src/pages/AnalysisPage.tsx b/client/src/pages/AnalysisPage.tsx index a67d276..61c5592 100644 --- a/client/src/pages/AnalysisPage.tsx +++ b/client/src/pages/AnalysisPage.tsx @@ -102,7 +102,7 @@ function EvalBar({ cp, mate }: { cp: number | null; mate: number | null }) { }} /> - {/* Score — vertical text, pinned to the far end of the winning colour */} + {/* Score: vertical text, pinned to the far end of the winning colour */}

{white} vs {black}

-

{game.result ?? '—'} · {game.timeControl ?? t('analysis.title')}

+

{game.result ?? '-'} · {game.timeControl ?? t('analysis.title')}

@@ -408,7 +408,7 @@ export function AnalysisPage() { - {/* Move list — chess.com style */} + {/* Move list: chess.com style */}
{t('analysis.moves')} diff --git a/client/src/pages/GamePage.tsx b/client/src/pages/GamePage.tsx index 01f0e17..129d2b9 100644 --- a/client/src/pages/GamePage.tsx +++ b/client/src/pages/GamePage.tsx @@ -112,7 +112,7 @@ export function GamePage() { }, [gameId, chess, applyMoveOptimistic, revertOptimistic, triggerIllegalFlash]); // When king is selected and user clicks/drops on own rook, remap to castling destination. - // chess.com supports this "king captures rook" convention; chess.js does not — we handle it. + // chess.com supports this "king captures rook" convention; chess.js does not, so we handle it. const resolveCastleTarget = useCallback((from: Sq, to: Sq): Sq => { const movingPiece = chess.get(from as Square); const targetPiece = chess.get(to as Square); diff --git a/client/src/pages/HomePage.tsx b/client/src/pages/HomePage.tsx index 9a4302d..ee3039f 100644 --- a/client/src/pages/HomePage.tsx +++ b/client/src/pages/HomePage.tsx @@ -70,7 +70,7 @@ export function HomePage() {
- {/* Chess board — real piece images via react-chessboard */} + {/* Chess board: real piece images via react-chessboard */}
- {/* Features — infinite-scroll carousel, pauses on hover */} + {/* Features: infinite-scroll carousel, pauses on hover */}

{t('home.featuresTitle')}

diff --git a/docs/adr/001-technology-stack.md b/docs/adr/001-technology-stack.md index 88c8440..1bf5f77 100644 --- a/docs/adr/001-technology-stack.md +++ b/docs/adr/001-technology-stack.md @@ -11,7 +11,7 @@ ChessKernel must be fully self-hosted with no paid third-party dependencies. The ### Backend: NestJS over Express/Fastify -NestJS provides dependency injection, module isolation, built-in WebSocket gateway, Swagger integration, and class-validator — all of which map directly to our feature set. The structure enforces the modular architecture we require. +NestJS provides dependency injection, module isolation, built-in WebSocket gateway, Swagger integration, and class-validator, all of which map directly to our feature set. The structure enforces the modular architecture we require. ### ORM: Prisma over TypeORM/Drizzle @@ -19,7 +19,7 @@ Prisma's type-safe client, excellent migration tooling, and schema-first approac ### Realtime: Socket.IO over raw WebSocket -Socket.IO provides rooms, namespaces, automatic reconnection, and a Redis adapter for horizontal scaling — all needed without additional infrastructure. +Socket.IO provides rooms, namespaces, automatic reconnection, and a Redis adapter for horizontal scaling, all needed without additional infrastructure. ### Chess Rules: chess.js (no alternatives) diff --git a/docs/adr/002-game-state-authority.md b/docs/adr/002-game-state-authority.md index 2bcc139..807b84e 100644 --- a/docs/adr/002-game-state-authority.md +++ b/docs/adr/002-game-state-authority.md @@ -25,7 +25,7 @@ The server manages both clocks. Clock state is stored in Redis with millisecond ## Consequences -- Every move requires a round-trip — no purely local game progression +- Every move requires a round-trip, with no purely local game progression - Server must handle move validation at WebSocket message rate - Reconnecting clients receive full game state from Redis/DB - Spectators see the same authoritative state as players diff --git a/docs/api/api-contracts.md b/docs/api/api-contracts.md index 08ab48d..d44a8aa 100644 --- a/docs/api/api-contracts.md +++ b/docs/api/api-contracts.md @@ -34,7 +34,7 @@ Create a new account. ```json { "email": "alice@example.com", "password": "s3cr3t!" } ``` -**Response 200** — same shape as register. +**Response 200**: same shape as register. --- @@ -45,14 +45,14 @@ Rotate token pair. Send current refresh token. ```json { "refreshToken": "" } ``` -**Response 200** — new `accessToken` + `refreshToken`. +**Response 200**: new `accessToken` + `refreshToken`. --- ### `POST /auth/logout` 🔒 Revoke current refresh token. -**Response 204** — no body. +**Response 204**: no body. --- @@ -98,7 +98,7 @@ Update own profile. ```json { "bio": "Chess enthusiast", "avatarUrl": "https://...", "country": "BR" } ``` -**Response 200** — updated user object. +**Response 200**: updated user object. --- @@ -256,14 +256,14 @@ Send a friend request. ```json { "username": "bob" } ``` -**Response 201** — friendship record. +**Response 201**: friendship record. --- ### `POST /friends/:id/accept` 🔒 Accept a pending request. -**Response 200** — updated friendship. +**Response 200**: updated friendship. --- @@ -296,7 +296,7 @@ Create an open invite (generates a shareable token). --- ### `GET /invitations/:token` -Get invite details (public — no auth required). +Get invite details (public, no auth required). --- @@ -380,7 +380,7 @@ io('https://', { auth: { token: '' } }) | Event | Payload | Description | |-------|---------|-------------| -| `queue:matched` | `{ gameId }` | Match found — navigate to game | +| `queue:matched` | `{ gameId }` | Match found, navigate to game | | `game:start` | `{ gameId, white, black, timeControl, ... }` | Game started | | `game:move:broadcast` | `{ move, fen, clock }` | Opponent played a move | | `game:move:rejected` | `{ reason }` | Server rejected your move | diff --git a/docs/architecture/system-design.md b/docs/architecture/system-design.md index 01d7036..54102d6 100644 --- a/docs/architecture/system-design.md +++ b/docs/architecture/system-design.md @@ -41,7 +41,7 @@ ZADD matchmaking:{timeControl} {rating} {userId} ``` - One sorted set per time control (bullet_1_0, blitz_3_2, rapid_10_0, etc.) -- Rating is the Z-score — enables O(log n) range queries +- Rating is the Z-score, which enables O(log n) range queries - Polling interval: 500ms worker checks for compatible players ### Match Algorithm @@ -141,10 +141,10 @@ Move classification thresholds (centipawn drop from best move): | Classification | CP Drop | |----------------|---------| | Brilliant | engine says sub-optimal but tactically sharp | -| Best / Excellent | 0–10 | -| Good | 10–25 | -| Inaccuracy | 25–100 | -| Mistake | 100–300 | +| Best / Excellent | 0-10 | +| Good | 10-25 | +| Inaccuracy | 25-100 | +| Mistake | 100-300 | | Blunder | >300 | | Book | matches opening book | @@ -152,15 +152,15 @@ Move classification thresholds (centipawn drop from best move): ### Namespaces -- `/game` — active game events -- `/matchmaking` — queue and match events -- `/notifications` — friend requests, invitations, alerts +- `/game`: active game events +- `/matchmaking`: queue and match events +- `/notifications`: friend requests, invitations, alerts ### Room Structure -- `game:{gameId}` — players and spectators of a game -- `user:{userId}` — private user channel -- `leaderboard` — live leaderboard updates +- `game:{gameId}`: players and spectators of a game +- `user:{userId}`: private user channel +- `leaderboard`: live leaderboard updates ### Key Events @@ -183,7 +183,7 @@ Move classification thresholds (centipawn drop from best move): | `game:{id}:state` | Hash | 2h | Active game FEN + clocks | | `game:{id}:players` | Hash | 2h | Player socket IDs | | `user:{id}:session` | String | 7d | JWT refresh token | -| `matchmaking:{tc}` | ZSet | — | Matchmaking queue | +| `matchmaking:{tc}` | ZSet | - | Matchmaking queue | | `leaderboard:{tc}` | ZSet | 5m | Cached ranking | | `user:{id}:online` | String | 30s | Online presence (heartbeat) | diff --git a/docs/backend/backend-architecture.md b/docs/backend/backend-architecture.md index dbf53d2..485c971 100644 --- a/docs/backend/backend-architecture.md +++ b/docs/backend/backend-architecture.md @@ -4,7 +4,7 @@ ``` server/src/ -├── app.module.ts # Root module — wires everything together +├── app.module.ts # Root module: wires everything together ├── main.ts # Bootstrap: NestJS app, Prisma hooks, validation pipe ├── common/ # Guards, decorators, interceptors, filters │ ├── guards/ # JwtAuthGuard, WsAuthGuard @@ -110,7 +110,7 @@ stateDiagram-v2 ABANDONED --> [*] ``` -Active game state is cached in Redis (`game:{id}`) for O(1) reconnect. PostgreSQL is authoritative — Redis is repopulated from DB on cache miss. +Active game state is cached in Redis (`game:{id}`) for O(1) reconnect. PostgreSQL is authoritative; Redis is repopulated from DB on cache miss. ## Matchmaking @@ -153,10 +153,10 @@ Move classification thresholds (centipawn eval drop from best move): | Classification | CP Drop | |----------------|---------| | Brilliant | sub-optimal but tactically sharp (heuristic) | -| Best / Excellent | 0–10 | -| Good | 10–25 | -| Inaccuracy | 25–100 | -| Mistake | 100–300 | +| Best / Excellent | 0-10 | +| Good | 10-25 | +| Inaccuracy | 25-100 | +| Mistake | 100-300 | | Blunder | >300 | | Book | matches opening book | diff --git a/docs/deployment/setup.md b/docs/deployment/setup.md index ddf00e3..8edd6e6 100644 --- a/docs/deployment/setup.md +++ b/docs/deployment/setup.md @@ -6,7 +6,7 @@ graph TD Internet["Internet\n(port 80 / 443)"] Caddy["Caddy\n(TLS termination)\nauto Let's Encrypt"] - Client["client container\nNginx — React SPA\n+ API proxy"] + Client["client container\nNginx, React SPA\n+ API proxy"] Server["server container\nNestJS API\n(port 3001 internal)"] PG["postgres container\nPostgreSQL 16"] Redis["redis container\nRedis 7"] @@ -38,7 +38,7 @@ All services run inside Docker and communicate on a private network. Only Caddy | Docker Compose | 2.x | | Domain | A record pointing to your server IP | -Stockfish is installed inside the `server` container via `apk add stockfish` — no host install needed. +Stockfish is installed inside the `server` container via `apk add stockfish`, no host install needed. --- @@ -68,7 +68,7 @@ cd chesskernel cp .env.example .env ``` -Edit `.env` — replace every placeholder value: +Edit `.env` and replace every placeholder value: ```env POSTGRES_USER=chesskernel @@ -125,21 +125,21 @@ Migrations run automatically on server start. | Variable | Required | Default | Description | |----------|----------|---------|-------------| -| `POSTGRES_USER` | Yes | — | PostgreSQL username | -| `POSTGRES_PASSWORD` | Yes | — | PostgreSQL password | -| `POSTGRES_DB` | Yes | — | PostgreSQL database name | -| `REDIS_PASSWORD` | Yes | — | Redis auth password | -| `JWT_SECRET` | Yes | — | Access token signing key (64+ hex chars) | +| `POSTGRES_USER` | Yes | - | PostgreSQL username | +| `POSTGRES_PASSWORD` | Yes | - | PostgreSQL password | +| `POSTGRES_DB` | Yes | - | PostgreSQL database name | +| `REDIS_PASSWORD` | Yes | - | Redis auth password | +| `JWT_SECRET` | Yes | - | Access token signing key (64+ hex chars) | | `JWT_ACCESS_EXPIRES_IN` | No | `15m` | Access token TTL | | `JWT_REFRESH_EXPIRES_IN` | No | `7d` | Refresh token TTL | -| `CLIENT_ORIGIN` | Yes | — | Allowed CORS origin — must match your public URL | -| `DOMAIN` | Yes | — | Public domain (used by Caddy for TLS cert) | +| `CLIENT_ORIGIN` | Yes | - | Allowed CORS origin, must match your public URL | +| `DOMAIN` | Yes | - | Public domain (used by Caddy for TLS cert) | --- ## Custom Stockfish Binary -The server image installs Stockfish via Alpine's package manager. To use a newer binary, place it at `server/bin/stockfish` — the container mounts this directory at `/app/bin/` and checks it first before the system path. +The server image installs Stockfish via Alpine's package manager. To use a newer binary, place it at `server/bin/stockfish`, and the container mounts this directory at `/app/bin/` and checks it first before the system path. ```bash wget https://... -O server/bin/stockfish @@ -165,7 +165,7 @@ pnpm --filter server db:migrate pnpm dev ``` -Frontend: `http://localhost:5173` — Backend: `http://localhost:3001` +Frontend: `http://localhost:5173`, Backend: `http://localhost:3001` For local analysis, install Stockfish on the host: ```bash @@ -226,7 +226,7 @@ PostgreSQL and Redis have built-in Compose healthchecks. The `server` container - Rate-limits `/api/auth/*` to 5 req/min per IP (brute-force protection) - Sets 1-year immutable cache headers on static assets -TLS is handled entirely by Caddy — Nginx only speaks plain HTTP on the internal Docker network. +TLS is handled entirely by Caddy; Nginx only speaks plain HTTP on the internal Docker network. ## Performance notes diff --git a/docs/development/git-workflow.md b/docs/development/git-workflow.md index e8e8f5b..5b78316 100644 --- a/docs/development/git-workflow.md +++ b/docs/development/git-workflow.md @@ -100,7 +100,7 @@ Before requesting review: 1. Ensure `develop` is stable and CI is green. 2. Open a PR: `develop → main` titled `release: vX.Y.Z`. 3. PR body: summary of all changes since last release (use `git log main..develop --oneline`). -4. Merge the PR (merge commit — preserves history). +4. Merge the PR (merge commit, preserves history). 5. Tag the merge commit on `main`: ```bash @@ -109,7 +109,7 @@ git tag -a v0.2.0 -m "release: v0.2.0" git push origin v0.2.0 ``` -6. Create a GitHub Release from the tag — paste the changelog. +6. Create a GitHub Release from the tag, then paste the changelog. ### Versioning (Semantic Versioning) diff --git a/docs/frontend/architecture.md b/docs/frontend/architecture.md index f5f1288..d0d6e27 100644 --- a/docs/frontend/architecture.md +++ b/docs/frontend/architecture.md @@ -149,6 +149,6 @@ sequenceDiagram ## i18n -Supported locales: `en`, `pt`, `es`. Locale is stored in localStorage and applied on mount. All user-visible strings must use `t('namespace.key')` — never hardcode text in JSX. +Supported locales: `en`, `pt`, `es`. Locale is stored in localStorage and applied on mount. All user-visible strings must use `t('namespace.key')`; never hardcode text in JSX. Translation files live at `client/src/i18n/locales/{locale}.json`. diff --git a/docs/security/security.md b/docs/security/security.md index 34d7d86..22afeb5 100644 --- a/docs/security/security.md +++ b/docs/security/security.md @@ -33,10 +33,10 @@ flowchart TD ### Properties -- Access tokens are short-lived (15 min) — exposure window is small -- Refresh tokens are opaque bytes, stored as SHA-256 hashes — DB breach does not leak raw tokens -- Refresh token rotation on every use — stolen tokens are detected on next legitimate use -- Logout revokes the refresh token — forces re-authentication +- Access tokens are short-lived (15 min), so the exposure window is small +- Refresh tokens are opaque bytes, stored as SHA-256 hashes, so a DB breach does not leak raw tokens +- Refresh token rotation on every use, so stolen tokens are detected on next legitimate use +- Logout revokes the refresh token, which forces re-authentication ## Move Validation @@ -50,7 +50,7 @@ flowchart LR Valid -- no --> Reject["Emit error to sender\nno state change"] ``` -Illegal moves are rejected silently to the room — only the sender receives an error event. This prevents information leakage about the server's game state. +Illegal moves are rejected silently to the room; only the sender receives an error event. This prevents information leakage about the server's game state. ## WebSocket Authorization @@ -61,7 +61,7 @@ Every WebSocket connection is authenticated via `WsAuthGuard`: 3. User identity is attached to the socket for all subsequent events 4. Each handler checks that the acting user is a participant in the referenced game -Room joining is controlled server-side — clients cannot self-subscribe to arbitrary rooms. +Room joining is controlled server-side; clients cannot self-subscribe to arbitrary rooms. ## Input Validation @@ -73,7 +73,7 @@ WebSocket event payloads are manually validated in gateway handlers. | Field | Constraint | |-------|-----------| -| username | 3–30 chars, alphanumeric + underscore | +| username | 3-30 chars, alphanumeric + underscore | | email | valid RFC email | | password | min 8 chars | | move.from / move.to | square regex `[a-h][1-8]` | @@ -127,4 +127,4 @@ add_header Content-Security-Policy "default-src 'self'; ..."; | Email verification | Schema supports it; flow not yet implemented | | 2FA | Not implemented | | Stockfish process isolation | Runs as app user; sandboxing (e.g. seccomp) not applied | -| Account rate limiting (per user, not IP) | Not implemented — relies on IP limits only | +| Account rate limiting (per user, not IP) | Not implemented, relies on IP limits only |