0

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:

  1. Enquanto a votação está aberta, o evento emitido para o canal informa apenas: { userId: "...", hasVoted: true }.
  2. O valor real do voto fica persistido em memória/banco no backend.
  3. 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!

Carregando publicação patrocinada...