Skip to content

feat(ui): feedback de erro no card do editor durante a corrida (#10) - #18

Merged
caioross merged 1 commit into
mainfrom
auto/issue-10-editor-error-feedback
Jul 8, 2026
Merged

feat(ui): feedback de erro no card do editor durante a corrida (#10)#18
caioross merged 1 commit into
mainfrom
auto/issue-10-editor-error-feedback

Conversation

@caioross

@caioross caioross commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Contexto

Feedback da persona O Iniciante (#9, fricção nº2): ao digitar funz em vez de func, nada ficava vermelho no ponto onde o olho fixa — a textarea do CodeEditor. O único char-error (vermelho) vivia no painel de código (CodeDisplay), longe do olhar; o resto era numérico e distante. Implementa a Parte IV · Spec — erro (docs/UI-AAA-OVERHAUL.md §779).

O que mudou (escopo mínimo, 3 arquivos, +81/-3)

  • Race.tsx — novo errorPulse (contador) incrementado em handleInput quando newErrors > 0. Reusa o sinal já computado; não altera nenhuma métrica (WPM/precisão/erros).
  • CodeEditor.tsx — recebe errorPulse; a cada pulso:
    • shake cirúrgico do card (transform only, ~90ms, 4–5px) via classe CSS;
    • flash de borda vermelha (cor) + ícone "erro" na margem (posição) por ~240ms; erros consecutivos só renovam a janela → nunca estrobosa >3Hz.
  • globals.css@keyframes cr-editor-shake (só translateX) + .editor-error (ring/borda vermelha).

Por que assim (área sagrada + acessibilidade)

  • Latência de input intacta: o shake é transform puro (composited na GPU), sem layout/reflow; roda só no erro, não por tecla. Sem transform em repouso → não cria containing block novo para o menu de opções position:fixed.
  • prefers-reduced-motion: a regra global já existente (animation-duration: 0.001ms) zera o shake automaticamente → sem movimento; sobra o flash de cor + ícone. Erro nunca é comunicado só por cor (cor + posição em ambos os modos).
  • Sem regressão: o char-error de CodeDisplay não foi tocado.

Gate (verde)

  • pnpm install --frozen-lockfile ✓ · pnpm typecheck ✓ · pnpm build
  • validate-metrics 24/0 ✓ · validate-persistence 22/0 ✓ (a mudança não toca cálculo/persistência; rodei por garantia)
  • lint = N/A (repo sem config ESLint; a CI não roda lint)

Fora de escopo (outras issues)

Som (type.error depende de lib/sound.ts), háptico e combo (§IV.4) — não entram aqui.

Sem Parecer do Conselho nesta issue; segui os critérios de aceitação e a dica de abordagem da própria issue.

Closes #10

O jogador olha para a textarea onde digita, mas o único sinal de erro
ficava no painel de código (char-error) e nos números distantes. Agora o
card do editor reage no ponto de fixação do olhar:

- shake cirúrgico (transform-only, ~90ms, 4–5px) via @Keyframes CSS —
  composited na GPU, sem layout/reflow competindo com a textarea (área
  sagrada). Sem transform em repouso: não cria containing block novo para
  o menu de opções (position:fixed).
- flash de borda vermelha (cor) + ícone "erro" na margem (posição) por
  ~240ms; erros consecutivos apenas renovam a janela → nunca estrobosa >3Hz.
- prefers-reduced-motion: a regra global zera a duração da animação →
  sem movimento, restando o flash de cor + ícone.

Dirigido por um errorPulse em Race.handleInput (incrementa quando
newErrors>0). Não altera nenhuma métrica (WPM/precisão/erros) nem o
char-error de CodeDisplay.

Refs #10

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

@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:

  • Make your repository public. Collaboration is free for open source and public repositories.
  • Upgrade to pro and add @caioross as a member. A Pro subscription is required to access Vercel's collaborative features.
    • If you're the owner of the team, click here to upgrade and add @caioross as a member.
    • If you're the user who initiated this build request, click here to request access.
    • If you're already a member of the caioross' projects team, make sure that your Vercel account is connected to your GitHub account.

To read more about collaboration on Vercel, click here.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements visual and motion error feedback in the code editor when a user types an incorrect character (Issue #10). This includes a brief border flash, a shake animation, and an error icon in the margin to ensure accessibility. The feedback is driven by an errorPulse prop. The review comments suggest simplifying the React state and effect management in CodeEditor.tsx by combining the two useEffect hooks and returning a cleanup function from the main effect, which eliminates the need for the errorTimer ref.

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.

Comment on lines 43 to +45
const pendingCaret = useRef<number | null>(null);
const errorTimer = useRef<number | null>(null);
const [errorActive, setErrorActive] = useState(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Podemos simplificar o código eliminando a referência errorTimer (useRef). Ao utilizar uma função de limpeza (cleanup) diretamente no useEffect do errorPulse, o React gerencia o ciclo de vida do timeout de forma nativa, tornando este useRef desnecessário.

Suggested change
const pendingCaret = useRef<number | null>(null);
const errorTimer = useRef<number | null>(null);
const [errorActive, setErrorActive] = useState(false);
const pendingCaret = useRef<number | null>(null);
const [errorActive, setErrorActive] = useState(false);

Comment on lines +80 to +100
useEffect(() => {
if (errorPulse === 0) return;
setErrorActive(true);
if (errorTimer.current) window.clearTimeout(errorTimer.current);
errorTimer.current = window.setTimeout(() => setErrorActive(false), 240);

const el = cardRef.current;
if (el) {
el.classList.remove("editor-shake");
// força reflow para reiniciar a animação em erros consecutivos
void el.offsetWidth;
el.classList.add("editor-shake");
}
}, [errorPulse]);

useEffect(
() => () => {
if (errorTimer.current) window.clearTimeout(errorTimer.current);
},
[]
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Podemos tornar este fluxo muito mais idiomático e limpo unificando a lógica de feedback e limpeza em um único useEffect.

Ao retornar a função de limpeza () => window.clearTimeout(timer) diretamente do efeito, o React cancelará automaticamente o timeout anterior quando um novo erro ocorrer (evitando sobreposição) e também quando o componente for desmontado. Isso elimina a necessidade de um segundo useEffect e do useRef para rastrear o timer.

  useEffect(() => {
    if (errorPulse === 0) return;
    setErrorActive(true);
    const timer = window.setTimeout(() => setErrorActive(false), 240);

    const el = cardRef.current;
    if (el) {
      el.classList.remove("editor-shake");
      // força reflow para reiniciar a animação em erros consecutivos
      void el.offsetWidth;
      el.classList.add("editor-shake");
    }

    return () => {
      window.clearTimeout(timer);
    };
  }, [errorPulse]);

@caioross

caioross commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

✅ Parecer PR Doctor — NORMAL (HANDBOOK §7.3), mergeando

Diff lido inteiro (3 arquivos, +81/-3). Classificado NORMAL (UI + engine sem tocar cálculo de métrica).

Gate

  • CI Install, typecheck & build = SUCCESS; MERGEABLE.
  • node .claude/skills/cr-typing-engine/scripts/validate-metrics.mjs24/0 ✓ (a mudança não toca WPM/precisão/erros).

Squash-merge.

@caioross
caioross merged commit bcca7f1 into main Jul 8, 2026
1 of 2 checks passed
@caioross
caioross deleted the auto/issue-10-editor-error-feedback branch July 8, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UX/Feel: erro de digitação é invisível onde o olho está (feedback de erro no card do editor · §779)

1 participant