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.