DEV Community

ArcaneGaming
ArcaneGaming

Posted on

Я запилил свой Scrum Poker, потому что все остальные - отстой 🎴

Проблема 😩

Спринт-планирование. Команде нужно оценить таски. Кто-то кидает ссылку на Planning Poker. Ты кликаешь. Ждёшь. Половина команды не может подключиться. Вторая половина уже ушла за кофе.

Знакомо?

У нас в AGG TEAM мы перепробовали всё:

  • Planning Poker Online (лагает, реклама, древний интерфейс)
  • Scrum Poker for Jira (дорого, требует Jira)
  • PlanITpoker (работает, но нет НАШИХ фич)

После третьего краша посреди оценки я подумал: "Да пошло оно, сделаю сам."


Настоящая задача: 6 команд, 1 комната 🤯

Вот в чём загвоздка: у нас не ОДНА скрам-команда. У нас ШЕСТЬ отделов:

  • Frontend, Backend, DevOps, QA, Аналитика, Менеджмент

Все хотят делать планинг покер. Одновременно. В одной комнате.

Это хаос. Как играть в шесть разных карточных игр за одним столом.

Существующие инструменты: "Вот комната, сами разбирайтесь!" Не идеально.


Что я сделал

🎰 Поддержка нескольких столов (до 6!)

Основная идея: одна сессия, несколько независимых столов.

interface Table {
  id: string;
  name: string;        // "Frontend отдел", "Backend ниндзя"
  revealed: boolean;   // Карты раскрыты для этого стола?
}

interface Player {
  id: string;
  name: string;
  tableId: string;     // За каким столом сидит
  vote: string | null; // "5", "13", "?", "☕"
}
Enter fullscreen mode Exit fullscreen mode

Хост создаёт столы с кастомными названиями. Все выбирают свой стол. Каждый стол голосует независимо. Видишь все столы + общую среднюю оценку.

Возможности хоста: Добавлять, удалять, переименовывать столы на лету. Перемещать людей между столами при необходимости.

⚡ Real-time синхронизация

Использовал Supabase как бэкенд. Простой подход с polling:

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

Да, WebSocket'ы были бы круче. Но для прототипа и ~50 пользователей? Polling отлично работает. Если масштаб вырастет, переделаю.

Обновления в реальном времени:

  • Кто-то присоединился → мгновенно
  • Кто-то проголосовал → сразу
  • Карты раскрыты → все видят результаты
  • Эмодзи брошено → летит через экран

💓 Умное определение отключения

Проблема других инструментов: люди закрывают вкладки, но их аватары остаются навсегда. Пользователи-призраки.

Моё решение использует heartbeat + явное отключение:

// Отправляем heartbeat каждые 10 секунд
const sendHeartbeat = async () => {
  await fetch(`${API_URL}/rooms/${roomId}/heartbeat`, {
    method: 'POST',
    body: JSON.stringify({ playerId }),
  });
};

// Также явное отключение при закрытии страницы
window.addEventListener('beforeunload', () => {
  navigator.sendBeacon(
    `${API_URL}/rooms/${roomId}/disconnect`,
    JSON.stringify({ playerId })
  );
});
Enter fullscreen mode Exit fullscreen mode

Результат:

  • Страница открыта, но бездействуешь? Сиди сколько хочешь (без киков)
  • Закрыл страницу/вкладку? Мгновенное отключение (2 секунды)

Никаких призраков. Чистые комнаты.

🎉 Атаки эмодзи!

Моя любимая фича. Кликаешь на любого участника, кидаешь эмодзи. Оно буквально летит от твоего аватара к его с плавной анимацией (💩)(🤮).

12 эмодзи: 👍 👏 🎉 ❤️ 🚀 🔥 😂 🤔 💯 ✨ 👌 🙌

Реальные сценарии:

  • 👍 согласен с оценкой
  • 🔥 кто-то делает отличное замечание
  • 😂 джун оценивает простую таску в 89 поинтов
  • ☕ определённо пора за кофе

Это сделало наши планирования весёлыми. Люди реально теперь ждут оценочные встречи!

