# Project Status - atprotogo **Last Updated:** September 22, 2026 ## Current State The application builds (`npm run build`) and runs on Cloudflare Pages with a D1 database. Gameplay, social and image features are implemented end to end, and game records are really published to the AT Protocol network. ## Implemented ### Platform - SvelteKit app built for Cloudflare Pages (`@sveltejs/adapter-cloudflare`), deployed with `npm run deploy` / `wrangler pages deploy` - D1 database (`atprotogo-db`, bound as `DB`) accessed through Kysely (`kysely-d1`) - OAuth sessions and state in KV when the `SESSIONS_KV` / `STATES_KV` bindings are configured, otherwise an in-memory store - Bluesky OAuth via `@atcute/oauth-node-client`, in two configurations: a public *loopback* client for local development (the atproto localhost exception — the authorization server synthesises the metadata from the `client_id`, so nothing is published and no signing key is used) and a *confidential* client that publishes `/oauth/v2/client-metadata.json` and signs its requests with `PRIVATE_KEY_JWK` - Image generation (board, OG, reaction, nudge) with `@cf-wasm/resvg` ### Features - **Game Creation**: Create Go games with various board sizes (5x5, 7x7, 9x9, 13x13, 19x19) - **Game Joining**: Join waiting games or play with specific opponents - **Gameplay**: Place stones, pass, resign - **Handicap**: Support for handicap stones (up to 9 on 19x19, 5 on 13x13) - **Scoring**: tenuki territory scoring with dead-stone marking and winner determination - **Reactions**: Comment on specific moves with text, emoji, and star ratings - **Profiles**: Player profiles with game history and ELO ratings - **Image Generation**: Board SVGs/PNGs, OG images, reaction images, nudge images - **Real-time**: Client-side Jetstream subscriber (`src/lib/firehose.ts`) streams moves, passes and resignations into the game page ## Architecture Notes ### Real-time updates The game page subscribes to Bluesky's public Jetstream over a WebSocket and applies `boo.sky.go.move` / `pass` / `resign` records as they are published, so players see opponent moves without refreshing. ### D1 discovery index Route handlers resolve a game by reading the D1 `games` table first and only sampling the network when it is missing. That index is maintained by a Durable Object Jetstream consumer in the standalone `firehose-worker/` Worker (its own `wrangler.toml`, deployed separately from the Pages app): the Durable Object holds a WebSocket to Jetstream filtered to `boo.sky.go.{game,move,pass,resign,action}`, applies records to the same D1 database through the shared helpers in `src/lib/server/firehose-records.ts`, resumes from a stored Jetstream cursor after reconnects (~36h of retention), and uses a Durable Object alarm as keepalive. A Cron Trigger bootstraps and recovers the consumer. It is a separate Worker because `@sveltejs/adapter-cloudflare` emits a fixed `_worker.js` and offers no supported way to export extra Worker modules from the Pages app. ## File Structure ``` src/ ├── routes/ │ ├── game/[id]/+page.svelte # Main game UI with reactions │ ├── profile/[did]/ # Player profiles │ ├── og-image/[id]/+server.ts # Social image generation │ └── api/games/ │ ├── +server.ts # Create game │ └── [id]/ │ ├── join/+server.ts # Join game │ ├── move/+server.ts # Record move │ ├── pass/+server.ts # Record pass │ ├── cancel/+server.ts # Resign │ ├── reaction/+server.ts # Create reaction │ └── score/+server.ts # Scoring ├── lib/ │ ├── server/ │ │ ├── db.ts # D1 + Kysely, ensureGameInDb() │ │ ├── auth.ts # OAuth client │ │ ├── scoring.ts # tenuki scoring │ │ ├── elo.ts # Ratings │ │ └── firehose-records.ts # Shared record → D1 helpers (used by firehose-worker) │ ├── atproto-client.ts # AT Protocol helpers │ ├── firehose.ts # Client-side Jetstream subscriber │ └── components/ │ └── Board.svelte # jgoboard board component lexicons/ ├── boo.sky.go.game.json ├── boo.sky.go.move.json ├── boo.sky.go.pass.json ├── boo.sky.go.resign.json ├── boo.sky.go.reaction.json └── boo.sky.go.profile.json migrations/ └── 0001_initial_schema.sql # D1 schema firehose-worker/ # Durable Object Jetstream consumer ``` ## Development ### Local Development ```bash # Install dependencies npm install # Build, then serve the worker with its D1 and KV bindings npm run build npx wrangler pages dev --port 8788 ``` Logging in needs neither a tunnel nor a signing key on a loopback origin, so `npm run setup:key` is only required to exercise the deployed (confidential) client configuration. Open the app at `http://127.0.0.1:8788`, not `localhost` — the spec only allows `127.0.0.1`/`[::1]` redirect URIs. See `LOCAL_DEVELOPMENT.md`. ### Production (Cloudflare Pages) ```bash # Build for production npm run build # Deploy to Cloudflare npx wrangler pages deploy .svelte-kit/cloudflare --project-name=atprotogo ``` The discovery-index consumer deploys separately: ```bash npm run deploy:firehose ``` **Production Stack:** - Cloudflare D1 database (`atprotogo-db`) - Cloudflare KV for sessions/state (optional bindings) - Environment secrets from the dashboard - Global edge deployment ## Known Limitations - Scoring is a territory estimate that relies on players marking dead stones - The D1 discovery index only contains what the Jetstream consumer has seen (plus manual backfill via `npm run backfill`), so older games may be missing until backfilled - No time controls or undo functionality ## Documentation - `README.md` - Project overview and setup - `CLOUDFLARE_DEPLOYMENT.md` - Deployment guide - `MIGRATION_SUMMARY.md` - Migration details - `LOCAL_DEVELOPMENT.md` - Local OAuth setup