268 lines
19 KiB
Markdown
268 lines
19 KiB
Markdown
# Design: endurecimento técnico do portfólio
|
|
|
|
**Status:** aprovado para planejamento; implementação não iniciada
|
|
**Data:** 2026-09-04
|
|
|
|
## Objetivo
|
|
|
|
Reduzir fragilidade operacional, JavaScript global e código sem consumidores sem redesenhar o portfólio. O resultado mantém a identidade preto/papel/verde ácido, a tipografia e a narrativa visual, enquanto torna projetos, foco, movimento, dependências, analytics, deploy e validação previsíveis.
|
|
|
|
## Estado de checkout que faz parte do alvo
|
|
|
|
A implementação futura trabalhará no checkout atual, sem worktree. Essa decisão é deliberada porque as alterações não commitadas abaixo são parte do estado-alvo e haverá um único editor; a pesquisa paralela é somente leitura:
|
|
|
|
- `data/project-health.json` modificado pelo usuário: não regenerar nem reverter nesta iniciativa.
|
|
- `lib/portfolio-data.ts` modificado pelo usuário: evoluir a partir do conteúdo atual, preservando a atualização apenas em desenvolvimento.
|
|
- `globals.css` na raiz, não rastreado: não editar, mover, importar ou apagar. O stylesheet ativo é `app/globals.css`.
|
|
- `lib/portfolio-data.load.test.ts` não rastreado: manter como baseline e atualizar suas expectativas somente quando o contrato de projetos mudar.
|
|
|
|
Não usar branch, commit, reset, checkout, stash ou comandos Git destrutivos. Rollback será por arquivo e por tarefa, usando os snapshots lidos antes de cada edição.
|
|
|
|
## Escopo e arquitetura atual observada
|
|
|
|
- Next.js App Router com React 19 e TypeScript estrito.
|
|
- `app/page.tsx` carrega projetos no servidor por `loadLiveFeaturedProjects()` e repassa dados para `CinematicHome`.
|
|
- `filterLiveProjects()` hoje transforma um snapshot ausente/expirado em lista vazia, ocultando todo projeto.
|
|
- `app/layout.tsx` envolve todas as rotas em Lenis/GSAP, contexto de animação, transição de rota e overlays; `PageIntro` cria um portal modal que cobre a página por tempo fixo.
|
|
- A home soma motion local, GSAP/ScrollTrigger redundante, cursores/overlays e loops contínuos de canvas.
|
|
- `GoogleAnalytics` carrega GA em produção sem consentimento e usa rewrites que camuflam script/coleta.
|
|
- `next.config.ts` marca diretórios públicos com nomes estáveis como `immutable` e mantém CSP somente em relatório com `unsafe-inline` e `unsafe-eval`.
|
|
- Não existe workflow em `.github/workflows`.
|
|
- `pnpm-lock.yaml` resolve Next `16.2.9`, contém overrides históricos e foi gerado com faixas, embora `package.json` não fixe o conjunto de dependências.
|
|
- `nixpacks.toml` já escolhe Node 22 e pnpm 9, mas instala com `--no-frozen-lockfile`.
|
|
- Há componentes e testes sem consumidores de produção, incluindo dois caminhos de contato, biblioteca de demos/magicui e infraestrutura de animação não consumida.
|
|
|
|
## Mapa de capacidades
|
|
|
|
| Módulo | Responsabilidade | Depende de |
|
|
| --- | --- | --- |
|
|
| Projetos e saúde de demo | Sempre fornecer conteúdo; anexar estado observacional do link | JSON estático, snapshot opcional |
|
|
| Shell acessível | Entregar conteúdo/foco sem overlay bloqueante e sem scroll engine global | layout, CSS global |
|
|
| Movimento e performance | Preservar efeitos essenciais, remover duplicação e pausar trabalho invisível | hooks locais, Intersection Observer, Page Visibility |
|
|
| Dependências e deploy | Fixar runtime/dependências e produzir lock reproduzível | código morto já removido |
|
|
| Analytics e headers | Consentimento opt-in, revogação e carregamento direto de GA | layout, `next/script`, configuração de headers |
|
|
| CI | Instalação congelada e gates determinísticos | lock final e scripts do pacote |
|
|
| Documentação operacional | Descrever apenas comandos e comportamento realmente presentes | implementação concluída |
|
|
|
|
Direção de dependência: conteúdo estático → projeção com saúde → home; shell/CSS → componentes; código vivo → manifesto de dependências → lock/deploy/CI. Nenhum componente de UI consulta rede para decidir se conteúdo existe.
|
|
|
|
## Decisões
|
|
|
|
### 1. Projeto é conteúdo; saúde é apenas estado do link
|
|
|
|
Substituir o contrato de “projetos vivos” por uma projeção que mantém todo projeto com `featured !== false` e anexa o estado da demo:
|
|
|
|
```ts
|
|
export type DemoAvailability = "available" | "unavailable" | "unknown" | "none";
|
|
|
|
export type PortfolioProjectView = PortfolioProject & {
|
|
demoAvailability: DemoAvailability;
|
|
demoCheckedAt: string | null;
|
|
};
|
|
```
|
|
|
|
Regras:
|
|
|
|
1. `featured === false` continua sendo a única regra de exclusão editorial.
|
|
2. Sem `demoUrl`, o estado é `none` e não há CTA de demo.
|
|
3. Sem entrada de snapshot, o estado é `unknown`; o card permanece e o URL configurado não é descartado.
|
|
4. Resposta final `200` com `Content-Type` HTML produz `available`.
|
|
5. Qualquer resultado explícito diferente produz `unavailable`.
|
|
6. `unavailable` renderiza a evidência inteira e um controle não navegável com texto “Demo indisponível na última verificação”.
|
|
7. `unknown` mantém o link e o anota como “Disponibilidade não verificada”.
|
|
8. Em desenvolvimento, snapshot ausente ou mais antigo que quinze minutos pode disparar uma atualização em memória; falha de rede retorna os projetos com o último estado conhecido/`unknown`.
|
|
9. Em produção, render nunca chama demos externas. O snapshot versionado é observacional e conteúdo não depende de cron, validade temporal ou rede.
|
|
|
|
`app/page.tsx` deixa de usar ISR de cinco minutos para simular monitoramento. `scripts/check-projects.mjs` continua como comando manual de atualização atômica, não como requisito de disponibilidade do site.
|
|
|
|
### 2. Acessibilidade não depende de animação
|
|
|
|
- Remover `PageIntro` e seu teste; não substituir por outro splash.
|
|
- Manter o skip link e garantir que nenhum overlay o cubra.
|
|
- Colocar o reset de cor de links na camada base do Tailwind, permitindo que `text-white`, `text-slate-*` e estados hover da 404 prevaleçam.
|
|
- Em `prefers-reduced-motion: reduce`, desativar smooth scroll e reduzir animações/transições CSS a uma atualização praticamente instantânea.
|
|
- Fallbacks estáticos permanecem com o mesmo conteúdo e ações essenciais.
|
|
|
|
### 3. Um efeito por intenção, não camadas redundantes
|
|
|
|
A assinatura visual preservada é: paleta, fontes, hero “command center”, globo, starfield, notebook 3D quando elegível, cartões, divisores e tipografia de marquee.
|
|
|
|
Serão removidos:
|
|
|
|
- Lenis e ticker GSAP do layout global.
|
|
- Transição de rota global.
|
|
- `AnimationProvider`, pois nenhum componente de produção consome seu contexto.
|
|
- `ReelDirector` e `CinematicSection`, que repetem revelações já feitas por componentes locais.
|
|
- Cursor customizado e seu `MutationObserver`, que reaplica listeners sobre a árvore.
|
|
- canvas de film grain; a textura passa a CSS estático.
|
|
- loops JS de marquee e divisores; tornam-se animações CSS/SVG sem estado React por frame.
|
|
|
|
GSAP/ScrollTrigger permanece somente na história do notebook, onde controla uma timeline scroll-driven real. Motion permanece nos componentes que já implementam feedback/reveal local. Nenhuma biblioteca nova será adicionada.
|
|
|
|
Loops restantes obedecem aos dois sinais:
|
|
|
|
```ts
|
|
const shouldRun = isIntersecting && !document.hidden && !shouldReduceMotion;
|
|
```
|
|
|
|
`HeroStarfield` já segue esse contrato e será preservado. `NotebookStory` combinará interseção e `visibilitychange`; o globo manterá flags separadas para interseção e visibilidade do documento para não reativar fora da viewport.
|
|
|
|
### 4. Hero terá somente a variante ativa
|
|
|
|
`Hero` mantém exclusivamente “command-center”. Remover pesos, seleção aleatória, cinco blocos editoriais não alcançáveis, layouts alternativos e branches de `SideVisual`. `HeroScene` perde o parâmetro `variant`, hoje ignorado. O conteúdo visível, CTAs, globo, telemetria, links sociais e fallback de reduced motion permanecem.
|
|
|
|
### 5. Remoção de código morto é um corte limpo
|
|
|
|
Excluir, após confirmar todos os callers na execução:
|
|
|
|
- `ContactForm`, `ContactInfo` e `usePortfolioData`; `Contact` é o caminho ativo.
|
|
- `CompanyLogos` e seus três arquivos de teste.
|
|
- todos os demos na raiz de `components/` e `components/magicui/`, sem importadores.
|
|
- `AnimationProvider`, seu teste, `app/lib/animations/config.ts`, `app/lib/animations/variants.ts` e `app/hooks/useScrollReveal.ts`.
|
|
- `FloatingElements`, `HomeButton`, `HorizontalScroll` e `CinematicText`, sem importadores.
|
|
- componentes de performance substituídos/removidos na decisão 3 e seus testes de implementação órfãos.
|
|
|
|
Não manter aliases, reexports ou shims.
|
|
|
|
### 6. Testes protegem comportamento observável
|
|
|
|
- Reescrever os testes de saúde para provar que todos os projetos editoriais permanecem e apenas o estado da demo muda.
|
|
- Preservar um teste de desenvolvimento que prova atualização em memória sem permitir fetch em produção.
|
|
- Cobrir o CTA disponível, indisponível e desconhecido de `Projects` por nome/estado acessível.
|
|
- Remover PBTs de `CompanyLogos` junto do componente.
|
|
- Remover PBTs do Footer que repetem um componente estático; `Footer.test.tsx` já cobre nomes acessíveis e segurança de links.
|
|
- Substituir três PBTs de `Experience` por testes determinísticos de filtro editorial e ordem cronológica; hierarquia de heading não precisa de geração aleatória.
|
|
- Remover a asserção que fixa classes internas do Footer e a asserção da hero que inspeciona props serializadas de motion.
|
|
- Remover `fast-check` quando seu último consumidor desaparecer.
|
|
|
|
### 7. Runtime e dependências serão reproduzíveis
|
|
|
|
- `engines.node`: `22.x`.
|
|
- `packageManager`: `[email protected]`.
|
|
- Next.js: `16.3.0`, menor versão estável da mesma major que eliminou as três vulnerabilidades high mantidas por `16.2.11` e `16.2.12`.
|
|
- Todas as dependências diretas e de desenvolvimento terão versão exata, sem `^`, `~` ou comparadores.
|
|
- `@types/node` acompanha o runtime em `22.20.1`.
|
|
- `@types/three` passa para `devDependencies`.
|
|
- Remover pacotes cujo último consumidor foi apagado: `@gsap/react`, `class-variance-authority`, `lenis`, `ogl`, `tailwindcss-animate`, `tw-animate-css`, `fast-check`, `@types/resize-observer-browser` e `resize-observer-polyfill`.
|
|
- Manter GSAP, cobe, Motion, React Three Fiber, Drei e Three porque ainda há consumidores ativos.
|
|
- Regenerar `pnpm-lock.yaml` com pnpm 9.15.9 e sem bloco `overrides`.
|
|
- Corrigir advisories atualizando o pacote direto proprietário da cadeia; não usar `audit --force`, exceções ou novos overrides.
|
|
- Nixpacks usa `pnpm install --frozen-lockfile`.
|
|
|
|
### 8. CI terá um caminho único
|
|
|
|
Criar `.github/workflows/ci.yml` para `push` e `pull_request`, em Ubuntu, Node 22 e pnpm 9.15.9. Ordem:
|
|
|
|
1. checkout;
|
|
2. instalação congelada com cache do pnpm;
|
|
3. `pnpm lint`;
|
|
4. `pnpm typecheck`;
|
|
5. `pnpm test -- --runInBand`;
|
|
6. `pnpm build`;
|
|
7. `pnpm audit --prod --audit-level=high`;
|
|
8. `pnpm audit --dev --audit-level=high`.
|
|
|
|
Não adicionar Playwright, Cypress ou outro browser ao projeto. O smoke visual/interativo usa o Chromium disponível no harness durante a implementação; CI fica restrita aos gates aprovados.
|
|
|
|
### 9. Analytics exige opt-in e permite revogação
|
|
|
|
Aplicar consentimento a todos os visitantes, sem geolocalização de IP: é menor, previsível e não arrisca carregar GA antes de uma decisão quando a obrigação legal se aplica.
|
|
|
|
- `GoogleAnalytics` começa sem `<Script>` e sem requisição a Google.
|
|
- Persistir somente `"granted"` ou `"denied"` em `localStorage` sob uma chave versionada.
|
|
- Exibir ações “Aceitar analytics” e “Recusar” quando não há escolha.
|
|
- Manter um controle “Preferências de privacidade” acessível após a escolha.
|
|
- Somente `granted` monta `https://www.googletagmanager.com/gtag/js?id=G-QTT9QT0T3S` e a configuração de GA.
|
|
- Consentimento de publicidade permanece negado; somente `analytics_storage` pode ser concedido.
|
|
- Revogação chama `gtag("consent", "update", ...)` com estados negados, define `window["ga-disable-G-QTT9QT0T3S"] = true`, expira cookies `_ga` e `_ga_*` visíveis no domínio atual e desmonta o loader.
|
|
- Remover todos os rewrites de analytics/ad-block bypass e usar endpoints oficiais diretamente.
|
|
|
|
A ordem segue a orientação oficial do Google: default negado antes de coleta, update após escolha e persistência/revogação controladas pela aplicação.
|
|
|
|
### 10. CSP e cache mudam somente quando compatíveis
|
|
|
|
Não introduzir nonces nesta rodada. A documentação do Next informa que nonces exigem renderização dinâmica, desabilitam otimização estática/ISR e alteram cache/custo; isso conflita com a home estática e seria endurecimento desproporcional.
|
|
|
|
- Manter CSP em `Report-Only`.
|
|
- Adicionar `default-src 'self'` e `form-action 'self'`.
|
|
- Remover `unsafe-eval` em produção; mantê-lo somente em desenvolvimento, onde o Next documenta a necessidade para debugging.
|
|
- Manter `unsafe-inline` por enquanto para compatibilidade observada com scripts/estilos do Next e JSON-LD inline.
|
|
- Manter origens Google apenas porque GA opt-in ainda as usa.
|
|
- Remover regras `immutable` para `/static`, `/fonts`, `/assets` e `/logos`; esses nomes públicos não têm hash garantido. Assets gerados pelo Next em `/_next/static` já recebem cache apropriado do framework.
|
|
|
|
### 11. Documentação será derivada do estado final
|
|
|
|
- README troca badge Next 15 por 16.3.0, remove Lenis e bibliotecas apagadas, documenta Node 22/pnpm 9.15.9, scripts reais, arquitetura e validação.
|
|
- `docs/project-health-cron.md` será substituído por `docs/project-health.md`, descrevendo snapshot opcional, atualização manual/desenvolvimento e a garantia de cards sempre visíveis.
|
|
- Deployment documentado no README usa somente `nixpacks.toml`: Node 22, pnpm 9 e lock congelado.
|
|
- Nenhum dado comercial, cliente, métrica, review, endereço ou claim novo será criado.
|
|
|
|
## Não-objetivos
|
|
|
|
- Redesenhar hero, paleta, tipografia, navegação ou arquitetura de informação.
|
|
- Remover notebook 3D, React Three Fiber, Drei, Three, cobe, Motion ou GSAP usado pelo notebook.
|
|
- Criar backend, banco, CMP externo ou coleta analytics própria.
|
|
- Migrar para outro gerenciador de pacotes ou outra plataforma de deploy.
|
|
- Alterar conteúdo, metadata, JSON-LD, sitemap ou robots por pesquisa SEO/GEO externa. Essa integração terá spec e plano separados.
|
|
- Atualizar o snapshot do usuário em `data/project-health.json`.
|
|
- Importar ou consolidar o `globals.css` não rastreado da raiz.
|
|
- Endurecer CSP com nonce/hash sem uma prova separada de compatibilidade e impacto de renderização.
|
|
|
|
## Riscos e mitigação
|
|
|
|
| Risco | Impacto | Mitigação |
|
|
| --- | --- | --- |
|
|
| Saúde antiga desabilita uma demo já recuperada | Médio | Exibir timestamp/estado como última verificação e manter atualização manual/dev; nunca ocultar card |
|
|
| Alterar o tipo de projeto quebra notebook/home | Alto | Introduzir tipo por interseção compatível, migrar callers no mesmo checkpoint e rodar testes focados |
|
|
| Remoção de wrappers muda composição/hidratação | Alto | Remover um wrapper por corte, verificar foco, rotas e console no browser após o shell |
|
|
| Remoção de efeitos achata identidade | Médio | Preservar hero command center, globo, starfield, notebook, Motion local e CSS visual; comparar desktop/mobile |
|
|
| CSS reduced motion excessivamente amplo oculta conteúdo | Alto | Testar com emulação reduce; regras mudam duração, não `display`/`opacity` final |
|
|
| Revogação não interrompe GA já carregado | Alto | Atualizar consent mode para denied, ativar flag `ga-disable`, expirar cookies e testar ausência de novas requisições após revoke |
|
|
| CSP passa a bloquear runtime/GA | Alto | Permanecer Report-Only e remover apenas `unsafe-eval` em produção; observar console/network antes de enforcement futuro |
|
|
| Lock regenerado reintroduz advisory | Alto | Auditar prod/dev após frozen install e atualizar cadeia proprietária sem overrides/force |
|
|
| Exclusão alcança arquivo ainda importado | Alto | Buscar referências antes de cada lote e exigir `tsc` no checkpoint de corte |
|
|
| Alterações do usuário são sobrescritas | Alto | Tratar quatro arquivos listados como baseline; não tocar nos dois fora de escopo e usar edições ancoradas nos dois em escopo |
|
|
|
|
## Rollback
|
|
|
|
Rollback é incremental, sem Git destrutivo:
|
|
|
|
1. Antes de cada tarefa, ler e registrar os arquivos exatos daquele lote.
|
|
2. Se o checkpoint falhar, interromper o lote seguinte.
|
|
3. Restaurar somente os arquivos do lote com edições inversas baseadas no snapshot pré-tarefa.
|
|
4. Para arquivos novos, removê-los somente se foram criados pelo lote e ainda não são consumidos.
|
|
5. Para dependências, restaurar juntos `package.json`, `pnpm-lock.yaml`, `tailwind.config.js` e `nixpacks.toml`; nunca restaurar apenas o lock.
|
|
6. Reexecutar o último checkpoint verde.
|
|
7. Nunca incluir `data/project-health.json`, `globals.css` da raiz ou mudanças não relacionadas no rollback.
|
|
|
|
## Critérios observáveis de aceite
|
|
|
|
1. Home e `/cases` continuam exibindo cards independentemente de snapshot ausente, antigo ou com falha.
|
|
2. Demo explicitamente indisponível não navega e tem estado acessível; estado desconhecido não remove o URL nem o card.
|
|
3. Produção não faz fetch de demos durante render; desenvolvimento pode atualizar em memória e falha de rede não esvazia conteúdo.
|
|
4. Skip link recebe foco visível imediatamente, sem splash sobreposto.
|
|
5. Links da 404 exibem as cores Tailwind pretendidas e foco visível.
|
|
6. Reduced motion mostra todo conteúdo essencial sem scroll suave, loops ou transições longas.
|
|
7. Loops restantes deixam de atualizar quando seção está fora da viewport ou `document.hidden` é verdadeiro.
|
|
8. Hero mostra o mesmo conteúdo command-center sem infraestrutura de variantes mortas.
|
|
9. Não há imports para os arquivos excluídos e TypeScript resolve o workspace sem erros.
|
|
10. `package.json` contém versões exatas, Node `22.x`, pnpm `9.15.9` e Next `16.3.0`; lock não contém `overrides`.
|
|
11. Instalação Nixpacks e local usa frozen lockfile com pnpm 9.
|
|
12. `pnpm audit --prod --audit-level=high` e `pnpm audit --dev --audit-level=high` encerram com código zero, sem force, ignore ou override.
|
|
13. Antes de consentimento/rejeição não há request a `googletagmanager.com`/`google-analytics.com` nem cookie `_ga*` criado pela página.
|
|
14. Aceite carrega GA diretamente; revogação nega consentimento, desabilita coleta futura e expira cookies acessíveis.
|
|
15. Configuração não contém rewrites de analytics; CSP continua Report-Only compatível e cache immutable não cobre arquivos públicos sem hash.
|
|
16. CI executa install congelado, Biome, tsc, Jest, build e auditorias prod/dev.
|
|
17. README e documentação de project health descrevem comandos, versões e comportamento finais sem depender de cron.
|
|
18. `globals.css` da raiz e `data/project-health.json` permanecem inalterados.
|
|
|
|
## Fontes técnicas
|
|
|
|
- Next.js, CSP: https://nextjs.org/docs/app/guides/content-security-policy
|
|
- Next.js, `next/script`: https://nextjs.org/docs/app/api-reference/components/script
|
|
- Google, consent mode: https://developers.google.com/tag-platform/security/guides/consent
|
|
- Google, API `gtag`: https://developers.google.com/tag-platform/gtagjs/reference#consent
|
|
- Nixpacks, provider Node: https://nixpacks.com/docs/providers/node
|
|
- pnpm, audit: https://pnpm.io/cli/audit
|
|
- pnpm Action para pnpm 10 e anteriores: https://github.com/pnpm/action-setup
|