Skip to content

feat: pagamento no BFF, JWT só em cookie HttpOnly e corte de escopo para a Fase 1 - #10

Merged
Grolla05 merged 36 commits into
mainfrom
Versão-1.0
Sep 17, 2026

Hidden character warning

The head ref may contain hidden characters: "Vers\u00e3o-1.0"
Merged

Grolla05 merged 36 commits into
mainfrom
Versão-1.0

Conversation

@Grolla05

@Grolla05 Grolla05 commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Resumo

Consolida a branch Versão-1.0 sobre main: a cadeia de pagamento passa a ser proxy fino para o BFF Java, o JWT do aluno sai do localStorage, o escopo do repositório é cortado para a Fase 1 e a documentação é reescrita contra o código atual.

São 22 commits, 347 arquivos alterados (+28.809 / −21.315). Boa parte do volume é asset binário (stickers/mascotes gerados) e a reorganização de src/lib por domínio, que aparece como rename.

Entregas principais

1. Pagamento no BFF (424857d)

As cinco rotas de pagamento viraram proxy fino. Preço, status, titularidade e tier saem inteiramente do Java; os webhooks das gateways apontam direto para o BFF.

  • bffPayments.ts concentra o encaminhamento (BACKEND_API_URL, Bearer do aluno, tratamento de erro). Nenhum erro do BFF chega ao navegador — acima de 2xx vira mensagem genérica do status.
  • sanitizeCheckoutBody remonta o corpo por allowlist: só planId e payer. Valor, status e e-mail do pagador não saem do navegador.
  • activate/route.ts grava o JWT novo no cookie user_data HttpOnly e remove o campo token do corpo.
  • normalizeTier com fail safe: desconhecido vira FREE.
  • Removidos stripe, mercadopago e @mercadopago/sdk-react. Ficam só @stripe/stripe-js e @stripe/react-stripe-js (Payment Element).

BREAKING CHANGE: enquanto o BFF Java não expuser GET /plans, os três endpoints de checkout e POST /subscriptions/activate, o checkout responde 503. O contrato exigido está na primeira seção do CHANGES.md.

2. JWT fora do localStorage (f8a638e)

O token era gravado no localStorage além do cookie — uma cópia legível por qualquer script na página (dependência npm comprometida, extensão, XSS).

  • Cinco rotas passam a aceitar o cookie via readUserToken (universities, flash-cards, user/stats, Nota-corte, subscribe). O subscribe recebia o JWT no corpo da requisição.
  • Nova GET /api/user/me devolve só claims de exibição (id, nome, email, tier, newsletter) — nunca token, exp, iat ou payload cru; um teste trava a lista exata de chaves.
  • sync-user para de devolver o JWT no corpo e de vazar details/error do backend.
  • Oito chamadas do cliente param de montar Authorization. Server Components que leem o cookie com cookies() ficaram intactos.
  • useUserTier busca /api/user/me; o setInterval de 2s foi removido.
  • Limpeza incondicional no carregamento apaga a cópia legada do localStorage.
  • Quatro console.log que imprimiam pedaço do JWT removidos.

3. Sessão não trava quando o BFF não responde (bb83bc8)

Sintoma: 401 permanente em GET /api/user/me e a aplicação inteira se comportando como deslogada.

  • POST /api/sync-user: timeout próprio de 8s (AbortSignal.timeout), abaixo dos 10s do undici. Classificação de falha por causa ([NET] 504, [BFF] relay, [SHAPE] 502, [BUG] 500) no log do servidor — na tela os quatro casos são idênticos. extrairJwt aceita cinco grafias de resposta do Java e o cookie só é gravado se decodeJWT devolver payload válido.
  • GET /api/user/me: fallback para a sessão do NextAuth como segunda fonte. readNextAuthSessionToken() remonta o cookie fragmentado (next-auth.session-token.0/.1) ordenando pelo sufixo numérico e reconhece o nome __Secure-. O valor remontado é um JWE, então quem abre é o decode do next-auth/jwt com a NEXTAUTH_SECRET. No fallback o id é sempre null, para não impedir a gravação futura do user_data. Novo header X-Claims-Source.

4. Corte de escopo para a Fase 1 (f73044e)

