Skip to content
Nicholas Pinto
← All projects

This site

A portfolio built ADR-first — the repo's decisions, gates, and content boundary are as much the product as the pages.

Problem

A portfolio has two audiences with opposed needs: a recruiter who wants a clear impression in about 60 seconds, and a senior engineer or interviewer who wants to stay and dig for ten minutes or more. Designing for both at once usually produces a site that serves neither — and a self-referential "portfolio about building a portfolio" reads as thin unless the engineering underneath it is real enough to survive scrutiny.

Approach

Scope and audience were locked before any code was written. A product brief fixed two personas — the Scanner and the Digger — and froze the v1 feature list against scope creep. From there, five ADRs (framework/hosting, content architecture, URL structure, accessibility conformance, testing strategy) were written, reviewed, and accepted before Phase 2's implementation began, so that structural decisions were argued from a stated rationale rather than decided ad hoc while under deadline pressure.

Key decision

The content architecture boundary (ADR-0002): all content lives in content/ as MDX and typed modules, validated by Zod schemas, and is read by application code through exactly one module (lib/content/) — enforced by an ESLint rule, not convention. This exists because Phase 7 commits to introducing a database for some or all of that content; without the boundary, that migration becomes a rewrite of every page that ever imported content directly.

Alternatives considered and rejected:

  • A database from day one — adds a runtime dependency and a failure mode during recruiting season for zero v1 benefit. The point of adding one later is to do it deliberately, on an already-stable product, not under a launch deadline.
  • No boundary — components read content files directly — the path of least resistance, and the one this ADR exists to prevent: migrating content later would mean editing every file that ever imported it, with no single point of change and no way to verify completeness.
  • A generic, CMS-agnostic content abstraction — rejected as premature. Designing an interface generic enough to support an unknown future backend means guessing at that backend's shape today; the narrower boundary makes a more honest promise without pre-committing to what a future module's internals must support.

Outcome

CI (.github/workflows/ci.yml) runs lint, typecheck, unit tests, end-to-end tests, and accessibility checks on every push and blocks merge on failure. The test suite follows an inverted pyramid (ADR-0005), weighted toward this site's actual failure modes — broken routes, broken links, accessibility regressions — rather than unit-testing presentational components that have no real logic to break. Accessibility targets WCAG 2.2 AA, enforced by automated axe checks in CI plus a manual keyboard/screen-reader pass. The repository itself — its ADRs, its CI configuration, its content boundary — is the evidence for the Digger persona this page exists to serve.

What I'd do differently

Several accepted ADRs cite an "Engineering Foundation Plan" — a decision-type framework, a continuity principle, a timeline for extracting a design system — by section number. That plan was worked out in conversation during planning but was never actually committed to the repository as a file, so those citations are unverifiable today; it's an open, documented gap (see .claude/CLAUDE.md). The lesson: commit a decision record the moment it's made, not after — a plan that only exists in chat history isn't actually part of the project, no matter how load-bearing the decisions inside it turned out to be.