Errors found during development — logged immediately, updated with solution and effectiveness. Format: discovered → root cause → fix → effectiveness.
- Data: 2026-04-09
- Contexto: Portal do Aluno — botão "Acessar Portal do Aluno" na tela de login não fazia nada
- Sintoma: Clicar no botão parecia não ter efeito; usuário permanecia em
/login - Causa raiz:
isAlunoRoute = pathname.startsWith('/aluno')incluía/aluno/login, causando redirect loop silencioso para usuários não autenticados - Fix:
src/utils/supabase/middleware.ts— excluir/aluno/logindeisAlunoRoute:const isAlunoRoute = pathname.startsWith('/aluno') && !pathname.startsWith('/aluno/login');
- Efetividade: ✅ Resolvido —
/aluno/loginagora acessível sem autenticação
- Data: 2026-04-09
- Contexto:
src/app/actions/auth.ts— ação de login para não-funcionários - Sintoma: Login bem-sucedido como aluno resultava em página 404
- Causa raiz:
redirect('/aluno')aponta para uma rota sempage.tsx; apenaslayout.tsxexiste em/aluno - Fix:
src/app/actions/auth.ts— alterar pararedirect('/aluno/dashboard') - Efetividade: ✅ Resolvido — alunos são direcionados corretamente ao dashboard
- Data: 2026-04-09
- Contexto:
src/lib/auth.test.ts— testes unitários derequireRole() - Sintoma: Testes falhavam com
received function did not throw - Causa raiz: Em Vitest,
redirect()mockado é um no-op; não lançaNEXT_REDIRECTcomo em produção - Fix: Substituir
expect(...).rejects.toThrow()por verificação direta do mock:Adicionarexpect(mockRedirect).toHaveBeenCalledWith('/login');
returnapósredirect('/login')emauth.tspara evitar execução continuar comuser === null - Efetividade: ✅ Resolvido — 5/5 testes passando
- Data: 2026-04-09
- Contexto:
src/lib/auth.ts— import de enum do Prisma - Sintoma: Warning ESLint:
@typescript-eslint/consistent-type-imports - Causa raiz: Enums do Prisma usados apenas como anotação de tipo devem usar
import type - Fix:
import type { Role } from '@prisma/client' - Efetividade: ✅ Resolvido — 0 erros de lint
- Data: 2026-04-09
- Contexto:
src/lib/auth.test.ts— mock do cliente Supabase - Sintoma: Warning ESLint:
@typescript-eslint/no-explicit-any - Causa raiz: Interface do Supabase é complexa; mock parcial requer cast
- Fix:
eslint-disable-next-line @typescript-eslint/no-explicit-anycom comentário justificando - Efetividade: ✅ Resolvido (workaround aceitável para mocks de test)
- Data: 2026-04-09
- Contexto: Primeiro commit do US00 — mensagem com linhas > 100 caracteres
- Sintoma: Hook
commit-msgrejeitou o commit:body-max-line-length[72] - Causa raiz: Commitlint configurado com limite de 72 chars por linha no body
- Fix: Reescrever mensagem do commit com linhas mais curtas (quebrar frases longas)
- Efetividade: ✅ Resolvido — sempre verificar comprimento de linha antes de commitar
- Data: 2026-04-09
- Contexto: Tentativa de criar spec para US00 a partir da branch
feat/us01-student-management - Sintoma:
setup-plan.shrejeitou o nome da branch - Causa raiz: Script exige padrão
NNN-feature-name;feat/us01-student-managementnão segue o padrão - Fix: Criar nova branch com nome correto:
002-us00-financial-role-access - Efetividade: ✅ Resolvido — seguir padrão
NNN-feature-namepara branches de feature
ERR-008 — Commits foram para branch errada (003-002-us00-financial em vez de 002-us00-financial-role-access)
- Data: 2026-04-09
- Contexto: Após implementação do US00
- Sintoma: Commits apareceram na branch temporária, não na branch de trabalho
- Causa raiz:
setup-plan.shcriou uma branch temporária003-002-us00-financial; não foi feito checkout da branch correta antes de commitar - Fix:
git checkout 002-us00-financial-role-access && git merge 003-002-us00-financial(fast-forward) - Efetividade: ✅ Resolvido — sempre verificar branch ativa antes de commitar
- Data: 2026-04-09
- Contexto: PR #57 criado sem as seções "Type of Change", "Related Documents" e "Checklist"
- Sintoma: Alerta de CI: "The description lacks the required template structure"
- Causa raiz: PR criado com template próprio em vez de usar
.github/pull_request_template.md - Fix:
gh pr edit 57 --body "..."com as seções obrigatórias preenchidas - Efetividade: ✅ Resolvido — sempre consultar
.github/pull_request_template.mdao criar PRs
- Data: 2026-04-09
- Contexto: Após criar spec para US00 com
setup-plan.sh - Sintoma: Comandos speckit referenciavam
specs/001-fix-type-safety-bugsem vez despecs/003-002-us00-financial - Causa raiz:
feature.jsonnão foi atualizado automaticamente pelo script - Fix: Editar manualmente
feature.jsonpara apontar para o diretório correto - Efetividade: ✅ Resolvido — verificar
feature.jsonapós criar nova feature
- Data: 2026-04-10
- Contexto: CI pipeline — job Quality Gates, step
npm audit - Sintoma:
npm auditretornou HIGH severity, travando o CI - Causa raiz:
next@15.5.14continha vulnerabilidade DoS (GHSA-q4gf-8mx6-v5v3) via request headers; patch disponível em15.5.15 - Fix:
npm audit fix— atualizounextde15.5.14para15.5.15automaticamente - Efetividade: ✅ Resolvido —
npm auditpassou sem high/critical - Lição: Manter dependências atualizadas;
nexttem releases frequentes de segurança
- Data: 2026-04-10
- Contexto: CI pipeline — job Quality Gates, step
npm run format:check - Sintoma: Prettier falhou em
docs/.obsidian/plugins/*/main.js(bundles minificados de plugins) - Causa raiz:
docs/.obsidian/não estava no.prettierignore; Prettier tentou formatar arquivos JS minificados de terceiros - Fix: Adicionar
docs/.obsidian/ao.prettierignore - Efetividade: ✅ Resolvido
- Lição: Qualquer diretório com código de terceiros ou gerado deve estar no
.prettierignore. Revisar ao adicionar novas ferramentas ao projeto.
- Data: 2026-04-10
- Contexto: CI pipeline — job E2E Tests, step Seed E2E test users
- Sintoma:
supabaseKey is required— variávelSUPABASE_LOCAL_ANON_KEYficou vazia - Causa raiz:
supabase statusem modo padrão não exporta chaves em formato parseável; a saída varia por versão da CLI.grep 'anon key'não encontrava nada. - Tentativa ineficiente: Regex/grep na saída plain text do
supabase status - Fix: Usar
supabase status --output env(disponível desde CLI v1.23) + fallback com JWTs padrão do Supabase demo:STATUS_ENV=$(supabase status --output env 2>/dev/null || echo "") ANON_KEY=$(echo "$STATUS_ENV" | grep "^ANON_KEY=" | cut -d= -f2- | sed "s/^[\"']//; s/[\"']$//") if [ -z "$ANON_KEY" ]; then ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." # fallback padrão fi
- Efetividade: ✅ Resolvido
- Data: 2026-04-10
- Contexto: CI pipeline — job E2E Tests, step Push database schema
- Sintoma: Seed falhou com erros de tabela não encontrada;
migrate deploycompletou sem erros mas não criou tabelas - Causa raiz: O projeto usa workflow
db push(sem diretórioprisma/migrations/).migrate deployverifica se há migrations pendentes — sem o diretório, retorna silenciosamente com "No pending migrations to apply" sem criar nenhuma tabela. - Fix: Substituir
npx prisma migrate deploypornpx prisma db push --accept-data-loss - Efetividade: ✅ Resolvido — tabelas criadas, seed executou com sucesso
- Lição: Sempre verificar se o projeto usa
migrateoudb pushantes de configurar o CI. Projetos comdb pushnão têm o diretóriomigrations/.
- Data: 2026-04-10
- Contexto: CI pipeline — job E2E Tests, step Seed E2E test users
- Sintoma:
invalid JWT: token is malformed: could not base64 decode header: illegal base64 data at input byte 0 - Causa raiz:
supabase status --output envretorna valores entre aspas:ANON_KEY="eyJ...". Apóscut -d= -f2-, o valor ficava"eyJ..."— com a aspa"inicial, que é um caractere inválido em base64, quebrando o decode do JWT. - Diagnóstico: O erro "input byte 0" indica que o primeiro byte do token já é inválido — revelador da aspa.
- Fix: Adicionar
sed "s/^[\"']//; s/[\"']$//"após ocutpara remover aspas envolventes - Debug útil adicionado:
echo "Extracted anon key length: ${#ANON_KEY}"para confirmar nos logs que a chave foi extraída - Efetividade: ✅ Resolvido
- Data: 2026-04-10
- Contexto: CI pipeline — job E2E Tests, step Run E2E tests
- Sintoma:
Error: Your project's URL and Key are required to create a Supabase client!no webserver do Playwright (src/utils/supabase/middleware.ts:15) - Causa raiz: Supabase renomeou o "anon key" para "publishable default key". O código fonte (
middleware.ts,client.ts,server.ts) já usava o novo nomeNEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY. O CI e o.env.testainda usavam o nome antigoNEXT_PUBLIC_SUPABASE_ANON_KEY. A variável correta nunca foi definida no ambiente do webserver. - Como o webserver recebe env vars: O webserver Playwright (
npm run dev) é processo filho do runner do Playwright, que herdaprocess.env. Se a variável não está no env do processo pai, não chega ao webserver. - Fix: Adicionar ao step "Run E2E tests":
E ao
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY: ${{ env.SUPABASE_LOCAL_ANON_KEY }}
.env.testlocal: mesma variável com o mesmo valor do anon key. - Efetividade: ✅ Resolvido — webserver inicializou corretamente
- Lição: Quando o Supabase (ou qualquer lib) renomeia variáveis, buscar sistematicamente: (1) o que o
src/lê, (2) o que os.env.*definem, (3) o que o CI injeta.
- Data: 2026-04-10
- Contexto:
tests/e2e/specs/student-portal.spec.ts:9— teste "ALUNO accesses /aluno/dashboard" - Sintoma:
expect(locator).toBeVisible() failed — Timeout: 5000ms — element(s) not found — waiting for getByRole('heading') - Causa raiz: A página
/aluno/dashboardé um Server Component que executa 2 queries Prisma antes de retornar o HTML com oh1. Em CI (runner mais lento, dev server compilando on-demand), esse processo pode levar 5-8 segundos — além do timeout padrão de 5000ms do Playwright. Falhou em ambas as tentativas (original + retry). - Contexto adicional: O
h1está dentro de ummotion.divdo framer-motion cominitial={{ opacity: 0 }}, masopacity: 0não esconde o elemento paratoBeVisible()do Playwright — o problema era exclusivamente de timing. - Fix:
{ timeout: 15_000 }na asserção, igual ao timeout já usado pelologinAs():Aplicado também preventivamente ao teste deawait expect(page.getByRole('heading')).toBeVisible({ timeout: 15_000 });
meus-treinos(mesma estrutura). - Efetividade: ✅ Parcialmente — timeout aumentado, mas o teste continuou falhando por duas causas adicionais (ver ERR-018)
- Lição: Em CI, nunca usar o timeout padrão de Playwright (5000ms) para asserções em elementos que dependem de dados carregados server-side. Usar pelo menos 15_000ms.
- Data: 2026-04-10
- Contexto:
tests/e2e/specs/student-portal.spec.ts— "ALUNO accesses /aluno/dashboard" — falhou mesmo com{ timeout: 15_000 } - Sintoma:
element(s) not foundcom timeout de 15 segundos — timeout não era mais o problema
- Componente:
src/components/ui/card.tsxlinhas 28-36 - Problema:
CardTitlerenderiza como<div>(não<h1>/<h2>/<h3>). Quando o aluno não existe no Prisma,page.tsxmostrava o estado de erro com<CardTitle>Sinto muito!</CardTitle>— que é um<div>.getByRole('heading')só encontra elementos com role semântico de heading (h1-h6ourole="heading"). - Diagnóstico: O
h1real só existe nodashboard-client.tsx, renderizado quandoalunoé encontrado. Se o seed E2E criou o usuário no Supabase Auth mas falhou ao criar o registro no Prisma, a página mostra o estado de erro sem heading. - Fix: Substituir
<CardTitle>por<h2 className="text-2xl font-semibold leading-none tracking-tight">no estado de erro depage.tsx. Remover importCardTitlenão utilizado. - Lição: Componentes de UI que parecem headings visualmente podem não ser semanticamente. Sempre verificar a tag HTML renderizada, não o nome do componente.
-
Arquivo:
src/app/aluno/dashboard/dashboard-client.tsxlinha 8 -
Problema:
import { generateWorkoutFeedback } from '@/ai/flows/workout-feedback-flow'era um import estático. Oworkout-feedback-flow.tsusa@opentelemetry/exporter-jaegerque não está disponível no ambiente de bundle do Next.js. O erro de bundling em CI gerava warnings repetidos no webserver e podia impedir a montagem do componente. -
Detalhe crítico: O
meus-treinostest passava porqueworkout-generator-flow.ts(importado lá) não tinha a mesma dependência de OpenTelemetry. -
Fix: Converter para import dinâmico dentro do handler
handleFinishTraining:const { generateWorkoutFeedback } = await import('@/ai/flows/workout-feedback-flow');
O flow só é necessário quando o usuário clica "Finalizar Treino" — nunca no render inicial.
-
Lição: Imports de módulos pesados com dependências de runtime (OpenTelemetry, telemetria, etc.) devem ser dinâmicos quando usados apenas em handlers de evento. Import estático ≠ "só executa quando chamado" — o módulo é resolvido e bundled na inicialização.
-
Efetividade: ✅ Resolvido — import dinâmico eliminou o erro de bundle; student-portal suite passou em CI
-
Data: 2026-04-17
-
Contexto: Branch
fix/e2e-auth-stabilization—src/app/actions/auth.ts— login de todos os roles -
Sintoma: Login retornava sem erro, mas o browser ficava na página
/loginsem navegar.waitForURL('**/dashboard**')timeout em 15s. -
Causa raiz: Next.js App Router implementa
redirect()lançando internamente uma exceção especial (NEXT_REDIRECT). Se oredirect()é chamado dentro de um blocotry/catchsem re-throw, a exceção é absorvida — o redirect nunca acontece e a server action simplesmente retornaundefined. -
Fix: Adicionar guard
isRedirectErrorantes de qualquer catch genérico:import { isRedirectError } from 'next/dist/client/components/redirect-error'; // ⚠️ NÃO importar de 'next/navigation' — não exporta isRedirectError (TS2305) try { redirect('/dashboard'); } catch (err: unknown) { if (isRedirectError(err)) throw err; // ← re-throw obrigatório return { error: 'Erro inesperado' }; }
-
Arquivo afetado:
src/app/actions/auth.ts -
Lição:
redirect()do Next.js é implementado via throw. Nunca envolver emtry/catchsem o guardisRedirectError. O path de import correto énext/dist/client/components/redirect-error—next/navigationnão exporta essa função (erro TypeScript TS2305 se tentar). -
Efetividade: ✅ Resolvido — todos os roles redirecionaram corretamente após o fix
- Data: 2026-04-17
- Contexto: Branch
fix/e2e-auth-stabilization—tests/e2e/specs/auth.spec.ts— teste ALUNO - Sintoma: Login do ALUNO:
waitForURLpassava (URL mudava para/aluno/dashboard), mas a página renderizava/aluno/login.getByRole('heading')— element not found. - Causa raiz: Next.js App Router inline-renderiza o target do redirect durante a mesma resposta POST da server action. Nesse momento,
cookies()no server component lê os request headers — mas o cookie de sessão (sb-*-auth-token) setado porsignInWithPassword()só existe no responseSet-Cookie. O browser ainda não armazenou o cookie, entãogetUser()falha e a página redireciona para/aluno/login.- Ponto crítico:
/dashboard(admin) não chamagetUser()no server component e não foi afetado./aluno/dashboardchama e foi afetado.
- Ponto crítico:
- Fix: No helper
loginAs(), forçar uma navegação GET após owaitForURL:await page.waitForURL('**/dashboard**', { timeout: 15_000 }); await page.goto(expectedPath); // GET real — browser já tem o cookie armazenado
- Arquivo afetado:
tests/e2e/helpers/auth.ts - Lição: Em Next.js App Router, o inline RSC render do redirect target não recebe o cookie de sessão que acabou de ser setado. Qualquer server component que chame
getUser()oucookies()e seja redirect target de uma server action está sujeito a esse timing. Em produção funciona (browser faz GET separado); em E2E é necessário forçar o GET explicitamente. - Efetividade: ✅ Resolvido — ALUNO aterrou em
/aluno/dashboardem todas as execuções subsequentes
ERR-021 — E2E Auth: Todos os logins falhando com TimeoutError: page.waitForURL — servidor usando credenciais de produção
- Data: 2026-04-17
- Contexto: Branch
fix/e2e-auth-stabilization— primeira execução após configurar o.env.test - Sintoma:
TimeoutError: page.waitForURL: Timeout 15000ms exceededem todos os 3 testes de login. Apenas o teste de credenciais inválidas passava. - Causa raiz:
playwright.config.tstinhareuseExistingServer: true(padrão) e a porta padrão (3001). Um servidor dev já estava rodando na porta 3001 carregado com.env.local(credenciais Supabase de produção). O Playwright reusou esse servidor — os usuários de teste não existem no projeto Supabase de produção, então todos os logins falhavam silenciosamente nosignInWithPassword(). - Diagnóstico confirmado: Trocar temporariamente para
reuseExistingServer: falsee port 3333 mostrou que o servidor novo (com.env.test) funcionava. - Fix: Em
playwright.config.ts:webServer: { command: 'npm run dev -- --port 3333', // porta dedicada E2E url: 'http://localhost:3333', reuseExistingServer: false, // ← nunca reusar env: { NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '', // ... forward explícito de todas as vars do .env.test }, }
- Arquivo afetado:
playwright.config.ts - Lição:
reuseExistingServer: trueé conveniente para velocidade mas perigoso quando o projeto tem múltiplos ambientes (.env.local+.env.test). O servidor reutilizado pode ter sido iniciado com qualquer conjunto de variáveis de ambiente. Em projetos com staging/test isolation, sempre usarreuseExistingServer: false+ porta dedicada para E2E. - Efetividade: ✅ Resolvido — porta 3333 dedicada, servidor sempre iniciado com
.env.test