← all work

This site: a portfolio that operates itself

A static site whose content and live status diagram are kept current by its own pipeline, and which only shows claims a repository can back.

Role
Design, build and operations
Period
Oct 2026
Status
Public source
  • Astro
  • Cloudflare Pages
  • GitHub Actions
  • GitHub API
  • dev.to API
  • Playwright
Problem
A portfolio goes stale the week it is published, and most of what portfolios claim cannot be checked.
Key decision
Make keeping it current the pipeline's job, show the pipeline on the home page, and publish nothing a repository cannot back.
Result
Nightly rebuilds from the GitHub and dev.to APIs, a live status diagram measured from the visitor's browser, and an agent that can only open pull requests.

Architecture

How this site is built and servedCICase-study agentscheduled · PR-gatedEXTGitHub API423 contributions / yrEXTdev.to API6 recent articlesSVCportfolio repomain @ 18436cdCIDeploy workflowrun #5 · successEDGECloudflare Pageschecking…CLIYouthis browserHow this site is built and servedCICase-study agentscheduled · PR-gatedEXTGitHub API423 contributions / yrEXTdev.to API6 recent articlesSVCportfolio repomain @ 18436cdCIDeploy workflowrun #5 · successEDGECloudflare Pageschecking…CLIYouthis browser
Pushes and a nightly schedule run the deploy workflow, which pulls from the GitHub and dev.to APIs and ships to Cloudflare Pages. The case-study agent can only propose changes through a reviewed pull request. Hover, tap or tab through the components; control flows are dashed.
Components and flows as text
ComponentKindTechnologyFlows out
Case-study agentpipelinescheduled · PR-gated⇢ portfolio repo: pull request, merged only after review
GitHub APIexternal service423 contributions / yr→ Deploy workflow: repos, upstream PRs, contributions
dev.to APIexternal service6 recent articles→ Deploy workflow: articles
portfolio reposervicemain @ 18436cd⇢ Deploy workflow: push to main
Deploy workflowpipelinerun #5 · success⇢ Cloudflare Pages: deploy build + status.json
Cloudflare Pagesedgechecking…→ You: pages
Youclientthis browser→ Cloudflare Pages: live status check

How a build works

  1. A push to main, the nightly schedule or a manual run starts the deploy workflow.
  2. scripts/fetch-data.mjs pulls repositories, upstream pull requests, the contribution total and articles. Each source is wrapped so that a failure is recorded rather than thrown:
async function attempt(source, fn, fallback) {
  try {
    return await fn();
  } catch (err) {
    errors.push({ source, message: String(err.message ?? err) });
    return fallback;
  }
}
  1. Astro type-checks and builds static HTML. Diagrams are laid out at build time and emitted as SVG, so no layout code runs in the browser.
  2. The build writes /status.json, the same data the home-page diagram is drawn from, and deploys to Cloudflare Pages.
  3. In the browser, one small script measures a round trip to /status.json, marks the edge node, and reports whether a newer build is live.

Verification

  • astro check type-checks every build.
  • CI fails if the output contains inline scripts, inline styles or data: URIs that the CSP would block.
  • Links, anchors and layouts at 375, 768 and 1440 pixels are checked with Playwright before changes ship.

Decisions

  1. D-01accepted

    Every source is optional; failures are shown, not hidden

    The build fetches each source inside a wrapper that records failures instead of throwing. A broken API produces a warning on the diagram, not a broken site.

    Options considered

    • Fail the build when an API fails: the site stops updating whenever GitHub has a bad hour.
    • Ship with the last good data and say so.

    Trade-off accepted

    A build can ship with a partial view; the status diagram makes that visible.

    scripts/fetch-data.mjs ↗

  2. D-02accepted

    Static, rebuilt nightly

    No server and no runtime API calls. GitHub Actions rebuilds on every push to main and every night, and deploys to Cloudflare Pages.

    Options considered

    • Server-side rendering with live API calls: always fresh, and a server to run and rate limits to manage.
    • Static output on a schedule.

    Trade-off accepted

    Content can be up to a day old; the footer and the diagram say exactly how old.

    .github/workflows/deploy.yml ↗

  3. D-03accepted

    A strict CSP with nothing inline

    script-src and style-src are both self. All JavaScript is in external files, stylesheets are never inlined, assets are never inlined as data: URIs, and diagrams are SVG with no style attributes. CI fails the build if anything inline appears.

    Options considered

    • Allow unsafe-inline: easier, and it removes most of what a CSP is for.
    • Make the build produce nothing inline, and test for it.

    Trade-off accepted

    Some framework conveniences are off, and code highlighting is plain.

    public/_headers ↗ · .github/workflows/ci.yml ↗

  4. D-04accepted

    The agent proposes; review commits

    The repository includes a scheduled workflow that runs an AI agent against my public repositories. When one is new or has changed significantly, it drafts a case study and opens a pull request labelled case-study-draft, listing the files it relied on. It has no path to main except a reviewed merge.

    Options considered

    • Let the agent commit to main: always current, and unreviewed claims in public.
    • Pull requests only.

    Trade-off accepted

    Content waits for review.

    .github/workflows/case-study-agent.yml ↗

  5. D-05accepted

    Only evidence-backed content

    Repositories appear only if an audit marked them worth showing, and every claim in a case study traces to a file, test or pull request.

    Options considered

    • List every public repository automatically.
    • Curate against an evidence ledger.

    Trade-off accepted

    Less on the page; everything on it holds up.

    src/data/showcase-repos.json ↗

Failure modes

FailureDetectionHandlingEvidence
GitHub or dev.to API downFetch wrapper records the errorBuild ships; node shows a warningfetch-data.mjs
Inline script or style slips inCI grep on the build outputBuild fails before deployci.yml
Agent drafts an unsupported claimPR review against cited filesNot mergedcase-study-agent.yml
Visitor has a stale pageLive status.json SHA differsStatus line says a newer build is livecontrol-plane.js

What I would change next

  • Add an uptime monitor to the status diagram once the site is on its own domain.
  • Run the link and layout checks as a Playwright job in CI.

Evidence index

Links point to the public repository.