Skip to content

re-cinq/retro

Repository files navigation

retro-board

A password-protected, real-time retrospective board. Columns for Happy / Idea / Sad / Action; vote, discuss with a 5-minute timer, end the retro to archive it. Sync happens peer-to-peer between browsers on the same URL with the same password — no server sees your plaintext.

How it works

  • CRDT state. A Yjs document per board holds the items, votes, archives, and who's currently being discussed. Concurrent edits merge intent-preservingly.
  • Transport. y-webrtc carries Yjs updates directly between peers via WebRTC data channels. Peers find each other through a tiny WebSocket signaling server; everything on the wire is encrypted with an AES-GCM key derived from the board password.
  • Persistence. y-indexeddb mirrors the doc to each browser's local storage, so a board is still usable offline and restores on reopen.
  • Auth model. The password never leaves the browser. A slug-derived canary value, encrypted with the password-derived key, tells a joining peer whether the password they typed matches the one everyone else is using — without needing to decrypt live traffic to find out. src/board/session.ts has the details.

The feature contract is in features.md; every live statement there is asserted by an end-to-end Playwright test.

Quick start

npm install
npm run dev             # Vite at http://localhost:5173
npm run signaling       # y-webrtc signaling server on :4444 (separate terminal)

Open two browsers at http://localhost:5173/b/<any-slug> and use the same password; they'll sync in real time.

Testing

npm run test:e2e        # Playwright against a one-shot signaling server + dev server
npm run check:features  # every feature ID in features.md has a matching test

Deployment

Deploys to Cloudflare as a single Worker with a static-assets binding and a Durable Object for the signaling server. Production hostname is retro.re-cinq.com (declared in wrangler.toml).

npx wrangler login      # one-time, OAuth in a browser
npm run deploy          # builds dist/ and pushes the Worker

First deploy creates the Worker, applies the v1 Durable Object migration, uploads dist/ to the assets binding, and attaches the custom domain automatically (the parent zone is already on the same Cloudflare account).

Stream live Worker logs with npx wrangler tail.

The TURN relay for users behind symmetric NAT / strict firewalls is not yet wired; see plans/cloudflare-bringup.md for the second-cut plan.

Local development: cross-profile Chrome on one machine

When testing P2P sync across two Chrome profiles on the same host, each profile's WebRTC offer carries anonymised *.local mDNS hostnames instead of your real local IP. The profiles can't resolve each other's mDNS names, ICE never picks a candidate pair, and the app shows "Wrong password" after the 5 s canary timeout — even when the password is correct.

Workaround: in each profile visit chrome://flags/#enable-webrtc-hide-local-ips-with-mdns, set it to Disabled, restart Chrome. Two tabs within a single profile don't need this — they sync via the shared IndexedDB broadcast channel. Doesn't affect real deployments.

License

Licensed under the AI Native Application License (AINAL) v2.0.

About

Peer-to-peer retro board

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors