dotfiles
Repo

The README: the header, the stats and the diagrams

Edit on GitHub

The README is the one page a visitor reads without going to the site, so it carries a header built to look like my profile README: an animated SVG, a row of badges, and a nix block describing the repo the way the profile describes me.

The stats are counted at BUILD, never committed

tools/repo-stats/ runs scc over the flake's own source and draws the badges, the stats card and a stats.json from the result. The facts only Nix knows (the nixpkgs release in the lock, the number of inputs, the enabled hooks, the commit's own date) enter through flake.nix. .#pages joins that output with docs-site under /stats, and .github/workflows/docs.yml publishes it, so the README points at dotfiles.v1cferr.dev/stats/.

Three ways were compared on 29/09/2026, and this one won on what lasts:

OptionWhy it lost or won
counted at build, served by the siteWON. A commit and its stats cannot disagree, the history gets no bot commits, and nothing is drawn by a third party
a workflow that commits the SVGwhat the profile does, and cheap; but every change means a bot commit in the diary (rule 17) and a CI token that can write
shields.io reading a JSON endpointthe familiar look, but a third party renders it, and it does not look like the profile

The evaluation cost stays OUT of the card. It needs nix eval of a host, which a build sandbox cannot run, so it lives where it already did: the gate's run summary, from eval-metrics.

The cost of the choice: docs.yml used to fire only on a push touching the site, and now fires on every push, since a commit to modules/nixos/ changes the stats. That job keeps no store cache on purpose (a publishing job restoring a cache is zizmor's cache-poisoning finding), so each push rebuilds the site on the runner. The machine time is the trade for numbers that are never stale.

GitHub caches README images through its camo proxy, so a fresh number can take a while to show on the README even after the site has it. The source of truth is stats/stats.json on the site.

The two diagrams, and why two

  • .github/assets/flow.svg, hand-drawn and animated with SMIL, which GitHub renders inside an <img> (scripts are stripped, SMIL and CSS animation are not). It shows the FLOW (commit, gate, build, switch, and the canary closing the loop), which changes rarely, and it carries no number on purpose: a count drawn by hand is a count that drifts (rule 16). Its panel is dark in both GitHub themes, like the profile's banner, since prefers-color-scheme inside an SVG image is not reliable there.
  • The Mermaid block inside a <details>, for the STRUCTURE (which directory feeds which). GitHub renders it natively and themes it, and it is text: it shows up in a diff and gets edited with the tree it describes. GitHub strips Mermaid's click links, so it links nowhere.

The HTML, and markdownlint

GitHub Markdown has no syntax for a badge row or a collapsible, so the header is HTML. .config/markdownlint.jsonc allows exactly the tags the README uses (MD033's allowed_elements), and the image before the first heading is excused on its own line (MD041), so neither rule is off for the rest of docs/.

On this page