Real-time planning poker for distributed teams: versioned room snapshots over Pusher, server-authoritative simultaneous reveal, and story points written back to Jira, GitHub, Trello and IzzyPlan.

01 Public landing in English and Portuguese, with direct link-based entry into a room.
The server owns room state and publishes the whole snapshot with a monotonic version number. Clients apply an update only when the incoming version is higher, so a duplicated, out-of-order or dropped event cannot corrupt the table. Pusher caps payloads at 10 KB, so large snapshots go out truncated and the client pulls the full state over GET.
Routes that ran up to seven serial database round-trips now send their writes along with the snapshot read inside a single prisma.$transaction. The Pusher broadcast moved into Next's after(), off the response path, and a failed broadcast never fails the mutation because the reconciliation poll covers it.
Reveal uses an absolute server instant: the client corrects its own clock offset and the server clamps the proposed value so nobody can rush or delay everyone else's countdown. Two concurrent reveal clicks become an updateMany with the condition in the filter, and a vote is an upsert on the participant + story composite key.
Poker Architect is a planning poker tool for teams that estimate remotely. Rooms open from a link, votes stay hidden until reveal, and the number lands before the discussion, which is the whole point for teams using the ritual to avoid anchoring on the loudest senior in the call.
The app is Next.js 15 on the App Router with TypeScript, Prisma 6 over PostgreSQL and NextAuth v4 with database sessions and GitHub or Google sign-in. The interface is Tailwind with shadcn/ui on Radix, light and dark themes, and next-intl covering English and Portuguese across every screen. It runs 9 pages, 25 API routes and 14 Prisma models.
Realtime is Pusher Channels, one channel per room and a single event, room:state. The interesting part is not the transport, it is the state model around it.
A planning poker table has several people mutating the same object at once, on different networks, some behind corporate firewalls that block WebSockets. Emitting incremental events like "Ana voted" breaks the moment one event is lost: from then on, every client renders a different table.
Here the server is the source of truth and publishes the entire room snapshot with a monotonic version. The client compares versions and discards anything stale. As a result:
On top of that sits the safety net: adaptive reconciliation polling, every 20 seconds while the socket is connected and every 4 seconds when it drops, running only while the tab is visible and reconciling on focus, online and visibility changes. Presence uses a 45-second heartbeat plus a sendBeacon on pagehide, because leaving a room cannot depend on a request finishing.
The database is remote, so every round-trip is expensive. Routes that serialized four to seven queries were pushing past a second. The fix was to send the writes together with the snapshot read in the same transaction, and to bump the version with update ... returning instead of spending a query reading it back. The broadcast left the critical path through Next's after().
The broadcast snapshot carries only hasVoted. Vote values appear after the story is revealed, and a participant's own vote comes back in the response to their own GET. You cannot open devtools and read a teammate's card early.
| Tracker | Auth | Pulls tasks | Writes points |
|---|---|---|---|
| Jira | OAuth 3LO with cloudId discovery | JQL for unfinished items | Story points field, comment fallback |
| GitHub Issues | OAuth with repo scope | Open issues, PRs filtered out | Comment on the issue |
| Trello | Fragment token with a callback bridge | Board cards | Comment on the card |
| IzzyPlan | API token validated before saving | Projects and tasks | No, the public API is read-only |
A provider with missing environment variables shows as unavailable instead of offering a button that breaks.
No automated tests and no rate limiting so far. Architecture notes live in CLAUDE.md and in long comments at the concurrency-sensitive spots, which is what held the decisions above together while the product kept changing.