Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# =================================================================
# ChessKernel Environment Variables
# ChessKernel: Environment Variables
# Copy this file to .env and fill in your values before starting.
# =================================================================

Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions client/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand All @@ -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%;
Expand Down Expand Up @@ -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); }
Expand Down Expand Up @@ -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%); }
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/chess/ChessBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const CLS: Record<string, { bg: string; text: string; icon: string; sqColor: str
miss: { bg: '#ca3431', text: '#fff', icon: '×', sqColor: 'rgba(202,52,49,0.45)' },
};

// Standalone classification badge its own SVG overlay, independent of arrows
// Standalone classification badge: its own SVG overlay, independent of arrows
function ClassificationBadge({
square, cls, orientation,
}: {
Expand Down Expand Up @@ -277,7 +277,7 @@ export function ChessBoard({
return `${'abcdefgh'[col]}${row + 1}`;
}, [orientation]);

// Right-click arrow drawing capture phase so react-chessboard doesn't see right-clicks
// Right-click arrow drawing: capture phase so react-chessboard doesn't see right-clicks
useEffect(() => {
const el = boardRef.current;
if (!el) return;
Expand Down Expand Up @@ -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 && (
<ClassificationBadge
square={lastMove.to}
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/chess/MaterialCount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { PieceIcon } from './PieceIcon';
const PIECE_VALUES: Record<string, number> = { p: 1, n: 3, b: 3, r: 5, q: 9 };
const STARTING: Record<string, number> = { 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 {
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/chess/PieceIcon.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
6 changes: 3 additions & 3 deletions client/src/pages/AnalysisPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */}
<div
className="absolute left-0 right-0 flex justify-center items-center pointer-events-none z-10"
style={whiteWinning ? { bottom: 5 } : { top: 5 }}
Expand Down Expand Up @@ -266,7 +266,7 @@ export function AnalysisPage() {
</button>
<div className="min-w-0">
<h1 className="font-bold text-lg leading-tight truncate">{white} vs {black}</h1>
<p className="text-xs text-muted-foreground">{game.result ?? ''} · {game.timeControl ?? t('analysis.title')}</p>
<p className="text-xs text-muted-foreground">{game.result ?? '-'} · {game.timeControl ?? t('analysis.title')}</p>
</div>
</div>

Expand Down Expand Up @@ -408,7 +408,7 @@ export function AnalysisPage() {
</div>
</div>

{/* Move list chess.com style */}
{/* Move list: chess.com style */}
<div className="bg-card border border-border rounded-xl overflow-hidden">
<div className="px-4 py-2.5 border-b border-border bg-muted/30 flex items-center gap-2">
<span className="font-semibold text-sm">{t('analysis.moves')}</span>
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/GamePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions client/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function HomePage() {
</div>
</div>

{/* Chess board real piece images via react-chessboard */}
{/* Chess board: real piece images via react-chessboard */}
<div className="shrink-0 relative" style={{ pointerEvents: 'none', userSelect: 'none', width: 300 }}>
<div
className="absolute rounded-3xl opacity-20 blur-3xl"
Expand All @@ -94,7 +94,7 @@ export function HomePage() {
</div>
</section>

{/* Features infinite-scroll carousel, pauses on hover */}
{/* Features: infinite-scroll carousel, pauses on hover */}
<section className="py-20 border-t border-border">
<div className="px-4 max-w-5xl mx-auto text-center mb-12">
<h2 className="text-3xl font-black tracking-tight mb-3">{t('home.featuresTitle')}</h2>
Expand Down
4 changes: 2 additions & 2 deletions docs/adr/001-technology-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ 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

Prisma's type-safe client, excellent migration tooling, and schema-first approach reduce runtime errors. TypeORM has well-known N+1 issues and weaker type inference.

### 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)

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/002-game-state-authority.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 8 additions & 8 deletions docs/api/api-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand All @@ -45,14 +45,14 @@ Rotate token pair. Send current refresh token.
```json
{ "refreshToken": "<opaque>" }
```
**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.

---

Expand Down Expand Up @@ -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.

---

Expand Down Expand Up @@ -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.

---

Expand Down Expand Up @@ -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).

---

Expand Down Expand Up @@ -380,7 +380,7 @@ io('https://<host>', { auth: { token: '<access_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 |
Expand Down
24 changes: 12 additions & 12 deletions docs/architecture/system-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -141,26 +141,26 @@ Move classification thresholds (centipawn drop from best move):
| Classification | CP Drop |
|----------------|---------|
| Brilliant | engine says sub-optimal but tactically sharp |
| Best / Excellent | 010 |
| Good | 1025 |
| Inaccuracy | 25100 |
| Mistake | 100300 |
| Best / Excellent | 0-10 |
| Good | 10-25 |
| Inaccuracy | 25-100 |
| Mistake | 100-300 |
| Blunder | >300 |
| Book | matches opening book |

## WebSocket Event System

### 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

Expand All @@ -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) |

Expand Down
12 changes: 6 additions & 6 deletions docs/backend/backend-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 | 010 |
| Good | 1025 |
| Inaccuracy | 25100 |
| Mistake | 100300 |
| Best / Excellent | 0-10 |
| Good | 10-25 |
| Inaccuracy | 25-100 |
| Mistake | 100-300 |
| Blunder | >300 |
| Book | matches opening book |

Expand Down
Loading
Loading