내가 직접 만든 Scrum Poker, 다른 건 다 별로라서 🎴

발행: (2026년 3월 16일 오후 08:03 GMT+9)
12 분 소요
원문: Dev.to

Source: Dev.to

The Problem 😩

스프린트 계획. 팀이 작업을 추정해야 합니다. 누군가가 Planning Poker 링크를 공유합니다. 클릭하고 기다립니다. 팀의 절반은 연결되지 못하고, 나머지 절반은 이미 커피를 마시러 갔습니다.

익숙하신가요?

AGG TEAM에서는 모든 방법을 시도했습니다:

  • Planning Poker Online – 지연, 광고, 구식 UI
  • Scrum Poker for Jira – 비싸고, Jira 필요
  • PlanITpoker – 작동하지만 우리의 기능이 부족

세 번째 추정 중 충돌이 발생한 후, 나는 생각했습니다: “젠장, 직접 만들자.”

실제 도전 과제: 6팀, 1개의 방 🤯

우리는 한 개의 스크럼 팀이 아니라 여섯 개 부서가 있습니다:

  1. 프론트엔드
  2. 백엔드
  3. DevOps
  4. QA
  5. 애널리틱스
  6. 경영

모두가 한 방에서 동시에 플래닝 포커를 하길 원합니다. 혼란스럽습니다 – 마치 같은 테이블에서 여섯 가지 다른 카드 게임을 하는 것과 같습니다. 기존 도구들은 “한 방이 있으니 스스로 해결하세요!”라고 말하지만 – 이상적이지 않죠.

Source:

내가 만든 것

🎰 멀티‑테이블 지원 (최대 6개!)

핵심 개념: 하나의 세션, 여러 독립 테이블.

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", "?", "☕"
}
  • 호스트가 커스텀 이름으로 테이블을 생성합니다.
  • 모든 사용자가 자신이 속할 테이블을 선택합니다.
  • 각 테이블은 독립적으로 투표합니다.
  • 전체 평균과 모든 테이블을 동시에 확인할 수 있습니다.

호스트 권한: 테이블을 실시간으로 추가·삭제·이름 변경; 필요하면 사람을 다른 테이블로 이동시킵니다.

⚡ 실시간 동기화

백엔드로 Supabase를 사용하고 간단한 폴링 방식을 적용했습니다:

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

WebSocket이 더 멋지겠지만, 프로토타입 단계와 약 50명 정도의 사용자라면 폴링으로 충분합니다. 규모가 커지면 전환할 예정입니다.

실시간 업데이트:

  • 누군가 입장 → 즉시
  • 누군가 투표 → 바로 반영
  • 카드 공개 → 모두 결과 확인
  • 이모지 전송 → 화면을 가로질러 날아감

💓 스마트 연결 끊김 감지

다른 도구들의 문제점: 탭을 닫아도 아바타가 영원히 남아 ‘유령 사용자’가 됨.

내 해결책은 하트비트 + 명시적 연결 끊김입니다:

// 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 })
  );
});

결과

  • 페이지는 열려 있지만 유휴 상태 → 원하는 만큼 유지 (강제 퇴장 없음)
  • 페이지/탭을 닫으면 → 즉시 연결 끊김 (≈ 2 초)

유령이 사라지고 방이 깔끔해집니다.

🎉 이모지 공격!

참가자를 클릭하면 이모지를 던집니다. 이모지는 아바타에서 상대 아바타까지 실제로 날아가듯 부드럽게 애니메이션됩니다.

사용 가능한 이모지:

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

활용 예시

  • 👍 추정에 동의
  • 🔥 훌륭한 의견에 박수
  • 😂 주니어가 간단한 작업을 89점으로 추정
  • ☕ 커피가 필요함을 표시

덕분에 우리 플래닝이 재미있어졌고, 이제 추정 회의를 기대하게 됩니다!

🃏 피보나치 + 특수 카드

클래식 시리즈: 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

거기에 특수 카드 추가:

  • ”?” – “전혀 모르겠어요”
  • ”☕” – “생각하기 전에 커피가 필요해요”

카드 공개 후에도 투표를 변경할 수 있습니다 (대부분의 도구는 투표를 잠급니다). 토론 중에 13점이 실제로는 8점이어야 한다는 걸 깨달아도 내 도구는 허용합니다.

🧮 자동 평균 계산

각 테이블은 자체 평균을 보여줍니다. 전체 테이블을 합산한 총 평균이 하단에 표시됩니다.

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

“전체 팀의 추정치는 얼마인가?” 라는 질문에 딱 맞는 기능입니다.

🌙 다크 모드 + 🌍 다국어

