Lossless Changelog
product

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.

Michael StatonMusic Editoraugmented with Claude Code on Claude Opus 4.7 (1M context)

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 an is:inline script tag for op1.js. Identical to the version in astro-knots/splash, content-farm/splash, and lfm/splash. clientId is read from import.meta.env.OPENPANEL_CLIENT_ID at build time and substituted into the script via Astro's define:vars.

  • splash/src/layouts/BaseLayout.astro — imports Analytics and 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 sets env: OPENPANEL_CLIENT_ID: ${{ vars.OPENPANEL_CLIENT_ID }} so the env var is injected during pnpm build. Repo Variable, not Secret — the clientId is publicly visible in rendered HTML by design (it's the public side of the SDK).

  • No clientSecret anywhere — the secret is only for server-to-server SDK calls. Browser auth uses clientId + Origin header validated 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 (curl confirmed)

  • 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:

  1. In the lossless-group/context-vigilance-kit GitHub repo → Settings → Secrets and variables → Actions → Variables tab → add OPENPANEL_CLIENT_ID with the clientId from the splash_context-vigilance OpenPanel project. (Variable, not Secret. The value is public.)

  2. In OpenPanel → splash_context-vigilance client → supported domains → add https://lossless-group.github.io/. Same domain edit pending for splash_content-farm and splash_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.md

  • The agent memory: feedback_openpanel_domain_allowlist.md in ~/.claude/projects/.../memory/

  • The reference wiring: astro-knots/splash/src/components/Analytics.astro