Repository navigation
feat(marketing): add PostHog analytics with AI-referral tagging - #42
Conversation
Load public/analytics.js (deferred) from the shared head block so every generated page gets it. It initializes PostHog cookieless, without session recording or input autocapture, and tags visits referred by AI assistants with an ai_referral_landing event and an ai_referrer super property. The committed file keeps an inert placeholder key; build:site injects POSTHOG_KEY from the environment and the deploy workflow passes the POSTHOG_KEY secret. The privacy page now describes the analytics, and the verify workflow runs the new node:test suite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔴 CodeDig PR Analysis — FAILED; analysis max severity: high; shown blockers: 1 (high); budget: 11.225/100 points consumed, 88.775 points remaining (green)The gate failed with blocking findings marked below. Budget status does not override the recorded gate verdict. 🤖 SummaryThis PR is high risk (1 findings, 3 files changed). Summary
Coverage data: not available.
✅ Recommendations
🧪 Test Coverage Delta (no data)Coverage data not available for this repo — enable via Settings → Coverage Detailed Findings[high] High complexity in public/analytics.jsCyclomatic complexity spike detected: public/analytics.js (complexity: 44, delta: +44) Powered by CodeDig |
🔴 CodeDig PR Analysis — FAILED; analysis max severity: high; shown blockers: 1 (high); budget: 11.225/100 points consumed, 88.775 points remaining (green)The gate failed with blocking findings marked below. Budget status does not override the recorded gate verdict. 🤖 SummaryThis PR is high risk (1 findings, 3 files changed). Summary
Coverage data: not available.
✅ Recommendations
🧪 Test Coverage Delta (no data)Coverage data not available for this repo — enable via Settings → Coverage Detailed Findings[high] High complexity in public/analytics.jsCyclomatic complexity spike detected: public/analytics.js (complexity: 44, delta: +44) Powered by CodeDig |
What this does
public/analytics.js, loaded with<script src="/public/analytics.js" defer></script>fromrenderSharedHeadBlock()inscripts/build-pages.js, so all 53 generated pages include it. Redirect stubs don't include it because they navigate away immediately. The root HTML was regenerated withbuild-pages.js --force(one added line per page;--checkis clean).https://us-assets.i.posthog.com/static/array.js, APIhttps://us.i.posthog.com), the same region the HelloSaaSy sites use.POSTHOG_KEY = "__POSTHOG_KEY__", which keeps it inert (nothing is loaded or sent). WhenPOSTHOG_KEYis set in the environment,npm run build:sitewrites it intodist/public/analytics.jsafter checking that it looks likephc_...; a malformed value fails the build.deploy-pages.ymlpassessecrets.POSTHOG_KEYto that step. The root fallback HTML and the repo never contain the key.classify(document.referrer, location)returnschatgpt | perplexity | claude | gemini | copilot | meta_ai | grok | deepseek | mistral | you | phindornull, from the referrer host (subdomains included; Copilot also viaedgeservices.bing.com/edgesvc/chat) orutm_source(e.g.utm_source=chatgpt.com). Same-site referrers returnnull. On a match, once per tab session (sessionStorage guard in try/catch), it callsposthog.register({ai_referrer})and capturesai_referral_landingwithai_engine,landing_path,referrer_host,utm_source, and$set_once: {initial_ai_referrer}. It also mirrors the event togtagif GA ever appears; there is none today.src/pages/privacy.html): it said "does not run analytics or tracking scripts". It now describes cookieless PostHog and the one sessionStorage key, and the "Last updated" date is bumped. Please review the wording.npm test(node:test) is added and run inverify-pages-build.yml. README documents the setup.Consent and persistence choice
The site has no cookie banner, no GA, and sets no cookies. Its privacy page promised none. PostHog therefore runs cookieless (
persistence: 'memory'): no cross-visit identifier, and each page load gets a fresh anonymous ID. To keep AI attribution across pages within one visit, the engine name is stored under one sessionStorage key (ai_referrer), cleared when the tab closes, and re-registered on later pages. Also set:disable_session_recording: true,disable_surveys: true,autocapture: { element_allowlist: ['a', 'button', 'form'] }(inputs are never autocaptured), andperson_profiles: 'identified_only'.Known limitation: legacy URLs that hit generated redirect stubs (e.g.
/compare-wiremock.htmlto/vs/wiremock.html,/blog.html) drop the query string, and the destination sees a same-site referrer. AI visits that land on an old URL therefore count as plain pageviews, notai_referral_landing. A follow-up could forwardlocation.searchinrenderRedirectPage.Conflicts with #41
None expected. This PR doesn't touch
src/pages/index.html,src/pages/faq.html, orpublic/llms.txtcontent. The regenerated rootindex.htmlandfaq.htmlgain one<head>line.git merge-treeagainstseo/llms-title-faq-schemamerges cleanly. Whichever merges second should rerunnpm run build:pages(or--force) if--checkreports drift.What Ray needs to do
phc_...).POSTHOG_KEY. Or rungh secret set POSTHOG_KEY -R SaaSy-Solutions/mockforge.dev.main(deploy-pages.yml); to turn analytics on without a code change later, re-run that workflow via workflow_dispatch after adding the secret.ai_referral_landingbroken down byai_engine.Verification
npm test: 10 passing. Covers every engine host and lookalikes (max.ai,notchatgpt.com,chatgpt.com.evil.example),utm_source, the same-site rule, placeholder inertness, and a simulated browser run with a test key (loader URL, init config, register/capture payloads, once-per-session guard, gtag mirror, blocked sessionStorage). It also runs a realbuild-pages.js <tmpdir>withPOSTHOG_KEY=phc_buildtestkey: the key is injected intodist/public/analytics.js, the committed file keeps the placeholder, and every generated page includes the script exactly once. A malformed key fails the build.npm run build:css && npm run build:site: built 53 pages;dist/index.html,dist/faq.html, anddist/vs/wiremock.htmleach include the script once.check:links --internal-only: no broken links.build-pages.js --check: in sync.deploy-pages.ymltriggers only on push tomainplus manual dispatch.verify-pages-build.ymlandrunner-guard.ymlrun on PRs but only build and check. Repo deployments show onlygithub-pagesfrommain.🤖 Generated with Claude Code