Pitch: Como estruturei um Planning Poker em tempo real com WebSockets, Next.js e Zero Atrito
Fala pessoal!
Nas últimas semanas, meu time e eu estávamos sofrendo com um problema clássico de cerimônias ágeis: reuniões de refinamento e planning que se arrastam porque as ferramentas de Planning Poker disponíveis no mercado colocam atrito em tudo.
A maioria exige que todos os membros do time criem conta, confirmem e-mail, lidem com limites artificiais de participantes no plano grátis ou sofram com sincronização em tempo real engasgando.
Decidi construir uma solução focada em zero fricção e baixa latência: o SwiftPoker.
Quero compartilhar aqui as principais decisões de arquitetura e os desafios que enfrentei para deixar a ferramenta rodando com latência imperceptível.
1. O Desafio do Tempo Real: WebSockets vs Long Polling
Para uma votação fluida, o time precisa ver instantaneamente quem já votou (sem revelar o voto antes da hora) e, quando o facilitador clicar em "Revelar", todos os cards devem virar em menos de 100ms.
- Por que não Long Polling/HTTP? Polling em intervalos curtos (ex: 500ms) cria uma carga desnecessária no backend quando há dezenas de salas ativas simultaneamente e gera uma sensação de "delay" para quem está votando pelo celular.
- A Solução: Adotamos WebSockets via Pusher Channels. Cada sala de poker é um canal isolado (
room-{roomId}). Quando um participante vota ou o moderador aciona um evento, disparamos um payload enxuto contendo apenas o estado modificado.
2. A Lógica de Voto Secreto (Sem Cheating no Client)
Um detalhe crucial em planning poker é o voto cego: ninguém pode saber o valor do voto do colega antes do reveal para não criar viés (efeito manada).
Se enviássemos o valor do voto pelo WebSocket antes do reveal, qualquer desenvolvedor que abrisse o DevTools > Network > WS poderia inspecionar a mensagem e ver os votos dos outros.
Para resolver isso:
- Enquanto a votação está aberta, o evento emitido para o canal informa apenas:
{ userId: "...", hasVoted: true }. - O valor real do voto fica persistido em memória/banco no backend.
- Apenas quando o facilitador emite o evento
reveal-votes, o backend recupera os valores de todos e faz o broadcast consolidado para a sala com as métricas (média, moda e consenso).
3. Evitando Layout Shift na Interface (Next.js + Tailwind)
A interface precisa alternar entre "Criar Sala" e "Entrar na Sala", além de exibir estados de espectador vs votante.
Inicialmente, usar renderização condicional do React ({tab === 'join' && <JoinForm />}) causava pequenas trepidações visuais (Cumulative Layout Shift) enquanto os componentes montavam e recalculavam altura.
Ajustamos para componentes persistentes com alternância puramente via classes CSS (block / hidden) e alturas de botões e cabeçalhos milimetricamente casadas. O resultado foi uma transição suave, sem nenhum pulo na tela, mesmo em conexões lentas ou dispositivos móveis mais modestos.
4. Zero Fricção: Por que não exigir Login para Votantes?
Uma das maiores decisões de produto: apenas quem cria a sala pode (opcionalmente) gerenciar configurações avançadas, mas quem vai votar precisa de exatamente 1 clique.
- O facilitador compartilha o link ou projeta o QR Code na chamada.
- O dev abre, digita o nome e já tem o baralho na mão.
- Se alguém quiser participar só como espectador (PO, Scrum Master ou observador), tem um toggle direto para não afetar as estatísticas da rodada.
Onde estamos rodando hoje:
- Frontend / Backend: Next.js (App Router) hospedado na Vercel
- Real-time Engine: Pusher Channels (WebSockets)
- Link do projeto: https://www.swiftpoker.pro
Gostaria muito de ouvir a opinião de vocês sobre a arquitetura e a usabilidade. Se alguém quiser testar com o time no próximo refinamento e tiver sugestões de features essenciais que faltam, mandem a ver nos comentários!