Let the marketing pages count visits, and nothing else - #13
Draft
jxxyx-bloop wants to merge 1 commit into
Draft
Conversation
The site has one deployment that would benefit from knowing whether anyone arrives, and a product whose headline promise is that nothing phones home. Those are not in conflict as long as the line between them is drawn somewhere a person cannot forget it, so this draws it in four places rather than one. `ANALYTICS_ID` in the build environment turns on a GA4 tag for the landing page in its thirteen locales and for /setup/. Unset — which is what every fork and every local build gets — there is no tag, the CSP is byte-for-byte the closed policy it was, and the privacy line still reads "no analytics", because that is still true for that build. The id is read from the environment rather than committed: it is not a secret, it is in the source of any page that runs one, but an id in a public repo follows every fork, and a stranger's readers would land in this project's property without either of them knowing. The demo dashboard and the file:// report are never covered. That is enforced by construction (the demo is rendered by the engine, which is passed no tag), by check_no_remote.py refusing to apply --allow under demo/, by the second _headers block that takes the demo back under the closed policy, and by a CI step that builds the analytics shape and greps the demo for the tag. The privacy copy is switched by the same variable in all thirteen locales, so no build can advertise a promise it is not keeping. The header checks moved out of site.yml into site/tools/check_headers.py. They had to grow to read more than one policy block, and sixty lines of Python inlined in YAML is sixty lines a contributor cannot run before pushing. docs/setup/ANALYTICS.md is the procedure, and weighs the alternatives that cost less — including the one that costs nothing and keeps the promise literal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LAJJsknjzbsp9tKEVtQ5Y
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ai-observatory | f7c20e2 | Commit Preview URL Branch Preview URL |
Aug 23 2026, 05:06 PM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds optional visitor analytics to the marketing pages only — the landing page in its thirteen locales, and
/setup/. It is off in this PR, and stays off until a deployment setsANALYTICS_ID.Merging this changes nothing about what the site serves. The switch is an environment variable in Cloudflare, not a commit.
Why it needed engineering
The obvious version of this change is one
<script>tag in<head>. That was not available here, because three things already existed and all three are deliberate:check_no_remote.pyfails the build on any remotesrc, and the CSP isdefault-src 'none'with per-script hashes;So the change is a boundary rather than an exception: the marketing pages may count visits; the product never phones home.
The boundary, and what holds it
/demo/and the dashboardrender.pywrites onto a laptop are never covered. Four independent guards, so it cannot be crossed by forgetting:site/build.pysite/tools/check_no_remote.py--allownever applies to anything underdemo/site/tools/check_headers.py/demo/site.ymlThe privacy copy is switched by the same variable as the tag, in all thirteen locales — a build cannot advertise a promise it is not keeping. With no id set,
_headersis byte-for-byte what it was.The measurement id
Read from
ANALYTICS_IDat build time, never committed. Not because it is a secret — it is in the source of any page that runs one — but because an id in this repo would follow every fork, and a stranger's readers would land in this project's property with neither party knowing.Changes
site/build.py—ANALYTICS_ID(validated), Consent Mode v2 defaults with storage denied in the EEA/UK/CH,csp()split out so both policy blocks come from one functionsite/i18n.py—priv_3_analyticsin all thirteen localessite/tools/check_headers.py— new. The header and CSP checks, lifted out ofsite.ymlbecause they had to grow to read more than one policy block, and sixty lines of Python inlined in YAML is sixty lines nobody can run before pushingsite/tools/check_no_remote.py— per-host--allow, never applicable underdemo/.github/workflows/site.yml— calls the tool; adds a step that builds the analytics shape and puts it through the same gates with a fake iddocs/setup/ANALYTICS.md— new. The procedure, the consent posture, and the alternatives weighed — including the one that costs nothing and keeps the promise literalREADME.md,CONTRIBUTING.md,DESIGN-SYSTEM.md §7,DEPLOY.md— the claim restated preciselyVerification
Both build shapes, locally:
Negative cases confirmed to fail: a tag planted on the demo while the host is allowed; a
/*policy reaching a host with no narrower block taking the demo back; an undeclared host; a wildcard inscript-src.Before merging
Read
docs/setup/ANALYTICS.md§1 and §3 first. §1 sets expectations on what analytics can actually tell you about an audience (the demographics half is the weakest part); §3 recommends starting with Cloudflare's script-free zone analytics, which costs no code, no copy change and no promise.Generated by Claude Code