🃏 Фибоначчи + специальные карты

Классика: 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

Плюс:

  • "?" → "Понятия не имею"
  • "☕" → "Нужен кофе перед размышлениями"

Можно менять голос даже после раскрытия. Почему? Потому что во время обсуждения можешь понять, что 13 на самом деле 8. Большинство инструментов блокируют голоса. Мой - нет.

🧮 Автоматический расчёт среднего

Каждый стол показывает свою среднюю. Плюс общий итог внизу по ВСЕМ столам.

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

Идеально для: "Какая общая оценка по всем командам?"

🌙 Тёмная тема + 🌍 Мультиязычность

Переключатель в углу меняет тему (светлая/тёмная) и язык (RU/EN). Сохраняется в localStorage, автоматически применяется в следующий раз.

Потому что если в твоём приложении в 2026-м нет тёмной темы... что ты вообще делаешь?


Технический стек 🛠

Frontend:

  • React + TypeScript
  • Tailwind CSS v4
  • Lucide иконки

Backend:

  • Supabase Edge Functions (Hono + Deno)
  • Supabase KV Store (key-value таблица)

Почему Supabase?

  • Быстрая настройка (без деплоя сервера)
  • Edge Functions (TypeScript бэкенд)
  • Простой KV store для состояния комнат
  • Бесплатный tier для прототипов

Вызовы и решения 💡

1. Пользователи-призраки

Проблема: Пользователи закрывают вкладки, аватары остаются навсегда

Решение: Механизм heartbeat + явное отключение через navigator.sendBeacon

2. Управление состоянием

Проблема: Синхронизация 6 столов + игроков

Решение: Единый источник правды в бэкенде, polling для обновлений


Как изменились планирования

До:

  • "Стойте, все загрузились?"
  • "Обнови, я не вижу твой голос"
  • "Кто ещё тут?"
  • неловкое ожидание

После:

  • Создать комнату (5 секунд)
  • Все заходят (мгновенно)
  • Голосуем, раскрываем, обсуждаем
  • Кидаемся эмодзи для веселья
  • Реально заканчиваем вовремя

Реальные отзывы:

"Стойте, планинг покер может быть плавным?"

"Обожаю кидать огненные эмодзи в людей!"

"Наконец инструмент, который не бесит"


Что дальше? 🚀

  • История сессий (кто как голосовал)
  • Таймер голосования (опциональный обратный отсчёт)
  • Кастомные колоды (размеры футболок?)
  • Звуковые эффекты (опционально)
  • Интеграция с Jira

Ключевые выводы 🧠

1. Делай то, что нужно тебе
Хватит ждать "идеальный" инструмент. 80% за выходные лучше чем 100% "когда-нибудь".

2. Polling - не зло
WebSocket'ы круты, но polling отлично работает для малых команд. Не усложняй.

3. Маленькие радости важны
Фича с эмодзи заняла 2 часа. Это любимая фича всех. Мелочи делают разницу.

4. Тёмная тема - обязательна
Серьёзно. На дворе 2026-й.


Попробуй! 🎮

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

Используй, это бесплатно


Итог

Иногда несколько вечеров кодирования экономят месяцы фрустрации. Плюс учишь что-то новое. Win-win.

Если у твоей команды та же боль с планинг покером - делай свой! Сейчас есть крутые фреймворки, бесплатный хостинг и AI-ассистенты. Отговорок нет.


Вопросы? Идеи? Хочешь помочь? Пиши в комментах!


P.S. Да, существуют enterprise-решения. Моё стоило $0, отлично работает для нас и было весело делать. 😄

Top comments (4)

Collapse
 
__2c0c37d1 profile image
Виталий Полежаев • Edited

Халявный скрам-покер))
Что-то новенькое😅

Collapse
 
arcanegaming profile image
ArcaneGaming

Да-да, он бесплатный, жду предложений по улучшению!)

Collapse
 
yulia_fedorova profile image
Yulia Fedorova

Огонь, сколько по времени заняло? Какой вайбкод инструмент используешь?

Collapse
 
arcanegaming profile image
ArcaneGaming

2 дня, Figma-make + Trae😇