Removido: app/api/ai/ (VestIA, Fase 3), PremiumFeatureModal, CronogramaMockup (Planner, Fase 2), lib/data/playlists.ts, o escopo calendar.events do provider Google, os wrappers DndProvider e 13 dependências sem consumidor (@google/genai, googleapis, date-holidays, react-dnd, @fullcalendar/*, docx, file-saver e outras).

Mantido de propósito: Explicação de Gabarito por IA, Discord, Stripe e a criação de prova personalizada. A copy da landing e do paidPlan foi reescrita para não vender features que não vão ao ar na Fase 1.

5. Documentação e testes (f00215e, 10a70ee)

README.md da raiz, front/README.md, src/app/api/README.md, src/lib/MOCK_ARCHITECTURE.md e src/test/K6/README.md reescritos contra o código atual — quatro afirmações que a documentação fazia e o código desmentia foram corrigidas. PAUTA.md virou docs/Pauta_para_reuniao.md. CHANGES.md guarda o histórico detalhado.

Vitest configurado (não havia test runner). Os scripts k6 foram separados dos unitários.

CI, README enxuto e lint/testes verdes (66bf2b2, 6ee5e21, b780b67)

  • GitHub Actions (.github/workflows/ci.yml): em todo PR contra main roda npm ci --legacy-peer-deps → npm run lint → npm run test com Node 20.
  • Lint de volta: next lint → eslint . com flat config nativo do eslint-config-next. Zerados 103 erros: 63 any substituídos por tipos das respostas do BFF, entities, ts-comment e 7 regras do React Compiler (purity, immutability, static-components, preserve-manual-memoization, error-boundaries). react-hooks/set-state-in-effect (29, padrão de hidratação) fica como warn; está registrado como dívida.
  • Testes: a rota generate-token passa a usar readUserToken + decodeJWT (antes, cookies() fora do request scope virava 500); 2 scripts assert convertidos para Vitest; removido o bloco que lia Questoes_Gemini.tsx, apagado em 83575da.
  • README enxuto, apontando para projeto-EdTech/docs; front/.env.example criado.

Verificado localmente: npm test 14 arquivos / 107 testes ✅ · npm run lint 0 erros ✅ · tsc --noEmit 0 erros ✅ · npm run build ✅ · npm ci --dry-run ✅

Open-source (MIT) e merge de main (719a612, d3b8a62, 3ee8161)

  • Licença: o projeto deixa de ser produto fechado. O LICENSE MIT já tinha entrado em 982f035; saíram o aviso "Repositório PRIVADO — uso exclusivo da equipe" e a seção "código proprietário, todos os direitos reservados" do README, e o rodapé do site passa a dizer "Projeto open-source sob licença MIT". A ressalva: a licença cobre o código — marca, identidade visual e o banco de questões das bancas ficam de fora.
  • Antes de tornar o repositório público: varredura no histórico completo por .env versionado e por chaves Stripe, Google, Mercado Pago, Slack e PEM — nenhum segredo real, só placeholders em documentação.
  • Merge de main (-s ours): as branches divergiram em ce69c32 e seguiram paralelas, sem nenhum patch em comum — eram 87 arquivos em conflito, que travavam o CI. A árvore da Versão-1.0 vale inteira, por ser a linha mais recente e a que registra o corte para a Fase 1. Ficam de fora os 68 arquivos que só existem na main (VestIA, planner, ranking, community, pricing, badges), removidos de propósito em f73044e e recuperáveis pelo histórico.
  • Primeira execução do CI pegou um bug real: DiscordTokenModal.test.tsx usava o global navigator, que só existe do Node 21 em diante — passava local (Node 24) e quebrava no runner (Node 20). Corrigido com vi.stubGlobal.

Como testar

cd front
npm ci --legacy-peer-deps
npm run lint
npm test

Pendências conhecidas (não introduzidas aqui)

  • POST /api/simulations/save-result e POST /api/generate-explanation não existem.
  • POST /api/games/flash-cards responde 405 (a rota exporta só GET).
  • simulationStore.ts e LoadingContext.tsx sem importador.
  • Causa raiz do sync-user falhar segue em aberto — suspeitas registradas em bb83bc8: o endpoint chamado é ${BACKEND_API_URL}/auth/google enquanto as rotas que funcionam usam ${BACKEND_API_URL}/api/...; o scope do Google não inclui openid; refreshAccessToken não renova o id_token.

Base sugerida: main. A branch está 42 commits atrás de main — vale conferir se o alvo certo não é DEV.

🤖 Generated with Claude Code

Grolla05 and others added 22 commits January 19, 2026 03:00
O Next resolvia preco na Stripe e no Mercado Pago, criava a cobranca com os
SDKs dos gateways, consultava se o pagamento aconteceu e conferia titularidade
contra a sessao. Isso violava a regra do CLAUDE.md ("thin proxies to the Java
BFF"), mantinha dois catalogos de preco independentes e deixava as chaves de
gateway num ambiente que nao precisa delas.

As cinco rotas de pagamento viram proxy fino. Preco, status, titularidade e
tier passam a sair inteiramente do Java, e os webhooks das gateways apontam
direto para ele -- o salto Next->Java, que nao tinha credencial para
autenticar, deixa de existir.

- bffPayments.ts concentra o encaminhamento: BACKEND_API_URL, Bearer do aluno
  e tratamento de erro num lugar so. Nada de erro do BFF chega ao navegador;
  acima de 2xx a resposta vira mensagem generica do proprio status.
- sanitizeCheckoutBody remonta o corpo por allowlist: so planId e payer
  (first_name, last_name, identification e, no boleto, address). Valor, status
  e e-mail do pagador nao saem do navegador -- e-mail vem da sessao no Java, o
  que e o que torna a conferencia de titularidade possivel.
- O payer do boleto foi unificado com o de cartao e PIX: um contrato so.
- activate/route.ts grava o JWT novo no cookie user_data HttpOnly e remove o
  campo token do corpo -- o tier muda sem o aluno relogar, e o navegador nunca
  recebe o token.
- normalizeTier absorve a grafia do claim tipo, que ainda nao foi acordada com
  o time Java. Fail safe: desconhecido vira FREE.
- Removidos stripe, mercadopago e @mercadopago/sdk-react. Ficam apenas
  @stripe/stripe-js e @stripe/react-stripe-js, que sao o Payment Element --
  o cartao vai direto do navegador para a gateway.

Vitest configurado (nao havia test runner). Os scripts k6 foram para
src/test/K6/ para nao se misturarem aos unitarios.

BREAKING CHANGE: enquanto o BFF Java nao expuser GET /plans, os tres
endpoints de checkout e POST /subscriptions/activate, o checkout responde 503.
O contrato exigido esta na primeira secao do CHANGES.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ttpOnly

O JWT do aluno era gravado no localStorage alem do cookie user_data. Eram duas
copias do mesmo token: uma que o navegador protege (HttpOnly, JS nao le) e uma
que qualquer script na pagina le numa linha -- dependencia npm comprometida,
extensao, XSS. Com o token, o portador se faz passar pelo aluno no BFF.
Contraria a regra explicita do CLAUDE.md.

A copia existia porque o cliente precisava de duas coisas, e so uma era real:
montar Authorization: Bearer (redundante -- o cookie acompanha sozinho todo
fetch same-origin) e ler o tier de dentro do JWT para desenhar a tela (essa
precisava de substituto).

Executado na ordem servidor -> cliente -> escrita; inverter derrubaria
autenticacao em tela que funciona.

- Cinco rotas passam a aceitar o cookie via readUserToken (universities,
  flash-cards, user/stats, Nota-corte, subscribe). O subscribe era o pior dos
  cinco: recebia o JWT no corpo da requisicao, e um token que o cliente
  escolhe qual mandar e um token que ele pode trocar pelo de outro aluno.
  Etapa aditiva -- o header continua sendo aceito.
- GET /api/user/me (nova) devolve so os claims de exibicao
  { id, nome, email, tier, newsletter }. Nunca token, exp, iat nem payload
  cru. Um teste trava a lista exata de chaves da resposta.
- sync-user para de devolver o JWT no corpo (responde { ok, id, tipo }) e
  para de vazar details/error do backend ao navegador.
- Oito chamadas do cliente param de montar Authorization. Server Components
  que leem o cookie com cookies() ficaram intactos: servidor lendo o proprio
  cookie e o caminho certo.
- useUserTier busca /api/user/me. O setInterval de 2s morreu -- era leitura de
  memoria, viraria uma requisicao a cada dois segundos por aba aberta. Restam
  os gatilhos user_synced (mesma aba) e storage (outras abas).
- As tres escritas no localStorage sumiram, e uma limpeza incondicional no
  carregamento apaga a copia legada: parar de escrever nao apaga o que ja
  foi escrito.
- Quatro console.log que imprimiam pedaco do JWT foram removidos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A documentacao descrevia um repositorio que nao existe mais: SDK do Mercado
Pago dentro do Next, webhooks que foram removidos, rotas lendo array estatico,
"no test runner configured yet". Quem chegasse pelo README implementaria
contra a arquitetura errada.

Conferido arquivo por arquivo contra o codigo. Quatro afirmacoes que os
documentos faziam e o codigo desmente foram corrigidas:

- "k6 scripts use __ENV.BFF_URL" -- BASE_URL e fixo em localhost:3000
- UniversityStorage "falls back to src/lib/dataUniversity" -- o arquivo nao
  existe; falhando, a lista fica vazia
- docs/manual-frontend.md, "always consult this guide" -- nao existe
- "There is no test runner configured yet" -- Vitest, 137 testes

- README.md (raiz): mapa de qual documento responde o que, os dois caminhos de
  acesso ao BFF (Server Component direto vs Route Handler), secoes de
  Pagamento e Pendencias Conhecidas.
- front/README.md: era o boilerplate do create-next-app. Agora e o guia
  operacional -- setup, scripts, estrutura, tres regras de arquitetura,
  tiers, testes e convencoes de nome.
- front/src/app/api/README.md: reescrito com as 18 rotas reais, o endpoint do
  BFF de cada uma, mapa de consumo e checklist para rota nova.
- front/src/test/K6/README.md: caminho dos scripts e separacao Vitest x k6.
- CLAUDE.md: seis afirmacoes que viraram falsas, mais as regras novas de
  seguranca (cliente nao monta Authorization, nenhuma rota devolve o JWT no
  corpo, nunca logar token) e a secao de Payment Chain.
- PAUTA.md -> docs/Pauta_para_reuniao.md, com o mapa de arquivos por rota de
  comunicacao e a tabela de desvios do fluxo de dez passos.
- CHANGES.md: historico detalhado das tres entregas.

Registradas como pendencia, nao corrigidas: POST /api/simulations/save-result
e POST /api/generate-explanation nao existem, POST /api/games/flash-cards
responde 405 (a rota exporta so GET), simulationStore.ts e LoadingContext.tsx
sem importador, e npm run lint quebrado desde o Next 16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corte de escopo conforme "Fase 1 - Lançamento Core (JAN 2027)": remove o que
restava de features das Fases 2 e 3, para que nada prometido chegue ao usuário
antes da hora.

Removido:
- app/api/ai/ (VestIA — chat e histórico), Fase 3
- components/PremiumFeatureModal.tsx, modal de upgrade do VestIA (órfão)
- components/mockups/CronogramaMockup.tsx, Planner de Estudos (órfão), Fase 2
- lib/data/playlists.ts, Playlist de Questões (órfão), Fases 2/5
- escopo calendar.events do provider Google em lib/core/auth.ts — existia só
  para o Planner; o login deixa de pedir acesso ao Google Calendar
- wrappers DndProvider/HTML5Backend em app/page.tsx e profile/ProfileClient.tsx
- 13 dependências sem consumidor: @google/genai, googleapis, date-holidays,
  react-dnd(+html5-backend), @fullcalendar/* (5), react-datetime-picker, docx,
  file-saver

Mantido de propósito: Explicação de Gabarito por IA (rota, Questoes_Gemini,
subjectKey, fiação no ProfileClient e @google/generative-ai), Discord, Stripe e
a criação de prova personalizada.

Copy da landing e do paidPlan reescrito: deixa de vender "agente de IA",
"plano personalizado" e "Recomendação IA" — features que não vão ao ar na
Fase 1.

Documentação:
- README.md da raiz reescrito de ponta a ponta contra o código atual. Corrige
  rotas e módulos inexistentes, o fluxo de sessão (JWT só no cookie HttpOnly),
  as variáveis de ambiente realmente lidas, e passa a registrar o estado atual
  e as dívidas conhecidas
- src/app/api/README.md: corrige a nota sobre /api/generate-explanation (a rota
  existe; falta a UI) e o escopo OAuth
- src/lib/MOCK_ARCHITECTURE.md: remove playlist, /api/ai e /api/planner

front/CLAUDE.md também foi reescrito como briefing e mapa de documentação, mas
não entra neste commit: o .gitignore ignora CLAUDE.md em qualquer nível.

Este commit também carrega trabalho em andamento que já estava na árvore e não
faz parte do corte: a reorganização de src/lib por domínio (core, data, games,
store — os arquivos movidos aparecem como rename), o gate de IP em proxy.ts, a
centralização de tests/ em front/ e os componentes novos de perfil e checkout.

Pendência conhecida: npm run build falha por @/lib/badges/*, que vive em outra
branch. Não foi introduzida aqui.
Duas entregas sobre o mesmo sintoma, nenhuma delas commitada até aqui:
401 permanente em GET /api/user/me e a aplicação inteira se comportando
como deslogada, sem nada na tela apontando a causa.

POST /api/sync-user — timeout explícito e diagnóstico

Timeout próprio de 8s (AbortSignal.timeout), abaixo dos 10s do undici,
para a falha ser nossa e nomeável em vez de um TypeError: fetch failed
genérico. O catch único virou classificação por causa, com as tags
[NET] 504, [BFF] relay, [SHAPE] 502 e [BUG] 500 no log do servidor —
na tela os quatro casos são idênticos. Fim do 200 silencioso: extrairJwt
aceita cinco grafias de resposta do Java, apara o prefixo Bearer, e o
cookie só é gravado se decodeJWT devolver payload válido. O corpo
devolvido ao navegador segue sanitizado nos quatro casos.

GET /api/user/me — fallback para a sessão do NextAuth

O cookie user_data só é gravado por /api/sync-user; quando essa rota
falha, o aluno tem sessão OAuth válida e mesmo assim aparece deslogado.
A rota passa a ler a sessão do NextAuth como segunda fonte.

Esse cookie chega fragmentado (next-auth.session-token.0/.1) porque o
callback jwt guarda o objeto account inteiro do Google e o valor passa
dos 4096 bytes que o navegador aceita. readNextAuthSessionToken() em
sessionToken.ts remonta os pedaços ordenando pelo sufixo numérico — não
alfabético, senão .10 cai depois de .1 — e reconhece também o nome
__Secure- usado em produção. O valor remontado é um JWE cifrado, não um
JWT: jwt-decode devolveria null, então quem o abre é o decode do
next-auth/jwt com a NEXTAUTH_SECRET.

No fallback o id é sempre null, nunca o sub do NextAuth: SyncUserEffect
só pula a sincronização quando claims.id é truthy, então preenchê-lo
faria o user_data nunca ser gravado. Novo header X-Claims-Source aponta
qual fonte respondeu.

Testes e documentação

21 specs em front/tests/sync-user.route.test.ts e 21 em
front/tests/user-me.route.test.ts, ambos passando. As specs de user/me
cifram de verdade com o encode do next-auth/jwt em vez de dublar o
decode. CHANGES.md, README.md da raiz e src/app/api/README.md
atualizados a partir do CHANGES.md.

Fica de fora, por decisão do usuário: a causa raiz do sync-user falhar.
Suspeitas em aberto — o endpoint chamado é ${BACKEND_API_URL}/auth/google
enquanto as rotas que funcionam usam ${BACKEND_API_URL}/api/...; o scope
do Google em lib/core/auth.ts não inclui openid; e refreshAccessToken
não renova o id_token guardado em googleAccount.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontend Error Error Sep 17, 2026 6:18pm UTC
vestibuline Ready Ready Preview Sep 17, 2026 6:18pm UTC

Grolla05 and others added 6 commits September 17, 2026 13:56
`next lint` foi removido no Next 16 e o FlatCompat quebrava com
"Converting circular structure to JSON". O script `lint` passa a ser
`eslint .` com eslint-config-next (core-web-vitals + typescript) nativo.

Com a config funcionando, 103 erros apareceram:
- 63 no-explicit-any: tipos locais para as respostas do BFF
  (CursoBackend, ArtigoBackend, QuestaoBruta/ProvaBruta, CourseResult,
  ComplexEnunciado); casts desnecessários removidos dos gateways
- 4 no-unescaped-entities, 1 ban-ts-comment
- React Compiler: purity (ids determinísticos em NotaCorteResultados),
  immutability (Audio em useRef no NavigationSound), static-components
  (ArenaGameClient), preserve-manual-memoization (deps do summary),
  error-boundaries (JSX fora do try/catch nos loaders)
- 29 set-state-in-effect rebaixados para warn: são efeitos de
  hidratação e refatorá-los pede teste visual (dívida no CHANGES.md)

Resultado: 0 erros, 106 avisos; tsc --noEmit limpo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rate-token

- generate-token: a rota lia o cookie com cookies() de next/headers, que
  lança fora do request scope e virava 500. Passa a usar readUserToken e
  decodeJWT (ponto único de decode). +2 specs: cookie user_data e JWT
  sem id/sub/email
- badge-cohesion e user-profile: scripts assert convertidos para
  describe/it, mesmas asserções
- materiaVisualIconBg: removido o bloco que lia Questoes_Gemini.tsx,
  apagado no commit 83575da

npm test: 14 arquivos, 107 testes passando.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- .github/workflows/ci.yml: em PR contra main roda npm ci
  --legacy-peer-deps, npm run lint e npm run test com Node 20
- README.md: versão enxuta; a documentação técnica passa a morar em
  projeto-EdTech/docs (architecture/frontend.md e adr/). A linha do
  script lint diz `eslint .`
- front/.env.example criado (o README manda copiá-lo) só com as chaves
  lidas pelo código; exceção no .gitignore
- CHANGES.md: entrada [Chore/ci]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
As duas branches divergiram desde ce69c32 (janeiro): 33 commits de um lado,
42 do outro, nenhum patch em comum. O merge mantém integralmente a árvore da
Versão-1.0, que é a linha mais recente (ago/set) e a que documenta o corte
para a Fase 1 em CHANGES.md e README.md.

Ficam de fora os 68 arquivos que só existem na main — ranking, VestIA,
planner, community, pricing e badges —, features de Fase 2 a 5 removidas de
propósito no commit f73044e. Elas continuam recuperáveis pelo histórico da
main quando a fase chegar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O LICENSE MIT entrou em 982f035, mas o repositório ainda se descrevia
como fechado:

- README: sai o aviso "Repositório PRIVADO — uso exclusivo da equipe";
  entra o aviso de projeto open-source sob MIT, com badges de CI e
  licença. A seção Licença deixa de dizer "código proprietário, todos os
  direitos reservados" e passa a MIT, ressalvando que a licença cobre o
  código — marca, identidade visual e o banco de questões das bancas
  ficam de fora
- README/Contribuindo: fluxo por fork para quem é de fora, e o workflow
  TDD descrito no próprio README, já que CLAUDE.md está no .gitignore e
  não chega a quem clona
- Footer: "Todos os direitos reservados" → "Projeto open-source sob
  licença MIT", com link para o LICENSE
- CHANGES.md: entrada [Chore/licença]

Varredura no histórico completo por .env versionado e por chaves Stripe,
Google, Mercado Pago, Slack e PEM: nenhum segredo real — só placeholders
em documentação.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DiscordTokenModal.test.tsx fazia Object.assign(navigator, …) no
beforeEach. O global navigator só existe a partir do Node 21: passava
local (Node 24) e quebrava no runner do CI (Node 20) com
"ReferenceError: navigator is not defined".

Troca por vi.stubGlobal('navigator', …), com vi.unstubAllGlobals() no
afterEach. Verificado rodando a suíte com o global removido: 14
arquivos, 107 testes passando.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Grolla05
Grolla05 merged commit 7cb10fc into main Sep 17, 2026
2 checks passed
Grolla05 added a commit that referenced this pull request Sep 17, 2026
Merge pull request #10 from projeto-EdTech/Versão-1.0

This branch was successfully deployed

1 active deployment
Production — 3ee81611 Deployed Sep 17, 2026 by vercel[bot]
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.

1 participant