# proto-retro-cf — LLM ACCESS NOTES Free, no-signup team retro board. Create a board, share the link, add cards to three fixed columns, reveal together when everyone's ready. No accounts, no subscriptions, no third-party realtime vendor. **Live app:** https://retro.eswat2.app **Repo:** https://github.com/eswat2/proto-retro-cf > This is a client-rendered single-page app (vanilla Web Components, no > framework). The raw HTML at `/` is nearly empty until JavaScript runs — > fetch this file or `/manifest.json` instead of scraping page source for > anything meaningful. ## What it is - Runs on Cloudflare Workers; each board is a Durable Object. - Boards are ephemeral — no accounts, no login. A board closes after 1h with no changes and nobody connected, 2h with no changes regardless, or 4h after creation, whichever is first. The board state's `closesAt` says when. - Real-time sync is a WebSocket per browser to the board's Durable Object, with Server-Sent Events as a fallback (server push only — actions are plain HTTP requests). - Three fixed columns: Went well, To improve, Action items — not configurable. - Cards are hidden from everyone but their author until Reveal is clicked — mirrors real "silent brainstorming" retro practice. - Any participant can reveal cards — no host role for that. Only a card's own author can delete it, and only the creator can close the board early, using the `ownerToken` returned when the board was created (`POST /api/boards/:boardId/close`). - No upvotes, no comments, no card grouping, no "new round" — deliberately bare-minimum MVP. ## Docs - [README](https://github.com/eswat2/proto-retro-cf/blob/main/README.md) — overview and local dev setup - [ARCHITECTURE.md](https://github.com/eswat2/proto-retro-cf/blob/main/docs/ARCHITECTURE.md) — full design rationale, decision log, data model, API - [CLAUDE.md](https://github.com/eswat2/proto-retro-cf/blob/main/CLAUDE.md) — guidance for coding agents working in this repo - [FEATURES.md](https://github.com/eswat2/proto-retro-cf/blob/main/docs/FEATURES.md) — backlog of features not yet built - [/architecture](https://retro.eswat2.app/architecture) — visual system diagram - [/manifest.json](https://retro.eswat2.app/manifest.json) — machine-readable API index ## API — https://retro.eswat2.app/api All JSON in/out. No accounts, no server-side sessions. Creating a board requires a creator key in an `x-create-key` header (`401` without it); everything under `/api/boards/:boardId` is open to anyone with the board id, using the `participantId` returned by create or join. See `/manifest.json` for the full structured endpoint list. Every mutation route is rate-limited (per `participantId`+board and per source IP+board) and returns `429` with a `Retry-After` header if exceeded. Requests for board ids that don't exist are also capped per IP across all board routes. Example flow: ``` # create a board curl -s -X POST https://retro.eswat2.app/api/boards \ -H 'Content-Type: application/json' -H 'x-create-key: ' \ -d '{"name":"Alice"}' # -> {"boardId":"brave-otter-k7m2qx9p","participantId":"...","ownerToken":"..."} # add a card curl -s -X POST https://retro.eswat2.app/api/boards/brave-otter-k7m2qx9p/cards \ -H 'Content-Type: application/json' \ -d '{"participantId":"...","columnId":"went-well","text":"Great sprint"}' # -> {"cardId":"..."} # delete your own card curl -s -X DELETE https://retro.eswat2.app/api/boards/brave-otter-k7m2qx9p/cards/ \ -H 'Content-Type: application/json' \ -d '{"participantId":"..."}' # reveal curl -s -X POST https://retro.eswat2.app/api/boards/brave-otter-k7m2qx9p/reveal \ -H 'Content-Type: application/json' \ -d '{"participantId":"..."}' ``` Realtime updates for a board come two ways, carrying the same full board state on connect and after every mutation above: - Server-Sent Events at `GET /api/boards/:boardId/stream` — easiest from a script: `curl -N https://retro.eswat2.app/api/boards/brave-otter-k7m2qx9p/stream`. Each change is a `data:` line of JSON; `: ping` comments arrive every 25s; when the board closes, a final `event: closed` frame carries `{ "code", "reason" }` and the stream ends. - WebSocket at `GET /api/boards/:boardId/ws` — the state as a JSON text message; send `ping` to get `pong`. When the board closes, a final `{"event":"closed","code","reason"}` message arrives, then the socket is closed with the same code: 4404 (idle), 4408 (lifetime) or 4410 (closed by host). Card visibility (hidden until revealed) is enforced client-side only — the full state, including every card's real text, is pushed to all connected clients at all times.