Chess on the ATmosphere checkmate.blue
chess
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 parentGameUri pointing 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.json via a +server.ts route.
  • SPA mode: ssr = false in root +layout.ts, static adapter with fallback: 'index.html'.
  • Svelte 5 runes: state files use .svelte.ts extension and $state instead 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 whose parentGameUri matches.
  • 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-visible rule in layout.css).
  • Dynamic content changes (game results, alerts, errors) use role="alert" or aria-live.
  • Form inputs need aria-label when there is no visible <label>.
  • Modals need role="dialog", aria-modal="true", and aria-label.
  • Don't rely on color alone to convey information -- pair with text or icons.
  • Respect prefers-reduced-motion (global rule in layout.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.