
Portfolio — the proxy that closed the door and still saved the form
How a custom domain missing from Capivara's allowed origins killed the contact form, and the same-origin proxy that brought tracking back.
21 posts

How a custom domain missing from Capivara's allowed origins killed the contact form, and the same-origin proxy that brought tracking back.

The portfolio test harness measured contrast on a port. When the address came from an environment variable, the branch server never started, and something else answered there. The test never lied. It just answered a different question.

My portfolio's next feature isn't live yet: an AI-powered generator that rewrites my résumé around a job description. Before it becomes a button, the hard part was already solved — and it isn't generating text. It's keeping the AI from describing experiences that never happened.

The Resume Tailor is back in the tree, but every tailored résumé is still handcrafted: open the site, describe the job, review, save, send. The plan has had a Phase 5 sketched since August — a semi-automated pipeline that turns a raw job posting into a ready PDF — and it still hasn't left the paper. On the difference between shipping a tool and shipping a habit.

Nine libraries, a test runner major bump and a GitHub action updated in a single day. What it took to merge 11 dependabot pull requests on the Portfolio without breaking the suite — and the one that almost turned into a false alarm.

React hydration error #418 in the portfolio footer, a mystery that only happened at night: the server renders in UTC, the visitor in local time, and a new Date() in JSX gave each of them a different answer. The hunt for the useState(null), the pinned timeZone, and the epilogue where I myself created a second bug while fixing the first.

The portfolio hero typed, erased, and retyped — not as an effect, but as a bug: deferred hydration mounted the component and reset the text already on screen. The story of how LCP dropped from 3.6s to 2.7s by removing idle-hydration from the wrong place.

Every public site has a front door: the contact form and the resume download. Two endpoints that accept input from strangers on the internet — and I finally treated them as such. Rate limiting, HTML escaping, a contact file for people who find flaws, and a scanner that watches everything in silence.

When the theme toggle animation revealed the old theme instead of the new one, the fault was not in the CSS — React had not committed the state in time.

A wrong Cache-Control header in vercel.json was overriding the revalidate=1800 ISR setting and turning every cache expiry into two synchronous GitHub calls inside the TTFB. The story of how cold cache became warm: 3.6s down to 140ms.

The portfolio's projects section almost got a category filter. It didn't — and that was the best decision. The story of a 2-column grid that resisted the lure of complexity.

pnpm audit on the Portfólio screamed 61 vulnerabilities. Instead of updating dependency by dependency or trusting audit fix, I solved it with 5 lines of pnpm.overrides in package.json. Result: 61 → 2, with the remaining 2 having no published fix at all.

Portfolio was 100% functional, 218 tests passing, zero CI errors. But mobile Chrome users saw old versions when restoring tabs. The cause: Chrome's bfcache ignores HTTP headers — and the fix came cross-project from LifeLog.

Nobody touched the code, yet Vercel kept rejecting the build: ERR_PNPM_EEXIST. The cause: a security cron ran pnpm audit fix with the Windows pnpm, regenerated a v10 lockfile while packageManager declared 9.12.3 — the saga of aligning local pnpm, packageManager and CI.

529 commits, 218 tests, 5 mini-games, multi-layer parallax scene, interactive terminal with 15+ commands, full PT/EN i18n — the journey of a portfolio that became an interactive showcase.

Broken Pix BR Code, focus trap stealing keyboard input, scroll jumping sections — a deep audit fixed everything at once.

I added Pix to my portfolio with BR Code generation. Only the QR Code didn't work in any banking app. The culprit: String.fromCharCode generating control characters instead of decimal digits in EMV 2022 length fields.

July 2026 was the month my portfolio stopped getting new features and started fixing the ones it already had. A deep audit revealed 10 problems — from Pix to focus trap — and by the end, deployment was more automated than ever.

Exploring the portfolio games — lessons learned, challenges and discoveries.

After building tools for others, I decided to dedicate time to my own space. A portfolio that is more than a resume.

Exploring the portfolio rebuild — lessons learned, challenges and discoveries.