
Portfolio — a auditoria que virou faxina de julho
O último post terminava com promessa: “em julho, o portfólio ganharia integração com Stripe + Mercado Pago”. A vida de dev tem dessas — o que planejei foi empurrado pra agosto, e julho virou o mês da faxina.
O Pix que não passava
Tudo começou com um bug silencioso. O botão de doação Pix gerava um código BR Code, mas os bancos recusavam. O payload EMV 2022 simplesmente não validava.
Depois de horas debugando, encontrei a causa: String.fromCharCode. Eu estava usando esse método para converter o comprimento dos campos em caracteres — exatamente o que o spec EMV pede, certo? Errado. String.fromCharCode produz codepoints Unicode, não dígitos decimais. Um campo de length 14 virava \x0E (shift out), não "14".
// ❌ ANTES — String.fromCharCode produz codepoints de controle
const keyField = `01${String.fromCharCode(key.length)}${key}`;
const maiLen = String.fromCharCode(mai.length);
// ✅ DEPOIS — dígitos decimais com padding, como o spec EMV realmente espera
const keyLen = String(key.length).padStart(2, "0");
const keyField = `01${keyLen}${key}`;
const maiLen = String(mai.length).padStart(2, "0");
Tinha também um slice(0, -4) que removia o placeholder 6304 do CRC — só que 6304 é o CRC inicial, não um placeholder. Removi o slice e o CRC passou a ser calculado sobre o payload completo.
Dois componentes quebrados (SupportButton.tsx e PixDonate.tsx), um fix de 26 linhas, várias horas perdidas. Mas o Pix finalmente funcionou.
Focus trap: o pesadelo de UX
O modal de currículo tinha um problema sutil: ao digitar qualquer campo, o foco voltava pro botão que abriu o modal. Cada tecla — click — de volta pro trigger.
A root cause estava no useFocusTrap. O onClose era passado como dependência do useEffect, e como ele era uma arrow function recriada a cada render, o efeito executava cleanup (que roubava o foco) e setup (que focava o primeiro elemento) a cada caractere digitado.
// ❌ ANTES — onClose na dep array recria o efeito a cada render
useEffect(() => {
// setup: foca primeiro elemento
// cleanup: rouba foco de volta pro trigger
return () => { triggerRef.current?.focus(); };
}, [active, onClose, containerRef]);
// ✅ DEPOIS — ref evita stale closure sem recriar o efeito
const onCloseRef = useRef(onClose);
onCloseRef.current = onClose;
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
onCloseRef.current();
}
};
// ...
}, [active, containerRef]); // onClose removida intencionalmente
O padrão useRef pra callback é algo que eu conhecia, mas que escapou na pressa de entregar o redesign. Serviu de lembrete: toda função passada como prop pra um hook de efeito merece uma ref.
A auditoria que virou faxina
No dia 21 de julho, sentei pra fazer uma auditoria de qualidade. O resultado foi uma lista de 10 problemas:
- CV download: usava
fs.appendFileSync— quebra no Vercel (serverless = sem filesystem persistente). Migrei pra Capivara API. - Terminal:
commandHistoryRefsem limite de crescimento — 100 entradas máx. Tracking corrigido (external_link→terminal_command). - Analytics:
window.umamitipado comoany— type augmentation limpo. - ErrorBoundary: erros silenciosos → agora reporta via
/api/contact-notify. - ContactForm: clipboard com fallback
execCommandpra HTTPS. Rate limit com contagem regressiva visível. - AppWrapper: dead code — só renderizava
children. Removido. - Layout: verification tags placeholder removidas.
- Navbar: ícone games vazio → 🎮.
- Loading skeleton: visual consistency melhorada.
- Pix: o bug que começou tudo.
Build: 0 errors, 0 warnings. Testes: 218 pass.
Deploy automático: o verdadeiro ganho
No meio da faxina, uma feature que realmente ficou pronta: o deploy automático via CI. Desde 12 de julho o workflow deploy.yml já existia, mas faltava o health check e rollback.
O pipeline hoje:
- Push na master
- GitHub Actions instala pnpm, builda, testa
- Deploy na Vercel
- Health check automático (verifica homepage + 404 + assets)
- Se health check falhar → rollback pro último deploy estável
Foram 29.241 linhas de package-lock.json varridas do repositório (projeto usa pnpm, não npm). O repositório encolheu de tamanho, e o CI passou a rodar em segundos.
O que aprendi
Julho foi o mês do “feito é melhor que perfeito, mas feito exige manutenção”. O Stripe + Mercado Pago continuam na lista — mas o portfólio está mais sólido do que nunca. 218 testes, 0 erros de build, deploy automático, e um Pix que finalmente funciona.