Splash gets analytics — OpenPanel wired the same way as the others
Context Vigilance's splash now phones home to OpenPanel like its siblings — production-only, clientId-from-env, no secrets in the browser. The interesting part wasn't this wiring; it was figuring out why the other three splashes had been silently 401-ing for a day.
Why Care?
Splash pages without analytics are flying blind. We don't run paid acquisition for these sites, so the only way we'd know whether anything we ship resonates with anyone is to actually count the visits. Yesterday we picked OpenPanel as the lightweight, low-cost (potentially self-hostable) tracker for the Lossless splash family and wired it into three of them — astro-knots, content-farm, and lossless-flavored-markdown-package. We didn't get to context-vigilance-kit because we didn't make it that far before the day collapsed.
Today's session got it wired with the exact same pattern, and solved the mystery of why none of yesterday's three splashes were actually recording events even though the script tag was deploying correctly.
What's New?
splash/src/components/Analytics.astro— a four-line gate (PROD && OPENPANEL_CLIENT_ID) wrapping the OpenPanel queue-shim IIFE plus anis:inlinescript tag forop1.js. Identical to the version inastro-knots/splash,content-farm/splash, andlfm/splash.clientIdis read fromimport.meta.env.OPENPANEL_CLIENT_IDat build time and substituted into the script via Astro'sdefine:vars.splash/src/layouts/BaseLayout.astro— importsAnalyticsand renders<Analytics />in<head>, right after the FOUC-prevention mode script. One canonical<head>per site, one place where analytics lives..github/workflows/pages.yml— Build step now setsenv: OPENPANEL_CLIENT_ID: ${{ vars.OPENPANEL_CLIENT_ID }}so the env var is injected duringpnpm build. Repo Variable, not Secret — the clientId is publicly visible in rendered HTML by design (it's the public side of the SDK).No
clientSecretanywhere — the secret is only for server-to-server SDK calls. Browser auth usesclientId + Origin headervalidated against the OpenPanel project's "supported domains" list. Anything you'd expose in HTML you don't want to be a secret.
The mystery from yesterday
Yesterday we successfully shipped Vercel-side analytics across mpstaton.com, the-water-foundation.com, and fullstack-vc.com, but the three GitHub Pages splashes showed zero activity in the OpenPanel dashboard despite:
Script tag visible in deployed HTML (
curlconfirmed)Correct clientId UUID baked in (
view-source:confirmed)Repo Variables set, workflows green, Pages deploys successful
We had a hypothesis written in the rollout doc — OpenPanel's per-project "supported domains" allowlist is probably empty/wrong — but didn't get to verify it.
Today, before wiring context-vigilance-kit, we tested the hypothesis on astro-knots/splash the way it should have been tested in the first place: open the splash in an incognito window, open DevTools → Network, filter by openpanel, reload.
The track request was returning 401 Unauthorized. Two requests, both red.
That's the diagnostic. OpenPanel rejects browser events at the API layer when the request Origin doesn't appear in the project's supported-domains list. Adding https://lossless-group.github.io/ to the splash_astro-knots client flipped the next reload's track from 401 to 200. Dashboard tile started counting within a minute.
The same single origin (https://lossless-group.github.io/) needs adding to the other three splash clients in OpenPanel — splash_content-farm, splash_lossless-flavored-markdown, and the new splash_context-vigilance — because GitHub Pages serves them all from one host with different paths, and OpenPanel allowlists by host.
How to verify it on any splash in 60 seconds
1. Open the splash in an incognito window (extensions off)
2. DevTools → Network tab → filter "openpanel"
3. Reload the page
4. Look at the `track` request status:
- 200/202 → working, dashboard will populate
- 401 → supported-domains not set on the OpenPanel client
- missing → tracker blocker active even in incognito,
or op1.js bailed (check console)This recipe is now the canonical diagnostic for the Lossless splash family, captured in context-v/prompts/Setup-Analytics-Across-Sites.md and in agent memory.
What's still on the user
This entry's code lands on push, but two things have to happen outside the repo before events flow:
In the
lossless-group/context-vigilance-kitGitHub repo → Settings → Secrets and variables → Actions → Variables tab → addOPENPANEL_CLIENT_IDwith the clientId from thesplash_context-vigilanceOpenPanel project. (Variable, not Secret. The value is public.)In OpenPanel →
splash_context-vigilanceclient → supported domains → addhttps://lossless-group.github.io/. Same domain edit pending forsplash_content-farmandsplash_lossless-flavored-markdown.
Without (1), the <Analytics /> gate stays closed and nothing renders into the HTML. Without (2), the script renders but every event 401s. Both are dashboard-config gaps, neither requires a code change.
Lesson captured
clientId is public by design — it's how the browser identifies which project an event belongs to. clientSecret is server-only and must never ship in client-side code. Any GitHub repo Secret holding OPENPANEL_CLIENT_SECRET for a browser-only splash is unused and can be deleted. The "auth" for browser events is the combination of clientId plus the request's Origin header, checked against the OpenPanel project's supported-domains list. Get the domain list right and the rest follows.
See also
Rollout master doc:
context-v/prompts/Setup-Analytics-Across-Sites.mdThe agent memory:
feedback_openpanel_domain_allowlist.mdin~/.claude/projects/.../memory/The reference wiring:
astro-knots/splash/src/components/Analytics.astro
