DEV Community

ArcaneGaming
ArcaneGaming

Posted on

I Built My Own Scrum Poker Because All Others Suck 🎴

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 🤯

Here's the twist: we don't have ONE scrum team. We have SIX departments:

  • Frontend, Backend, DevOps, QA, Analytics, 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", "?", "☕"
}
Enter fullscreen mode Exit fullscreen mode

Host creates tables with custom names. Everyone picks their table. Each table votes independently. You see all tables + 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. Simple polling approach:

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

Yeah, 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 }),
  });
};

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

Result:

  • Page open but idle? Stay as long as you want (no kicks)
  • Close page/tab? Instant disconnect (2 seconds)

No more ghosts. Clean rooms.

🎉 Emoji Attacks!

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

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

Real use cases:

  • 👍 agree with estimate
  • 🔥 someone makes a great point
  • 😂 junior estimates 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: 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

Plus:

  • "?" → "I have no idea"
  • "☕" → "Need coffee before thinking"

You can change your vote even after reveal. Why? Because during discussion you might realize 13 should actually be 8. Most tools lock votes. Mine doesn't.

🧮 Auto-Average Calculation

Each table shows its average. Plus grand total at bottom across ALL tables.

const calculateAverage = (players, revealed) => {
  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);
};
Enter fullscreen mode Exit fullscreen mode

Perfect for: "What's the overall estimate across all teams?"

🌙 Dark Mode + 🌍 Multi-Language

Toggle in corner switches theme (light/dark) and language (EN/RU). Saved to localStorage, auto-applied next time.

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 (TypeScript backend)
  • Simple KV store for room state
  • Free tier for prototypes

Challenges & Solutions 💡

1. Ghost Users

Problem: Users close tabs, avatars stay forever

Solution: Heartbeat mechanism + explicit disconnect via navigator.sendBeacon

2. Z-Index Wars

Problem: Theme toggle covered modals

Solution: Proper z-index hierarchy (z-30 buttons, z-40 modals)

3. State Management

Problem: Keeping 6 tables + players in sync

Solution: Single source of truth in 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:

  • Create room (5 seconds)
  • Everyone joins (instant)
  • Vote, reveal, discuss
  • Throw emojis for fun
  • Actually 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)
  • Voting timer (optional 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
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 cost $0, works perfectly for us, and was fun to build. 😄

Top comments (0)