Skip to content

Add consent-gated Conceptual Analytics pixel to dev docs - #550

Open
andrewleesteele wants to merge 2 commits into
mainfrom
hypeship/conceptual-analytics-pixel
Open

Add consent-gated Conceptual Analytics pixel to dev docs#550
andrewleesteele wants to merge 2 commits into
mainfrom
hypeship/conceptual-analytics-pixel

Conversation

@andrewleesteele

@andrewleesteele andrewleesteele commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the Conceptual Analytics front-end pixel to the dev docs site, so ad click IDs and page views are captured on /docs/* as well as the marketing site. Pairs with kernel/website#305, which covers the marketing site.

Why a custom script

www.kernel.sh/docs/* is a Next rewrite that proxies to this Mintlify app, so the marketing site's root layout — and the pixel component in it — never renders on a docs page. Docs currently have no pixel at all.

Mintlify loads every .js file in the content directory on every page, which is the supported way to inject a third-party script. docs.json integrations is a fixed vendor allowlist with no Conceptual entry, so a custom script is the only route short of adding a GTM container.

How it decides whether to load

  1. If a c15t decision is already stored, honour it. Docs share an origin with the marketing site, so the cookie set there is readable here and an accept or decline carries over.
  2. Otherwise apply the same rule c15t applies on the marketing site: ask /api/c15t/show-consent-banner, which is same-origin here, and load only where no banner is required. In regulated jurisdictions c15t prompts; everywhere else it auto-grants marketing and measurement, so this matches the behaviour a visitor would get had they landed on a marketing page instead.
  3. Any failure — endpoint unreachable, non-OK response, parse error — leaves the pixel unloaded.

The gate is needed because the consent banner lives in the marketing site's React tree and never renders on a docs page. Requiring a stored decision would have meant almost no coverage for visitors whose first page is a docs page, which is the traffic this is for.

It also turns on respectDNT and anonymizeIP, both off in the shipped pixel config, before the loader runs, and sends a page_view on docs navigation — the pixel sends one on init and has no router hooks, so client-side navigation between docs pages is otherwise invisible.

The key in this file is the public pixel key, which is designed to be visible in browser code. The server-side conversion key is not used here and must never appear in a browser.

Testing

Verified end to end against the live www.kernel.sh/docs origin by injecting this script with the vendor's dev key, which validates requests but stores nothing:

egress resolved jurisdiction banner required pixel loaded device cookie events
us-east US NONE no yes set 1 page_view
eu-west GB GDPR yes no none none

node --check passes. Not verified in a Mintlify preview deployment — the jurisdiction endpoint is only reachable when the docs are served under the marketing domain, and the script fails closed anywhere else.

Note for reviewers

The consent category is marketing, matching kernel/website#305. Our privacy policy files ad-campaign measurement under analytics, so measurement is arguable; it is one constant in each PR if we want to switch.


Note

Medium Risk
Introduces third-party analytics and consent logic tied to cookies, localStorage, and a jurisdiction API; mis-gating could load tracking where consent is required or skip coverage incorrectly.

Overview
Adds conceptual.js, a Mintlify-loaded script that brings the Conceptual Analytics pixel to /docs/* where the marketing site layout never runs.

Loading is consent-gated on the shared marketing c15t category: existing cookie/localStorage decisions from www.kernel.sh are honored; if none exist, it calls /api/c15t/show-consent-banner and only starts the pixel when no banner is required (matching auto-grant outside regulated regions). Failures leave the pixel unloaded.

When allowed, it injects the vendor loader, sets __CA_CONFIG with respectDNT and anonymizeIP, and patches history.pushState/replaceState plus popstate to emit page_view on client-side docs navigation.

Reviewed by Cursor Bugbot for commit 05231c2. Bugbot is set up for automated code reviews on this repo. Configure here.

Mintlify loads every .js file in the content directory on every page, which
is how the pixel reaches docs pages that the marketing site's layout never
renders.

The docs are served under www.kernel.sh/docs, so the c15t consent cookie set
on the marketing site is readable here and gates the pixel the same way.
respectDNT and anonymizeIP are off in the shipped pixel config and are turned
on before the loader runs. The pixel has no router hooks, so docs navigation
sends its own page_view.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Kernel 🟢 Ready View Preview Sep 5, 2026, 6:54 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

The consent banner lives in the marketing site's app and never renders on a
docs page, so a visitor whose first page is a docs page had no way to reach a
decision and never got the pixel — which is most of the traffic this is meant
to cover.

An existing decision still wins. Absent one, ask the same c15t endpoint the
marketing site uses and apply its rule: prompt in regulated jurisdictions,
auto-grant elsewhere. The endpoint is same-origin here. Any failure leaves the
pixel unloaded.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Risk assessment: Medium

Verdict: Do not auto-approve.

This is not a documentation-only change. The diff adds conceptual.js, which Mintlify loads on every docs page. That script injects a third-party analytics loader from plfalg.kernel.sh, reads c15t consent state from cookies and localStorage, calls /api/c15t/show-consent-banner, and monkey-patches history.pushState / history.replaceState to emit page_view events.

Why this is Medium (not Low / Very Low)

  • Site-wide user-facing surface. The new file runs for all /docs/* visitors, not an isolated page or internal tool.
  • Privacy / consent model. Tracking can start without a stored decision whenever the jurisdiction endpoint returns showConsentBanner === false. The second commit widened that from fail-closed (require stored consent) to geo-gated auto-load.
  • Third-party script injection. A remote loader is appended to document.head on production docs traffic.
  • Global History API patching. Wrapping pushState / replaceState can interact with Mintlify's own client router.
  • Blast radius is the full docs origin. A wrong consent parse, endpoint response, or loader behavior affects measurement and privacy for every visitor.

Why this is not High

The change is a single self-contained file, fails closed on fetch/parse errors, uses credentials: "same-origin", and does not touch auth, billing, infra, or server-side code in this repo. Those bounds keep it at Medium rather than higher.

Action: Comment only. Human review of the consent/geo-gate and third-party loader is required before merge.

Open in Web View Automation 

Sent by Cursor Automation: Assign PR reviewers

@andrewleesteele andrewleesteele changed the title Add consent-gated Conceptual Analytics pixel to docs Add consent-gated Conceptual Analytics pixel to devdocs Sep 5, 2026
@andrewleesteele andrewleesteele changed the title Add consent-gated Conceptual Analytics pixel to devdocs Add consent-gated Conceptual Analytics pixel to dev docs Sep 5, 2026
@andrewleesteele
andrewleesteele marked this pull request as ready for review September 5, 2026 19:46

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk assessment: Medium

Verdict: Do not auto-approve.

This is not a documentation-only change. The diff adds a single new file, conceptual.js (110 lines). Mintlify loads every .js file in the content directory on every docs page, so this runs for all /docs/* visitors.

What the code actually does

  • Injects a third-party loader from https://plfalg.kernel.sh/analytics/loader-v1.js into document.head
  • Reads c15t consent state from cookies and localStorage
  • If no stored decision exists, calls /api/c15t/show-consent-banner and starts tracking when showConsentBanner === false
  • Monkey-patches history.pushState / history.replaceState and listens for popstate to emit page_view events

Why this is Medium (not Low / Very Low)

  • Site-wide user-facing surface. Every docs visitor executes this script, not an isolated page or internal tool.
  • Privacy / consent model. Tracking can start without a stored user decision whenever the jurisdiction endpoint returns showConsentBanner === false. The second commit widened this from fail-closed (require stored consent) to geo-gated auto-load.
  • Third-party script injection. A remote analytics loader is appended on production docs traffic.
  • Global History API patching. Wrapping pushState / replaceState can interact with Mintlify's client router.
  • Blast radius is the full docs origin. A wrong consent parse, unexpected endpoint response, or loader behavior affects measurement and privacy for every visitor.

Why this is not High

The change is one self-contained file. Fetch/parse failures leave the pixel unloaded, the jurisdiction request uses credentials: "same-origin", and this repo's auth, billing, infra, and server-side code are untouched. Those bounds keep it at Medium rather than higher.

Action: Comment only. Human review of the consent/geo-gate and third-party loader is required before merge.

Open in Web View Automation 

Sent by Cursor Automation: Assign PR reviewers

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 05231c2. Configure here.

Comment thread conceptual.js
}
} catch (e) {}

return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined consent treated as undecided

Medium Severity

storedConsent only treats c.marketing:1 or c.marketing:0 (and an explicit JSON boolean) as a recorded choice. c15t omits false keys from the compact c15t cookie, so a stored decline looks like a missing decision. The jurisdiction fallback then loads the pixel wherever showConsentBanner is false, including visitors who already opted out.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 05231c2. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant