I Built My Own Scrum Poker Because All Others Suck 🎴

Published: (March 16, 2026 at 07:03 AM EDT)
6 min read
Source: Dev.to

Source: Dev.to

The Problem 😩

Sprint planning. Your team needs to estimate tasks. Someone shares a Planning Poker link. You click. You wait. Half the team can’t connect. The other half already went for coffee.

Sound familiar?

At AGG TEAM we tried everything:

  • Planning Poker Online – laggy, ads, ancient UI
  • Scrum Poker for Jira – expensive, requires Jira
  • PlanITpoker – works, but missing OUR features

After the third crash mid‑estimation I thought: “Screw it, I’ll build my own.”


The Real Challenge: 6 Teams, 1 Room 🤯

We don’t have one scrum team – we have six departments:

  1. Frontend
  2. Backend
  3. DevOps
  4. QA
  5. Analytics
  6. Management

Everyone wants to do planning poker simultaneously in one room. It’s chaos – like playing six different card games at the same table. Existing tools say “Here’s one room, figure it out!” – not ideal.


What I Built

🎰 Multi‑Table Support (Up to 6!)

Core concept: one session, multiple independent tables.

interface Table {
  id: string;
  name: string;        // "Frontend Squad", "Backend Ninjas"
  revealed: boolean;    // Are cards revealed for this table?
}

interface Player {
  id: string;
  name: string;
  tableId: string;      // Which table they're at
  vote: string | null; // "5", "13", "?", "☕"
}
  • Host creates tables with custom names.
  • Everyone picks their table.
  • Each table votes independently.
  • You see all tables plus an overall average.

Host powers: add, remove, rename tables on the fly; move people between tables if needed.

⚡ Real‑Time Sync

Used Supabase as backend with a simple polling approach:

useEffect(() => {
  if (view === 'room' && roomId) {
    fetchRoomState();
    const interval = setInterval(fetchRoomState, 2000);
    return () => clearInterval(interval);
  }
}, [view, roomId]);

WebSockets would be cooler, but for prototypes and ~50 users polling works great. If it grows I’ll switch.

Updates in real time:

  • Someone joins → instant
  • Someone votes → immediate
  • Cards revealed → everyone sees results
  • Emoji thrown → flies across screen

💓 Smart Disconnect Detection

Problem with other tools: people close tabs, but their avatars stay forever (ghost users).

My solution uses heartbeat + explicit disconnect:

// Send heartbeat every 10 seconds
const sendHeartbeat = async () => {
  await fetch(`${API_URL}/rooms/${roomId}/heartbeat`, {
    method: 'POST',
    body: JSON.stringify({ playerId }),
  });
};

// Explicit disconnect on page close
window.addEventListener('beforeunload', () => {
  navigator.sendBeacon(
    `${API_URL}/rooms/${roomId}/disconnect`,
    JSON.stringify({ playerId })
  );
});

Result

  • Page open but idle → stays as long as you want (no kicks)
  • Close page/tab → instant disconnect (≈ 2 seconds)

No more ghosts. Clean rooms.

🎉 Emoji Attacks!

Click any participant, throw an emoji. It literally flies from your avatar to theirs with smooth animation.

Available emojis:

👍 👏 🎉 ❤️ 🚀 🔥 😂 🤔 💯 ✨ 👌 🙌

Use cases

  • 👍 agree with estimate
  • 🔥 someone makes a great point
  • 😂 junior estimates a simple task at 89 points
  • ☕ definitely coffee time

This made our plannings fun – people actually look forward to estimation meetings now!

🃏 Fibonacci + Special Cards

Classic series: 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

Plus special cards:

  • ”?” – “I have no idea”
  • ”☕” – “Need coffee before thinking”

You can change your vote even after reveal (most tools lock votes). During discussion you might realize 13 should actually be 8 – my tool allows it.

🧮 Auto‑Average Calculation

Each table shows its average. A grand total appears at the bottom across all tables.

