Skip to content

Practice — rota /practice com Treino Livre (sem sala), reusando Race #25

Closed

Description

@caioross

Primeira fatia do epic #24 (Practice/Solo · §V.5). Menor valor entregável: dar ao visitante solitário algo para jogar em um clique, sem sala.

Problema

Para digitar qualquer coisa hoje é preciso criar/entrar numa sala multiplayer (src/components/HomeView.tsx → API rooms → router.push('/room/<code>'), l.90). O visitante que chega sozinho não tem caminho jogável imediato — fricção nº1 da persona O Iniciante (discussions #9/#14). A engine já roda solo (o ranking global aceita corrida solo), então falta só desacoplar o Race do useRoom e expô-lo numa rota própria.

Escopo (só Treino livre)

  • Nova rota /practice (src/app/practice/page.tsx) que renderiza a corrida sem sala e sem Realtime (sem useRoom, sem broadcast, sem presença).
  • Seletor de linguagem + dificuldade antes de começar (reusar os controles/estilos do modal de criar sala em HomeView; dificuldade default Fácil, coerente com Onboarding: dificuldade nasce em "Médio" e atropela o Iniciante — primeira sala deveria nascer no Fácil (§0.3) #16 e §0.3).
  • Ao terminar: mostrar as stats pessoais (WPM/precisão/erros/tempo) e um botão "treinar de novo" (novo snippet da mesma config) + link "criar sala para jogar com amigos".
  • CTA na home: um botão/atalho "treinar →" que leva a /practice sem criar sala (HomeView, perto do bloco "Criar sala", ~l.294).

Acceptance criteria (verificáveis)

  1. /practice carrega e inicia uma corrida solo sem nenhuma chamada à API rooms nem canal Supabase Realtime (verificar na aba Network: zero request a /api/rooms).
  2. Trocar linguagem/dificuldade no seletor troca o snippet exibido; a corrida começa Fácil por default.
  3. A digitação preserva a área sagrada: input a 60fps, sem trabalho pesado competindo com a textarea; feedback de erro char-a-char idêntico ao Race atual (CodeDisplay).
  4. prefers-reduced-motion respeitado (sem animações de entrada quando ativo).
  5. Ao concluir, as stats aparecem e "treinar de novo" reinicia com novo snippet sem recarregar a página.
  6. Home tem CTA "treinar →" visível que navega para /practice sem criar sala.
  7. pnpm typecheck + pnpm build verdes; node scripts/validate-metrics.mjs verde (engine reutilizada, não regredida).

Dica de abordagem

Extrair de src/components/Race.tsx a parte que só depende de snippet + onFinish (métricas, CodeDisplay, CodeEditor, anti-paste) para um wrapper standalone PracticeRace, deixando o Race multiplayer chamando o mesmo núcleo. Evitar duplicar a lógica de métricas — a honestidade de WPM/precisão precisa continuar validada por validate-metrics. Ghost (PB) e bots ficam para as fatias 3 e 2 do #24 — não implementar aqui.

Fora de escopo

Ghost/PB, bots, persistência de melhor resultado, metadata/SEO da landing (fatia 4 do #24).

Activity

  1. added
    P2Prioridade normal
    area:engineMotor de digitação, WPM, precisão, anti-cheat
    area:uiInterface, design system, animação, game feel
    on Jul 11, 2026
  2. caioross commented on Jul 12, 2026

    @caioross
    OwnerAuthor

    Nota do Curador (do [Feedback] O Iniciante · D#27): enquanto a rota /practice não sai, uma microcopy barata no botão da home reduz a fricção de descoberta quase de graça — algo como "Criar sala — dá pra jogar sozinho também". Candidata a fatia mínima/quick-win desta issue (ou PR próprio pequeno em area:ui) antes do modo completo. A dor real do Iniciante é descobrir que o solo existe.

  3. caioross commented on Jul 15, 2026

    @caioross
    OwnerAuthor

    🏛️ Parecer do Conselho · 2026-07-14

    Veredito: APROVADA PARA EXECUÇÃO

    Fatia concreta do epic #24; #2 no Top-3 do Curador (porta de entrada, [Plano] #29 "Confiança & Porta de Entrada").

    • Engenharia: src/components/Race.tsx é acoplado a room: RoomState (usa room.snippet, room.startedAt, room.players, room.chat e renderiza RaceTrack + FloatingChat, ambos multiplayer-only). Mas o núcleo de digitação é autocontido: typed/errors/totalKeystrokes, correctChars (l.49-53), wpm (l.55), accuracy (l.56-57), progress (l.58), handleInput (l.74-96), noPaste (l.99) + CodeDisplay/CodeEditor. Caminho limpo: extrair esse núcleo para um componente presentacional sem room (ex.: TypingCore com props code/language/onFinish?) e montar /practice reusando-o sem RaceTrack/FloatingChat/broadcast. Casa com Engine/Testes: extrair métricas puras de Race.tsx para src/lib/metrics.ts + testes de honestidade (WPM/precisão) #32 (extrair métricas puras p/ src/lib/metrics.ts) — coordene: se Engine/Testes: extrair métricas puras de Race.tsx para src/lib/metrics.ts + testes de honestidade (WPM/precisão) #32 landar antes, TypingCore consome metrics.ts; senão, cálculo inline e Engine/Testes: extrair métricas puras de Race.tsx para src/lib/metrics.ts + testes de honestidade (WPM/precisão) #32 refatora depois. Cross-link no epic Practice/Solo mode (§V.5): treinar sem sala — modo inteiro a fatiar #24.
    • ⚠️ Fonte de snippet: src/lib/languages.ts diz "Snippets live on the server" e pickSnippet é server-side (src/lib/snippets.ts). Practice é client-side sem sala → precisa obter snippet via (a) novo GET /api/snippet?language=&difficulty= leve reusando pickSnippet, ou (b) expor snippets ao cliente. Recomendo (a) — 1 fonte de verdade, sem inchar o bundle. Registrar a decisão na PR.
    • Segurança/Anti-cheat: Nula por design — Practice não escreve em matches/scores nem toca leaderboard. Confirmar que o TypingCore em modo practice não chama nenhuma rota de persistência (senão vira forja trivial). Não persistir PB de treino nesta fatia.
    • UX/Game-feel: É a porta de entrada — reduzir atrito do novato (§0.3) para treinar sem criar sala. Manter teclado-primeiro, prefers-reduced-motion, tokens do docs/UI-AAA-OVERHAUL.md. Sem countdown de sala: iniciar o cronômetro no 1º keystroke (elimina espera). Botões reiniciar/trocar snippet.
    • Performance: Área sagrada preservada por construção — reusar CodeEditor/handleInput mantém a latência de input; Practice remove RaceTrack/FloatingChat/broadcast → mais leve que a corrida. Manter o tick de now em 200ms (l.38-42); não adicionar loops.
    • Produto: Alto valor de aquisição/retenção — treino solo é o funil antes do multiplayer. Fatia correta do Practice/Solo mode (§V.5): treinar sem sala — modo inteiro a fatiar #24 (PB local/streaks ficam para fatias seguintes). P2 e posição Feat/aaa overhaul #2 do Curador adequadas.

    Plano sugerido:

    1. Extrair núcleo de Race.tsx → src/components/TypingCore.tsx (props code, language, onFinish?), sem room. Race.tsx passa a compor TypingCore + track/chat (refactor sem mudança de comportamento no multiplayer — rode o app e confira a corrida).
    2. GET /api/snippet?language=&difficulty= reusando pickSnippet + allowlist (ver Segurança/API: language e difficulty entram no banco sem allowlist e vazam para matches/scores #35).
    3. src/app/practice/page.tsx: escolhe lang/dificuldade, busca snippet, renderiza TypingCore; start no 1º keystroke; zero persistência.
    4. CTA no home (src/app/page.tsx) para /practice.
    5. PR normal (§7.3); validate-metrics verde se tocar cálculo.
  4. added
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    and removed
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    on Jul 16, 2026
  5. caioross commented on Jul 16, 2026

    @caioross
    OwnerAuthor

    Resolvida na PR #40 seguindo o plano do 🏛️ Parecer (opção (a) p/ snippet: GET /api/snippet server-side): núcleo de digitação extraído para TypingCore (Race multiplayer intacto, extração 1:1), rota /practice com seletor (default Fácil), cronômetro no 1º keystroke, stats + treinar de novo sem reload, CTA na home. Verificado no browser: zero chamadas a /api/rooms/Realtime no practice; gate 100% verde (typecheck, build, persistence 33/33, metrics 27/27).

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:engineMotor de digitação, WPM, precisão, anti-cheatarea:uiInterface, design system, animação, game feel

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions