feat(dev): harness só-de-dev das telas pós-corrida (Results/pódio) com fixtures (#37) - #63
Conversation
A tela de Resultado/pódio só existia depois de uma corrida multiplayer completa (lobby → countdown → racing → finished), então PRs de UI dessas telas shipavam sem verificação em browser (3 casos registrados no Retro W28, D#15). - `src/app/(dev)/harness/results` monta `<Results>` com dados sintéticos em `/harness/results`, com 5 cenários (0/1/2/3/5 jogadores) e toggle de líder. - `src/components/dev/results.fixtures.ts` tipado por `src/lib/types.ts` — se `RoomState`/`Player` mudarem, o typecheck quebra aqui em vez de deixar o harness divergir da tela real. Sem Supabase/Realtime, sem nick de jogador real. - Gate por `NODE_ENV` (variável de build) + `import()` dentro do ramo: no build de produção o webpack elimina harness, client e fixtures — zero pegada no output. - `/harness/` entra no disallow do robots.txt, junto com `/room/`. O cenário de 0 jogadores achou um bug real na tela: `podium[1]` num array vazio estourava `TypeError: Cannot read properties of undefined (reading 'id')` (Results.tsx:87-96). Corrigido com um estado vazio.
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
@caioross is attempting to deploy a commit to the caioross' projects team on Vercel, but is not a member of this team. To resolve this issue, you can:
To read more about collaboration on Vercel, click here. |
✅ Parecer PR Doctor — APROVADA (Normal §7.3), aguardando próxima rodadaDiff lido inteiro. Classificação Normal: harness só-de-dev sob route group Não mergeada nesta rodada apenas pelo teto de 2 merges/rodada (HANDBOOK §8) — já foram #57 e #60. Está aprovada e primeira da fila para a próxima rodada; nenhum reparo pendente. A limitação honesta de status 200 (não 404) em prod, com zero conteúdo do harness no bundle, está documentada no corpo e é aceitável (o AC é bundle, e |
✅ PR Doctor — merge (Normal §7.3)Reconfirmado |
Achado do quorum: congelar a lista da rodada reintroduzia sala presa. Se todos os competidores saem durante racing e resta so um retardatario-espectador, racers fica [] e shouldFinishRace (guarda de lista vazia) nunca encerra. Agora o lider posta finish com results vazios (tela de Results vazia da #63) apos o countdown, liberando a sala.
…lista da rodada (#64) (#80) * feat(multiplayer): entrada após o início vira espectador — congela a lista da rodada (#64) A lista de participantes ficava aberta durante toda a partida: quem entrasse com a corrida já em `racing` passava a compor a mesma rodada, e todos que já tinham terminado ficavam presos esperando o retardatário terminar ou desistir. Uma partida deve ser composta por quem estava presente quando ela COMEÇOU. - `isSpectatorJoin(joinedAt, startMs, status)` em `src/lib/room.ts` — puro e determinístico (joinedAt e start_at são compartilhados por presence + linha da sala, então todos os clientes classificam igual). Espectador = entrou depois do start (`joinedAt > startMs`) com a rodada em andamento. - `useRoom`: cada `LivePlayer` ganha `spectator`; o gate de fim de corrida passa a ignorar espectadores (`racers = players.filter(!spectator)`), então um retardatário sem `finishedAt` não prende mais a sala. Expõe `isSpectator` para a UI. - `RoomView`: espectadores saem da pista/ranking (filtrados do roster de competidores) e veem a nova `SpectatorView` — banner "Partida em andamento", pista ao vivo e chat. Entram automaticamente na próxima rodada (start_at muda → deixam de ser espectadores). - 6 casos novos em `room.test.ts` cobrindo a classificação (limite exato, lobby, finished, sem corrida). * fix(#64): rede de seguranca — lider encerra se so restam espectadores Achado do quorum: congelar a lista da rodada reintroduzia sala presa. Se todos os competidores saem durante racing e resta so um retardatario-espectador, racers fica [] e shouldFinishRace (guarda de lista vazia) nunca encerra. Agora o lider posta finish com results vazios (tela de Results vazia da #63) apos o countdown, liberando a sala.
Contexto
A tela de Resultado/pódio só é alcançável depois de uma corrida multiplayer completa
(lobby → countdown → racing →
finished). O Retro W28 (D#15)registrou 3 PRs de UI (#11, #23, #26) mergeadas com o Resolvedor declarando honestamente
"a tela fica atrás de corrida concluída, inviável headless". Este PR paga essa dívida: é
enabler, não feature — destrava verificação em browser para a #22 (share no
Results)e para as telas pós-corrida seguintes.
Segui o plano do 🏛️ Parecer do Conselho, com uma divergência (item 1 abaixo).
O que mudou
src/app/(dev)/harness/results/page.tsxNODE_ENVsrc/app/(dev)/harness/results/HarnessClient.tsx<Results>dentro de<ToastProvider>+ navegação por cenáriosrc/components/dev/results.fixtures.tssrc/lib/types.tssrc/components/Results.tsxsrc/app/robots.ts/harness/no disallow, junto com/room/README.md1. Divergência do parecer: a rota não pode ser
src/app/_harness/. No App Router,pasta com prefixo
_é private folder — fica fora do roteamento, então/_harness/resultsdaria 404 também em dev e o harness nasceria morto. Usei route group:
src/app/(dev)/harness/results/→ URL
/harness/results. O(dev)não entra na URL, só agrupa.2. Fixtures tipados pela fonte real.
SCENARIOSsatisfazRoomStatedesrc/lib/types.ts— nada de shape paralelo.
startedAté um instante fixo (nãoDate.now()), então os temposda tabela são determinísticos entre recargas e screenshots de PR são comparáveis. Dados 100%
sintéticos: nenhum nick real de partida, nenhuma chamada a Supabase/Realtime.
3. O cenário
emptyachou um bug real — exatamente o que o Conselho previu. Comranked.length === 0,solo/duosãofalse,screenOrder = [1,0,2]epodium[1].idestoura. Reproduzido rodando o harness contra o
Results.tsxdeorigin/main:4. Cenários:
empty(0) ·solo(1,SoloHero) ·duo(2, grid 2 col) ·trio(3,padrão) ·
crowd(5, com 2 não-finalizadores para exercitarResults.tsx:297-299).&leader=0alterna para a visão de quem não é líder (// aguardando líder reiniciar...).Cenário desconhecido cai no default em vez de quebrar.
Como foi validado (resultado real)
AC "zero pegada em produção" — grep no output servido (
.next/static+.next/server,build limpo):
/harness/resultsno build de produção: 146 B / 87.3 kB First Load JS — idêntico a/_not-found, ou seja, nenhum JS de cliente do harness foi emitido. Comnext start, a URLrenderiza a tela de 404 do app, com zero conteúdo do harness no HTML.
First Load JS de
/room/[id]não regrediu (build de baseline em worktree separada deorigin/main):origin/main/room/[id]Os +0.1 kB são o estado vazio adicionado ao
Results.tsx; o First Load JS não mudou — sinalde que o
import()dinâmico pegou.Verificação em browser (
next devna worktree, os 5 cenários + toggle de líder abertos elidos): todos renderizam. Screenshot não foi possível nesta rodada — a Browser pane não
compositava frames em execução headless (
Screenshot timed out: the Browser pane is not displayed); a evidência acima é o texto renderizado extraído de cada tela.Riscos e limitação conhecida
harness, mas o status sai 200 porque o app tem
src/app/loading.tsxna raiz: o shell éenviado antes de o
notFound()resolver. Medi três variantes —notFound()no corpo async,guard síncrono,
force-dynamice guard emgenerateMetadata— todas respondem 200.É propriedade do app, não do gate; o AC (bundle) está cumprido, e o
robots.txtcobre olado de indexação. Se o Doctor quiser 404 duro, o caminho é middleware — fora do escopo mínimo.
page.js.nft.json(manifesto de trace de deploy) lista o caminho deHarnessClient.tsx.É uma string de path num manifesto conservador do Next, não código: o
page.jscompiladotem zero referências a ele, e nenhum fixture aparece no trace.
Race/textarea/latência) foi tocado.A única mudança fora do harness é o guard de estado vazio no
Results, que só roda na telapós-corrida.
src/app/api/rooms/**,src/lib/supabase.ts,anti-cheat, migrations, CI, nem adiciona dependência.
Closes #37