우측 구석의 토글로 테마(라이트/다크) 언어(EN/RU)를 전환합니다. 설정은 localStorage에 저장돼 다음 방문 시 자동 적용됩니다.

2026년에 앱에 다크 모드가 없으면… 뭘 하고 있는 건가요?

Tech Stack 🛠

프론트엔드

  • React + TypeScript
  • Tailwind CSS v4
  • Lucide 아이콘

백엔드

  • Supabase Edge Functions (Hono + Deno)
  • Supabase KV Store (키‑값 테이블)

왜 Supabase인가?

  • 빠른 설정 – 서버 배포 불필요
  • Edge Functions를 통해 TypeScript 백엔드 로직 작성 가능
  • 방 상태를 위한 간단한 KV 스토어
  • 무료 티어로 프로토타입 충분히 구현 가능

도전 과제 및 해결책 💡

  1. Ghost Users

    • 문제: 사용자가 탭을 닫으면 아바타가 남아 있음.
    • 해결책: Heartbeat + navigator.sendBeacon을 이용한 명시적 연결 해제.
  2. Z‑Index Wars

    • 문제: 테마 토글이 모달을 가림.
    • 해결책: 명확한 계층 구조 정의 (z-30은 버튼, z-40은 모달).
  3. State Management

    • 문제: 6개의 테이블과 플레이어를 동기화 유지.
    • 해결책: 백엔드에 단일 진실 소스 유지; 업데이트를 위한 폴링.

계획이 바뀐 방식

Before

  • “잠깐, 모두 로드됐나요?”
  • “새로고침해 주세요, 당신의 투표가 안 보여요.”
  • “아직도 여기 있는 사람은?”
  • 어색한 대기

After

  1. 방 만들기 (≈ 5 초)
  2. 모두 입장 (즉시)
  3. 투표 → 공개 → 토론
  4. 재미로 이모지 던지기
  5. 제시간에 마무리

Real feedback

“잠깐, 플래닝 포커가 이렇게 부드러울 수 있나요?”
“사람들에게 불 이모지를 던지는 게 정말 좋아요!”
“드디어 나를 분노 퇴장하게 만들지 않는 도구가 나왔어요.”


What’s Next? 🚀

  • 세션 기록 (누가 무엇에 투표했는지)
  • 선택적 투표 타이머
  • 내보낼 수 있는 보고서 (CSV/JSON)
  • 더 많은 언어 지원

6개의 팀, 하나의 방, 그리고 많은 커피를 위해 만들어졌습니다.

# Countdown

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

---

Key Takeaways 🧠

1. Build what you need
완벽한 도구를 기다리는 것을 그만두세요. 주말에 만든 80 %가 결국 나올 100 %보다 낫습니다.

2. Polling isn’t evil
WebSocket은 멋지지만, 폴링은 작은 팀에 아주 잘 맞습니다. 과도하게 설계하지 마세요.

3. Small delights matter
이모지 기능은 2 시간 만에 만들었습니다. 모두가 가장 좋아하죠. 작은 것이 큰 차이를 만듭니다.

4. Dark mode is mandatory
진심입니다. 2026년이니까요.

Try It! 🎮

AGG POKER – 팀이 협업 추정에 참여하도록 하는 인터랙티브 스크럼 포커 도구로, 사용자가 세션에 참여하고 프로젝트 계획을 효율화할 수 있습니다.

scrumpoker.aggone.dev

무료로 사용하세요


핵심 요약

때때로 몇 저녁만 코딩하면 수개월간의 좌절을 피할 수 있습니다. 게다가 새로운 것을 배우게 되죠. 일석이조.

팀에 같은 플래닝‑포커 문제점이 있다면—직접 만들어 보세요! 이제 우리는 훌륭한 프레임워크, 무료 호스팅, 그리고 AI 어시스턴트를 제공합니다. 변명할 여지가 없습니다.

질문? 아이디어? 기여하고 싶으신가요? 댓글을 남겨 주세요!

P.S. 네, 엔터프라이즈 솔루션도 존재합니다. 제 솔루션은 비용이 $0이고, 우리에게 완벽히 작동하며, 만드는 과정도 즐거웠습니다. 😄

0 조회
Back to Blog

관련 글

더 보기 »

Luminary: Week 2 — 핵심 구축

Luminary: Week 2 — Building the Core 표지 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2F...

Jemalloc, Meta가 포기하지 않음

- Meta는 소프트웨어 인프라에서 고성능 메모리 할당기인 jemalloc의 장기적인 이점을 인식하고 있습니다. - 우리는 jemalloc에 대한 관심을 새롭게 하고 있습니다…