Project Configuration#
- Language: TypeScript
- Package Manager: npm
- Add-ons: tailwindcss, sveltekit-adapter
checkmate.blue#
Federated chess platform built on the ATmosphere. Players authenticate with their ATmosphere account, play real-time 1v1 chess with game state stored as AT Protocol records, move updates via Jetstream. No application database, no server-side game logic.
See SPEC.md for full technical specification.
Stack#
- Framework: SvelteKit (static adapter)
- Chess logic: chess.js (client-side validation, PGN generation)
- Board UI: @lichess-org/chessground (Lichess BSD-licensed board)
- Auth: @atproto/oauth-client-browser (sessions in IndexedDB)
- AT Protocol: @atproto/api (read/write records to PDS)
- Real-time: Jetstream WebSocket (opponent move events)
- Queries: Constellation (game discovery), Slingshot (handle/DID resolution)
- Styling: Tailwind CSS
- Domain: checkmate.blue
Key Architecture Decisions#
- No app server or database. SvelteKit app deploys as a static site.
- Each player writes moves to their OWN repo (atproto permissions are per-user). Both players maintain their own game record with full PGN.
- White's game record is the "primary" record; Black's record has a
parentGameUripointing back to it. The game URL always uses White's DID and rkey. - On load, both records are read and the longer PGN is used (since each record is one move behind 50% of the time).
- PGN is the source of truth for game state.
- Move validation is client-side only (acceptable for PoC).
- Jetstream delivers real-time move updates; falls back to polling on disconnect.
Lexicons#
Namespace: blue.checkmate.*
blue.checkmate.game-- chess game record (key: tid)blue.checkmate.challenge-- challenge to play (key: tid)
Lexicon definitions live in lexicons/.
Commands#
npm run dev # Start dev server
npm run build # Build for production
npm run preview # Preview production build
npm run check # Type check
Development Notes#
- Vite dev server binds to
127.0.0.1(not localhost) -- required for atproto OAuth loopback client. - In dev mode, OAuth uses
clientMetadata: undefined(loopback client). In prod, it uses the full metadata object. - OAuth client-metadata.json is prerendered at
/oauth/client-metadata.jsonvia a+server.tsroute. - SPA mode:
ssr = falsein root+layout.ts, static adapter withfallback: 'index.html'. - Svelte 5 runes: state files use
.svelte.tsextension and$stateinstead of traditional stores. - Chessground requires 3 CSS imports (base, brown, cburnett) -- all imported in
layout.css.
Game Record Pairing#
When Black joins a game, they create their own blue.checkmate.game record on their PDS with parentGameUri set to White's game AT-URI. This pairing is how each side finds the other's record:
- White finds Black's record:
findGameRecordByParent(agent, blackDid, parentUri)searches Black's PDS for a record whoseparentGameUrimatches. - Black finds their own record: Same function against their own PDS.
When White discovers Black via Jetstream (waitForOpponent), White persists { black: blackDid, status: 'active' } to their own record so the pairing survives page reload.
Accessibility#
All UI changes must follow WCAG 2.1 AA standards:
- Interactive elements need visible focus indicators (global
:focus-visiblerule inlayout.css). - Dynamic content changes (game results, alerts, errors) use
role="alert"oraria-live. - Form inputs need
aria-labelwhen there is no visible<label>. - Modals need
role="dialog",aria-modal="true", andaria-label. - Don't rely on color alone to convey information -- pair with text or icons.
- Respect
prefers-reduced-motion(global rule inlayout.css). - Each route should have a descriptive
<title>via<svelte:head>.
Specs#
Feature specs live in specs/ and follow the /spec workflow. Treat specs as source of truth for implementation. If something isn't covered by a spec, ask rather than guessing.