Jailson Brito
← All side projects

Planning Poker

Peer-to-peer planning poker — story points, or fully custom dimensions.

BetaSince June 2026
  • React
  • TypeScript
  • WebRTC
  • MUI
  • Vite

Peer-to-peer planning poker for Agile teams, with story points or fully custom multi-dimensional estimation — connects browser to browser over encrypted WebRTC, no backend, no accounts.

The problem

My team’s tool only supported single-number story points. I wanted to try rating dimensions separately instead of folding them into one guess — my own first template was Complexity, Uncertainty, Effort, and Risk (CURE). More than that, I was curious whether a real planning-poker session could run on WebRTC alone, with zero backend. That combination — true multi-dimensional estimation with no server at all — wasn’t something I could find anywhere.

Tool Backend required Account to join Custom dimensions
Scrum Poker Online Yes No No
Pointing Poker Yes No No
Story Point Poker Yes No No
Parabol ¹ Yes Yes No
This app No — peer-to-peer No Yes

¹ part of a broader retro/standup suite, not a planning-poker-only tool.

Planning Poker's voting screen, showing a card deck and per-participant vote progressPlanning Poker's voting screen, showing a card deck and per-participant vote progress

Approach and methodology

The open question at the start wasn’t “how do I build planning poker” — it was whether it could be built at all without a server. A same-team voting session needs everyone seeing the same state at the same moment; the obvious way to do that is a server holding the room state. Going in, I didn’t know whether a frontend-only, peer-to-peer web app could do that reliably enough to actually run a meeting on. That was a real open question, not a foregone conclusion — see Results for the answer.

Going serverless wasn’t a given — a WebSocket signalling server, or a real backend entirely, were both simpler in some ways. The reasoning documented in the app itself is direct: no backend means nothing to host and nothing to breach — for a tool used in ten-minute meetings, that trade was worth it. The serverless matchmaking step is handled by a third-party library (Trystero), deliberately pluggable — the same code can run over BitTorrent, MQTT, or Nostr relays as the rendezvous layer; this app uses Nostr, picked as a reasonable default rather than after an exhaustive comparison. Is it safe? The connection itself, yes — WebRTC data channels are encrypted end-to-end (DTLS/SRTP) by the browser, not by anything this app does itself, and that’s mandatory in the WebRTC spec, not optional. The honest caveat: a relay operator can’t read what’s said, but can see that two peers connected and roughly when — connection metadata, not content.

Built with Claude Code as the coding agent, and BDD-first specifically because of that: a Gherkin scenario is an unambiguous, machine-checkable definition of done, which matters more when an AI agent is the one deciding whether a change actually works. The repo’s own docs put it plainly — green BDD suite, or it isn’t finished — not a suggestion, the actual bar every change is held to. 24 feature files now cover host/peer flows, connection health, and even simulated network partitions, run against a fake in-page transport so the protocol logic is thoroughly exercised even though the real signalling path isn’t covered by the automated suite.

This was also my first real project with WebRTC and Trystero — peer-to-peer browser connections weren’t something I’d built with before, and getting a working session up changed how I think about what a no-backend app can actually do.

Architecture

Planning Poker connection and votingA host creates a room and a joining peer's browser is matched to it through a public relay. Once matched, the two browsers open a direct, encrypted WebRTC connection, and votes travel over that connection directly — sent privately to the host until reveal broadcasts every value at once.PeerPublic relay (Nostr)HostPeerPublic relay (Nostr)HostVote value known only to host until revealCreate roomJoin room (room id)Signal peerSignal hostDirect WebRTC connection (encrypted)Cast vote (private)Reveal (broadcast all values)
Planning Poker connection and votingA host creates a room and a joining peer's browser is matched to it through a public relay. Once matched, the two browsers open a direct, encrypted WebRTC connection, and votes travel over that connection directly — sent privately to the host until reveal broadcasts every value at once.PeerPublic relay (Nostr)HostPeerPublic relay (Nostr)HostVote value known only to host until revealCreate roomJoin room (room id)Signal peerSignal hostDirect WebRTC connection (encrypted)Cast vote (private)Reveal (broadcast all values)
A public relay only ever handles matchmaking. Votes travel directly between browsers once connected, private to the host until reveal.

