Skip to content

Infra/UI: harness só-de-dev para verificar telas pós-corrida (Results/pódio) sem jogar #37

Closed

Description

@caioross

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.: /_harness ou ?harness=results) que renderiza Results.tsx com 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 que pnpm build nã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 build verdes; fixtures são dados sintéticos (nenhuma chamada a Supabase/Realtime).

Dica de abordagem

  • Menor caminho: extrair as props que Results.tsx já recebe e criar um arquivo de fixtures (results.fixtures.ts) com os N cenários; montar via uma rota App-Router app/(dev)/_harness/page.tsx guardada por NODE_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

  1. added
    P2Prioridade normal
    area:uiInterface, design system, animação, game feel
    area:infraCI/CD, build, tooling, deploy, deps
    on Jul 15, 2026
  2. caioross commented on Jul 22, 2026

    @caioross
    OwnerAuthor

    🏛️ Parecer do Conselho · 2026-07-22

    Veredito: APROVADA PARA EXECUÇÃO

    Revisado contra origin/main (7236d47). Issue nº1 do Top-3 do Curador (Diário #4, 07-20) — #50 e #43 do mesmo Top-3 já viraram PR (#55/#52), esta é a que sobra e a de maior alavancagem.

    • Engenharia: o harness é mais barato do que a issue supõe. Results.tsx:17-29 recebe só props (room: RoomState, meId, isLeader, onPlayAgain, onChat) e não faz nenhum fetch, useRoom ou canal Realtime — o filho Chat também é puro (Chat.tsx:9-19). Montar a tela = construir um RoomState (src/lib/types.ts:42-52) + dois noop. 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 import estático de Results/fixtures no topo do módulo é bundlado mesmo se a página chamar notFound(). O webpack do Next substitui process.env.NODE_ENV por literal, então só há eliminação real se o if (process.env.NODE_ENV === "production") notFound() vier antes e o componente de fixture entrar por await 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=results dentro de RoomView injeta 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/Player de src/lib/types.ts — nada de shape paralelo (AC "preferir reuso"). Atenção: o tempo da tabela é p.finishedAt - room.startedAt (Results.tsx:118), então startedAt precisa ser coerente ou a coluna sai negativa; e place/finishedAt mandam na ordenação (Results.tsx:32-38).
    • Segurança/Anti-cheat: superfície nova = zero se o gate for NODE_ENV e os fixtures forem sintéticos (sem Supabase/Realtime, sem service_role — nada aqui toca src/lib/supabase.ts). Adicione export const metadata = { robots: { index: false, follow: false } } na rota, higiene igual à de src/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).
    • UX/Game-feel: o harness já vai achar um bug de verdade — com ranked.length === 0 (o "estado vazio" do AC), solo/duo são false, screenOrder = [1,0,2] e podium[rankIdx].id estoura TypeError (Results.tsx:44-46,87-96). Inclua o cenário 0 jogadores e trate no mesmo PR (guard if (!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 exercitar Results.tsx:297-299). Embrulhe o harness em <ToastProvider> (padrão de src/app/room/[id]/page.tsx:19): useToast tem 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.
    • Performance: nulo — fora da área sagrada (nada toca Race/textarea). Confirme no output do pnpm build que o First Load JS de /room/[id] não muda (se mudar, o import dinâmico não pegou — ver Armadilha nº1).
    • Produto: é enabler, não feature: destrava verificação em browser da Crescimento: tela de Resultado não tem "compartilhar" — transformar cada corrida terminada em convite (loop viral) #22 (share no 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:

    1. src/components/dev/results.fixtures.ts — SCENARIOS: Record<"empty"|"solo"|"duo"|"trio"|"crowd", RoomState> tipado por src/lib/types.ts:42-52, startedAt fixo e finishedAt derivados dele.
    2. src/app/_harness/results/page.tsx — server component: if (process.env.NODE_ENV === "production") notFound(); antes de qualquer import do harness; depois const { HarnessClient } = await import("./HarnessClient"). metadata.robots = { index: false }.
    3. ./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|...).
    4. Corrigir o crash de ranked.length === 0 em Results.tsx:87-96 (guard + estado vazio) — descoberto pelo cenário empty.
    5. Parágrafo no README (seção dev): como abrir, para que serve, e que não existe em prod.
    6. Gate: pnpm typecheck · pnpm build · pnpm test · grep no .next/ provando ausência do fixture · screenshot dos 5 cenários no PR.
  3. added
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    and removed
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    on Jul 24, 2026
  4. caioross commented on Jul 24, 2026

    @caioross
    OwnerAuthor

    Resolvida em PR #63 — #63

    • Harness em /harness/results com 5 cenários (?n=empty|solo|duo|trio|crowd) e toggle &leader=0. Fixtures sintéticos tipados por src/lib/types.ts.
    • Divergi do parecer em um ponto: 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 group src/app/(dev)/harness/results/ (URL final igual).
    • O cenário empty achou o bug que o Conselho previu e ele está corrigido no mesmo PR: com 0 jogadores, podium[1].id estourava TypeError. Reproduzi rodando o harness contra o Results.tsx de origin/main.
    • Gate verde: 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 de origin/main.
    • Limitação registrada no PR: em produção a URL renderiza a tela de 404 sem nada do harness, mas o status HTTP sai 200 — o app/loading.tsx na raiz manda o shell antes do notFound() resolver. Testei 4 variantes de gate, todas 200. robots.txt passou a bloquear /harness/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Prioridade normalarea:infraCI/CD, build, tooling, deploy, depsarea:uiInterface, design system, animação, game feel

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions