Repository navigation
Description
Problema
As telas de pós-corrida (Results.tsx — pódio, ranking final, contagem de stats) só existem depois de uma corrida multiplayer completa: lobby → countdown → racing → finished. Não há como alcançá-las de forma isolada (dev/headless), então PRs de UI dessas telas shipam sem verificação em browser.
Isso não é hipótese: o Retro W28 (D#15) registrou 3 PRs de UI (#11 pódio solo, #23, #26) mergeadas com o Resolvedor declarando honestamente "a tela fica atrás de corrida concluída, inviável headless" — e propôs esta issue: "harness p/ verificar telas pós-corrida (Resultado/pódio) — vale virar issue area:infra/area:ui quando houver folga."
Impacto direto na fila atual: #22 (share na tela de Resultado) e #25 (/practice) vão mexer exatamente nessas telas e herdar o mesmo ponto cego. Um harness pago uma vez destrava a verificação visual de todas elas.
Objetivo
Um harness só-de-dev que monta as telas de pós-corrida com dados-fixture, alcançável em um clique/URL, sem jogar uma corrida — para que um agente (ou humano) tire screenshot e valide layout/estados antes do merge.
Acceptance criteria (verificáveis)
- Existe uma rota/entrada apenas em dev (ex.:
/_harnessou?harness=results) que renderizaResults.tsxcom props-fixture, sem passar por lobby/countdown/corrida. - Cobre os estados do pódio que já quebraram/importam: 1 jogador, 2, <3 (o caso da UI: pódio solo mostra quadrados vazios/quebrados (adaptar Results a < 3 jogadores · §III.7) #11) e ≥3, mais o estado de erro/vazio.
- Zero pegada em produção: a entrada não aparece no bundle/rota de prod (gate por
process.env.NODE_ENV !== 'production'ou equivalente) — comprovar quepnpm buildnão expõe a rota. - Área sagrada intacta: nada no caminho da corrida (
Race/textarea/latência de input) muda; o harness é isolado. - Um parágrafo no
README(seção de dev/testes) explica como abrir o harness e para que serve. -
pnpm typecheck+pnpm buildverdes; fixtures são dados sintéticos (nenhuma chamada a Supabase/Realtime).
Dica de abordagem
- Menor caminho: extrair as props que
Results.tsxjá recebe e criar um arquivo de fixtures (results.fixtures.ts) com os N cenários; montar via uma rota App-Routerapp/(dev)/_harness/page.tsxguardada porNODE_ENV, ou um client component atrás de um query-param que só o dev conhece. - Não precisa de Playwright ainda (isso é EngFase C na Parte VII) — este é o enabler barato que já torna as telas alcançáveis; o E2E entra depois reusando os mesmos fixtures.
- Preferir reuso: se
Race/fluxo já expõe um tipo de "resultado final", os fixtures devem satisfazer esse tipo (nada de shape paralelo que possa divergir).
Refs: spec docs/UI-AAA-OVERHAUL.md §III.7 (Results/pódio) e Parte VII (EngFase C = E2E/Playwright, do qual isto é a primeira pedra). Origem: Retro W28 · D#15.
Activity
caioross commentedon Jul 22, 2026
🏛️ Parecer do Conselho · 2026-07-22
Veredito: APROVADA PARA EXECUÇÃO
Results.tsx:17-29recebe só props (room: RoomState,meId,isLeader,onPlayAgain,onChat) e não faz nenhum fetch,useRoomou canal Realtime — o filhoChattambém é puro (Chat.tsx:9-19). Montar a tela = construir umRoomState(src/lib/types.ts:42-52) + doisnoop. Alvo sugerido:src/app/_harness/results/page.tsx(Server Component) +src/components/dev/results.fixtures.ts.Armadilha nº1 (o AC "zero pegada em prod"): um
importestático deResults/fixtures no topo do módulo é bundlado mesmo se a página chamarnotFound(). O webpack do Next substituiprocess.env.NODE_ENVpor literal, então só há eliminação real se oif (process.env.NODE_ENV === "production") notFound()vier antes e o componente de fixture entrar porawait import("./HarnessClient")dentro do ramo de dev. Prova do AC:pnpm build+grep -rl "<string única do fixture>" .next/→ 0 ocorrências (cole a saída no PR).Armadilha nº2 (não usar query-param):
?harness=resultsdentro deRoomViewinjeta caminho de dev no bundle de produção e é acionável por qualquer visitante. Gate =NODE_ENV(variável de build), rota separada.Fixtures: satisfaça
RoomState/Playerdesrc/lib/types.ts— nada de shape paralelo (AC "preferir reuso"). Atenção: o tempo da tabela ép.finishedAt - room.startedAt(Results.tsx:118), entãostartedAtprecisa ser coerente ou a coluna sai negativa; eplace/finishedAtmandam na ordenação (Results.tsx:32-38).NODE_ENVe os fixtures forem sintéticos (sem Supabase/Realtime, sem service_role — nada aqui tocasrc/lib/supabase.ts). Adicioneexport const metadata = { robots: { index: false, follow: false } }na rota, higiene igual à desrc/app/room/[id]/page.tsx:12. Não reaproveite dados reais de partida como fixture (nick de jogador real vira dado pessoal em repo público).ranked.length === 0(o "estado vazio" do AC),solo/duosãofalse,screenOrder = [1,0,2]epodium[rankIdx].idestouraTypeError(Results.tsx:44-46,87-96). Inclua o cenário0 jogadorese trate no mesmo PR (guardif (!ranked.length) return <estado vazio>, ~3 linhas, §7.3 Normal). Cenários mínimos: 0 · 1 (SoloHero, l.74-80) · 2 (duo, grid 2 col) · 3 · 5 (com não-finalizadores, para exercitarResults.tsx:297-299). Embrulhe o harness em<ToastProvider>(padrão desrc/app/room/[id]/page.tsx:19):useToasttem fallback silencioso (Toast.tsx:48-52), então sem provider o toast da Crescimento: tela de Resultado não tem "compartilhar" — transformar cada corrida terminada em convite (loop viral) #22 não quebra, apenas não aparece — e o harness daria falso-verde. Navegação entre cenários por<Link>nativo = teclado-primeiro de graça.Race/textarea). Confirme no output dopnpm buildque o First Load JS de/room/[id]não muda (se mudar, o import dinâmico não pegou — ver Armadilha nº1).Results) e das telas pós-corrida da Practice — rota /practice com Treino Livre (sem sala), reusando Race #25/PR feat(practice): rota /practice com Treino Livre solo, sem sala (#25) #40, e paga a dívida registrada no Retro W28 (D#15 — 3 PRs de UI mergeadas sem verificação). P2 está correto; dentro do P2 é a de maior alavancagem porque converte "inviável headless" em screenshot para todas as PRs seguintes. Não puxe Playwright (EngFase C) para dentro.Plano sugerido:
src/components/dev/results.fixtures.ts—SCENARIOS: Record<"empty"|"solo"|"duo"|"trio"|"crowd", RoomState>tipado porsrc/lib/types.ts:42-52,startedAtfixo efinishedAtderivados dele.src/app/_harness/results/page.tsx— server component:if (process.env.NODE_ENV === "production") notFound();antes de qualquer import do harness; depoisconst { HarnessClient } = await import("./HarnessClient").metadata.robots = { index: false }../HarnessClient.tsx—"use client",<ToastProvider>+<Results room={SCENARIOS[n]} meId=... isLeader={true} onPlayAgain={noop} onChat={noop} />e uma barra de<Link>por cenário (?n=empty|solo|...).ranked.length === 0emResults.tsx:87-96(guard + estado vazio) — descoberto pelo cenárioempty.README(seção dev): como abrir, para que serve, e que não existe em prod.pnpm typecheck·pnpm build·pnpm test·grepno.next/provando ausência do fixture · screenshot dos 5 cenários no PR.caioross commentedon Jul 24, 2026
Resolvida em PR #63 — #63
/harness/resultscom 5 cenários (?n=empty|solo|duo|trio|crowd) e toggle&leader=0. Fixtures sintéticos tipados porsrc/lib/types.ts.src/app/_harness/não funciona — pasta com prefixo_é private folder no App Router e fica fora do roteamento, então a rota daria 404 até em dev. Usei route groupsrc/app/(dev)/harness/results/(URL final igual).emptyachou o bug que o Conselho previu e ele está corrigido no mesmo PR: com 0 jogadores,podium[1].idestouravaTypeError. Reproduzi rodando o harness contra oResults.tsxdeorigin/main.typecheck·build·test(34) ·validate-metrics(37) ·validate-persistence(49). AC "zero pegada em prod" comprovado por grep em.next/static+.next/server(0 ocorrências) e por/room/[id]mantendo 221 kB de First Load JS contra baseline deorigin/main.app/loading.tsxna raiz manda o shell antes donotFound()resolver. Testei 4 variantes de gate, todas 200.robots.txtpassou a bloquear/harness/.feat(dev): harness só-de-dev das telas pós-corrida com fixtures (#37) (…
feat(mobile): corrida jogável no touch — alvo e caret visíveis juntos (…
feat(mobile): corrida jogável no touch — código-alvo e caret visíveis…