The host is authoritative for round state, and “host” is self-asserted — an acceptable trade for a same-team tool, not a security boundary, per the app’s own docs. Connection reliability is three layers: heartbeat-based liveness per peer, a gossip roster so someone unreachable by the host can still show up via a bridging peer, and a resync mechanism so a refreshed tab is recognised as the same participant.

Estimation isn’t limited to a single point value, and it isn’t limited to one fixed multi-dimensional model either — dimensions are fully custom. A host can add, edit, or remove them in Settings and save the result as a reusable template. Four ship as starting points:

Template Dimensions Scale
Story Point (Classic) Story Point Fibonacci
Effort & Risk Effort, Risk T-shirt
Complexity, Effort & Risk Complexity, Effort, Risk T-shirt
Complexity, Uncertainty, Effort & Risk (CURE) Complexity, Uncertainty, Effort, Risk T-shirt

CURE is the one I personally wanted (see Problem) — Complexity, Uncertainty, Effort, and Risk rated separately, since a small, well-understood but risky change and a large, poorly-understood but low-stakes one look identical on a single point scale, and shouldn’t. But it’s one template among four, not a special case in the code — any team can define whatever dimensions actually matter to them. Five reveal visualisations (radar, quadrant bar, circular target, weighted gauge, profile curve) all adapt automatically to however many dimensions are configured.

Results

The research question got a real answer: yes. A frontend-only, peer-to-peer architecture is enough to run a real planning session on. The MVP shipped with per-dimension estimation, five reveal visualisations, participant management, and a reconnect story that survives a refreshed tab.

That reconnect story wasn’t there from day one. Early on, peers would sometimes go invisible to each other — worst case, to the host — and a refresh made things worse, not better: it lost the vote tally instead of recovering it. The fix is the three-layer reliability system in Architecture above: heartbeat-based liveness so a backgrounded tab isn’t falsely marked gone, a gossip roster so a peer unreachable by the host directly still shows up via a bridging peer, and a stable per-device id so a refreshed tab is recognised and its vote recovered instead of lost. Two real transport bugs surfaced and got fixed along the way — a rejected send used to fail silently, and only the most recent connection-event callback was ever kept instead of all of them — the kind of bug that’s invisible until real people are actually using the thing.

What the no-backend choice actually costs and saves, against the server I didn’t build — maintenance and scaling, not price:

Backend-based Peer-to-peer (what shipped)
Ongoing maintenance Server software, OS, and dependencies need patching; uptime to monitor No server process to patch, monitor, or keep alive
Scaling to more rooms Add server capacity Nothing to scale — each room is peers talking directly, no shared resource
Scaling within a room Not a factor — the server mediates Full mesh — every added participant means more direct connections each peer holds; fine for team-sized rooms, degrades for very large ones
Where reliability lives Centralised — the server is the single source of truth Distributed — every peer independently tracks who’s actually still connected
Failure blast radius One outage can take every room down at once Each room’s connections are independent of every other room

Limitations

  • The signalling layer depends on public relay infrastructure with no SLA — the single most likely point of failure if it ever goes down for everyone at once.
  • Strict/corporate NATs sometimes can’t open a direct connection without a self-hosted TURN server, not configured by default.
  • If the host leaves for good, nobody is promoted — reveal and reset stop working for whoever’s left.
  • A vote-value relay for peers the host can’t reach directly is designed but not built — ship TURN support first, build the relay only if real sessions still need it.
  • The real WebRTC signalling path isn’t covered by the automated test suite — only the app’s own protocol logic, against a simulated transport.

Work in progress

The vote-value relay above, gated on real usage data.

A browser-extension build and a Jira Forge add-on are on the roadmap — the core was kept framework-agnostic specifically so they’re additive later.

Host handoff, for when the host disconnects for good.

The connection-reliability layer — liveness, gossip roster, resync — doesn’t depend on anything specific to planning poker. It could become its own small library for any peer-to-peer tool that needs to know who’s actually still there.