refactor(engine): extrai métricas puras de Race.tsx para src/lib/metrics.ts (#32) - #48
Conversation
…ics.ts (#32) Move o cálculo de WPM, precisão, progresso e contagem de acertos de dentro de Race.tsx para funções puras e determinísticas em src/lib/metrics.ts, cobertas por src/lib/metrics.test.ts. Extração 1:1 — nenhuma mudança de fórmula, arredondamento ou comportamento observável na tela. - countCorrectChars / computeWpm / computeAccuracy / computeProgress - correctChars segue no useMemo (área sagrada, HANDBOOK §2 — sem trabalho extra por keystroke no hot path da corrida) - fonte única de verdade: elimina a divergência silenciosa entre a lógica de produção e a cópia em validate-metrics.mjs A precisão honesta da issue #20 (floor + teto de 99% com erros) já estava inline em Race.tsx e foi preservada byte-a-byte; os testes a pinam. Refs #32 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@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. |
There was a problem hiding this comment.
Code Review
This pull request extracts typing metrics calculation logic (countCorrectChars, computeWpm, computeAccuracy, and computeProgress) from the Race component into a dedicated metrics utility file, accompanied by a comprehensive unit test suite. Feedback was provided to optimize the countCorrectChars function by limiting the loop iteration to the minimum length of the typed and target strings, avoiding unnecessary comparisons with undefined.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export function countCorrectChars(typed: string, code: string): number { | ||
| let c = 0; | ||
| for (let i = 0; i < typed.length; i++) if (typed[i] === code[i]) c++; | ||
| return c; | ||
| } |
There was a problem hiding this comment.
Atualmente, o loop percorre todo o comprimento de typed. Se typed for maior que code, o código tentará acessar índices fora dos limites de code (retornando undefined), o que é ineficiente e conceitualmente incorreto. Embora a comparação com undefined resulte em false e não quebre a lógica, é mais seguro e eficiente limitar a iteração ao menor comprimento entre as duas strings usando Math.min(typed.length, code.length).
| export function countCorrectChars(typed: string, code: string): number { | |
| let c = 0; | |
| for (let i = 0; i < typed.length; i++) if (typed[i] === code[i]) c++; | |
| return c; | |
| } | |
| export function countCorrectChars(typed: string, code: string): number { | |
| let c = 0; | |
| const minLength = Math.min(typed.length, code.length); | |
| for (let i = 0; i < minLength; i++) { | |
| if (typed[i] === code[i]) c++; | |
| } | |
| return c; | |
| } |
🩺 Parecer do PR Doctor — APROVADA (§7.3 Normal)Classificação: normal (refactor de engine com Conferido linha a linha (extração é 1:1, não há mudança de número na tela):
Gate reproduzido localmente no worktree da branch (não confiei só no corpo do PR):
|
União com origin/main (conflito em Race.tsx) mantendo uma só fonte de verdade em vez de reintroduzir cópias que a main acabou de eliminar. - Race.tsx fica na forma composta desta branch (RaceTrack + TypingCore + chat); o cálculo inline que o #32 tirou de lá não volta. - TypingCore.tsx passa a importar countCorrectChars/computeWpm/computeAccuracy/ computeProgress de @/lib/metrics (#32, mergeada no PR #48). A extração era 1:1 do Race.tsx pré-#32; sem isso o merge recriava a duplicação de fórmula que a #32 existiu para matar. `correctChars` segue no useMemo (área sagrada §2). - /api/snippet usa resolveLang/resolveDifficulty de @/lib/room (#35, mergeada no PR #38) no lugar da checagem própria contra LANGUAGES/DIFFICULTIES — mesma política de fronteira das rotas de sala, uma implementação só. Gate: typecheck OK · build OK (/practice 149 kB estática, /api/snippet dinâmica) · vitest 34/34 · validate-metrics 37/0 · validate-persistence 62/0. Refs #25 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contexto
A honestidade das métricas (WPM, precisão, erros, progresso) é a verdade sagrada do domínio (spec §0.3). Até aqui todo o cálculo morava inline em
src/components/Race.tsx, impossível de testar por unidade — o único "teste" era uma cópia da lógica em.claude/skills/cr-typing-engine/scripts/validate-metrics.mjs, sujeita a divergir em silêncio do código de produção.O que mudou e por quê
Extrai as quatro funções puras e determinísticas para
src/lib/metrics.ts(fonte única de verdade) e cobre comsrc/lib/metrics.test.ts:countCorrectChars(typed, code)— loop char-a-char;typed>codenão estoura.computeWpm(correctChars, elapsedMin)— ~5 chars/palavra;elapsedMin ≤ 0.001 → 0.computeAccuracy(errors, totalKeystrokes)— precisão honesta (Engine/Honestidade: "Precisão 100%" coexiste com "Erros 1" — arredondamento mascara erro e rótulo confunde #20):errors === 0 → 100, senãofloorcom teto de 99%, clamp[0,100].computeProgress(typedLen, codeLen)—[0,1],codeLen === 0 → 0.Race.tsxpassa a importar e usar essas funções;correctCharssegue nouseMemo(área sagrada HANDBOOK §2 — nenhuma alocação/loop novo por keystroke no hot path).Extração 1:1 — zero mudança de número na tela
O Parecer do Conselho antecipava uma contradição ("copie 1:1" × "nunca 100 com erro") porque avaliou uma revisão antiga com
Math.round. Divergência: a correção de honestidade da #20 já está namain(Race.tsxjá usavaMath.floor+ teto de 99%). Logo esta fatia é um refactor puro, byte-a-byte — nenhuma corrida mostra número diferente de antes. Os testes apenas pinam as invariantes já vigentes; não há impacto visível a declarar.Como foi validado (gate real)
pnpm typecheck✅pnpm build✅pnpm test✅ — 34/34 (13 novos emmetrics.test.ts+ 21 deroom.test.ts)node .claude/skills/cr-typing-engine/scripts/validate-metrics.mjs✅ 37/0node scripts/validate-persistence.mjs✅ 33/0pnpm lint= N/A (repo sem config ESLint; a CI não roda lint)Riscos
Baixos. Refactor sem mudança de comportamento; a UI renderiza os mesmos valores. Diff mínimo (3 arquivos, +138/−15). Numa fatia futura,
validate-metrics.mjspode passar a importar demetrics.tspara eliminar de vez a duplicação de harness (fora do escopo mínimo aqui).Segui o plano do Conselho, com uma divergência declarada: como a honestidade #20 já estava na
main, não houve mudança de número nem necessidade deCloses #20.Closes #32