const calculateAverage = (players: Player[], revealed: boolean) => {
  if (!revealed) return null;

  const numericVotes = players
    .map(p => p.vote)
    .filter(v => v && !isNaN(Number(v)))
    .map(Number);

  if (numericVotes.length === 0) return null;
  return (numericVotes.reduce((a, b) => a + b) / numericVotes.length).toFixed(1);
};

Perfect for answering “What’s the overall estimate across all teams?”

🌙 Dark Mode + 🌍 Multi‑Language

Toggle in the corner switches theme (light/dark) and language (EN/RU). Settings are saved to localStorage and applied automatically on next visit.

Because if your app doesn’t have dark mode in 2026… what are you even doing?


Tech Stack 🛠

Frontend

  • React + TypeScript
  • Tailwind CSS v4
  • Lucide icons

Backend

  • Supabase Edge Functions (Hono + Deno)
  • Supabase KV Store (key‑value table)

Why Supabase?

  • Fast setup – no server deployment
  • Edge Functions let me write TypeScript backend logic
  • Simple KV store for room state
  • Free tier sufficient for prototypes

Challenges & Solutions 💡

  1. Ghost Users

    • Problem: Users close tabs, avatars linger.
    • Solution: Heartbeat + explicit disconnect via navigator.sendBeacon.
  2. Z‑Index Wars

    • Problem: Theme toggle covered modals.
    • Solution: Defined a clear hierarchy (z-30 for buttons, z-40 for modals).
  3. State Management

    • Problem: Keeping 6 tables + players in sync.
    • Solution: Single source of truth in the backend; polling for updates.

How Planning Changed

Before

  • “Wait, is everyone loaded?”
  • “Refresh, I don’t see your vote.”
  • “Who’s still here?”
  • awkward waiting

After

  1. Create room (≈ 5 seconds)
  2. Everyone joins (instant)
  3. Vote → reveal → discuss
  4. Throw emojis for fun
  5. Finish on time

Real feedback

“Wait, planning poker can be smooth?”
“I love throwing fire emojis at people!”
“Finally a tool that doesn’t make me rage‑quit.”


What’s Next? 🚀

  • Session history (who voted what)
  • Optional voting timer
  • Exportable reports (CSV/JSON)
  • More language support

Built for six teams, one room, and a lot of coffee.

# Countdown

- Custom decks (T‑shirt sizes?)
- Sound effects (optional)
- Jira integration

---

Key Takeaways 🧠

1. Build what you need
Stop waiting for the perfect tool. 80 % built in a weekend beats 100 % coming “eventually.”

2. Polling isn’t evil
WebSockets are cool, but polling works great for small teams. Don’t over‑engineer.

3. Small delights matter
The emoji feature took 2 hours. It’s everyone’s favorite. Little things make big differences.

4. Dark mode is mandatory
Seriously. It’s 2026.


Try It! 🎮

AGG POKER – Engage teams in collaborative estimation with an interactive Scrum poker tool, allowing users to join sessions and streamline project planning.

scrumpoker.aggone.dev

Use it, free


The Bottom Line

Sometimes a few evenings of coding saves months of frustration. Plus you learn something new. Win‑win.

If your team has the same planning‑poker pain—build your own! We have amazing frameworks, free hosting, and AI assistants now. There’s no excuse.

Questions? Ideas? Want to contribute? Drop a comment!

P.S. Yes, enterprise solutions exist. Mine costs $0, works perfectly for us, and was fun to build. 😄

0 views
Back to Blog

Related posts

Read more »

Luminary: Week 2 — Building the Core

!Cover image for Luminary: Week 2 — Building the Corehttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2F...

Jemalloc un-abandoned by Meta

- Meta recognizes the long‑term benefits of jemalloc, a high‑performance memory allocator, in its software infrastructure. - We are renewing focus on jemalloc,...

Meta’s renewed commitment to jemalloc

Meta recognizes the long‑term benefits of jemalloc, a high‑performance memory allocator, in its software infrastructure. We are renewing focus on jemalloc, aimi...