3 Commits
Author SHA1 Message Date
istos 3bc7e6ec7e site: let the analytics beacon through img-src
Fathom sends its pageview as an image request. script-src named the
origin and img-src did not, so the script loaded and the one thing it
loads to do was blocked — visible only in the browser console, with a
200 on every response and no pageview at the other end.

The console said it plainly:

  Loading the image 'https://cdn.usefathom.com/?h=...&sid=ZPKDEHCV...'
  violates the following Content-Security-Policy directive: img-src
  'self'

A test now asserts the origin appears under all three directives it
actually uses, because nothing on this side of the wire can tell that it
does not.
2026-07-31 16:15:50 +02:00
istos 662ecc001f site: add Fathom analytics, and fix the caching rule it sits beside
Two changes to what the live site sends.

Analytics: Fathom's tag goes in every template's <head>, deferred. It is
cookieless and collects nothing about a person, so no consent banner —
but it is a third party, so the CSP names cdn.usefathom.com for script
and connect rather than opening the door generally, and the tests that
said 'no script at all' now say 'no script this site depends on, and no
origin nobody chose'.

Caching: the host concatenates a header two matching rules both set
rather than overriding, so /* and /static/* each setting Cache-Control
sent 'max-age=0, must-revalidate, max-age=31536000, immutable' on the
stylesheet — first max-age wins, and the year-long cache never happened.
Measured on the live site, which is where the from-memory assumption in
task 32 said to check it. Now /static/* is the only rule that sets it
and HTML takes the host's revalidating default; the post-deploy checks
in site/README.md verify both ends.
2026-07-31 15:39:46 +02:00
istosandClaude Opus 5 6c06583109 site: serve bench.12vectors.com from a Cloudflare Worker
site/wrangler.jsonc puts site/dist/ behind bench.12vectors.com as static
assets. No `main`: the site is files, and a Worker with no script is the
cheapest correct way to serve them.

  html_handling      force-trailing-slash, so /x redirects to /x/ — the
                     url the pages link and rel=canonical names. One
                     page, one address; no url ends in .html.
  not_found_handling 404-page, so an unknown path gets dist/404.html
                     with a 404 status rather than the landing page
                     with a 200.
  routes             bench.12vectors.com as a custom domain. Cloudflare
                     takes the hostname at the zone level and makes the
                     DNS record; nothing else on 12vectors.com moves.

site/root/_headers carries the response policy. HTML revalidates on
every view, so a deploy is visible on the next reload without anyone
clearing a cache; /static/* is kept for a year and never re-checked,
which is safe because the stylesheet and icon urls carry a hash of their
contents. The general rule is written first and the specific one second,
so a host that merged the two instead of overriding would still land on
max-age=0 — the safe side. Alongside it the baseline a public page owes:
nosniff, a referrer policy, a year of HSTS without preload,
X-Frame-Options, and a default-src 'none' CSP that makes "no analytics,
no third-party anything" something the browser enforces rather than
something a test asserted once.

Deploys are run by hand, as releases already are — no Cloudflare token
in repository secrets, no first deploy pipeline. site/README.md names
the account, the Worker, the route and the four-command sequence, plus
the four things to check after a deploy that no test here can reach.

The tests cover everything before Cloudflare: that the config says what
the site needs, that the build writes the files it names, and that
wrangler.jsonc, pages.json and README.md cannot drift apart about which
domain this is. A live response is not among them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:47:12 +02:00