Add consent-gated Conceptual Analytics pixel to dev docs - #550
Add consent-gated Conceptual Analytics pixel to dev docs#550andrewleesteele wants to merge 2 commits into
Conversation
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>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 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.
There was a problem hiding this comment.
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 fromplfalg.kernel.sh, readsc15tconsent state from cookies andlocalStorage, calls/api/c15t/show-consent-banner, and monkey-patcheshistory.pushState/history.replaceStateto emitpage_viewevents.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.headon production docs traffic.- Global History API patching. Wrapping
pushState/replaceStatecan 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.
Sent by Cursor Automation: Assign PR reviewers
There was a problem hiding this comment.
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.jsintodocument.head - Reads
c15tconsent state from cookies andlocalStorage - If no stored decision exists, calls
/api/c15t/show-consent-bannerand starts tracking whenshowConsentBanner === false - Monkey-patches
history.pushState/history.replaceStateand listens forpopstateto emitpage_viewevents
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/replaceStatecan 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.
Sent by Cursor Automation: Assign PR reviewers
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.
| } | ||
| } catch (e) {} | ||
|
|
||
| return null; |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 05231c2. Configure here.




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
.jsfile in the content directory on every page, which is the supported way to inject a third-party script.docs.jsonintegrationsis 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
/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-grantsmarketingandmeasurement, so this matches the behaviour a visitor would get had they landed on a marketing page instead.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
respectDNTandanonymizeIP, both off in the shipped pixel config, before the loader runs, and sends apage_viewon 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/docsorigin by injecting this script with the vendor's dev key, which validates requests but stores nothing:NONEpage_viewGDPRnode --checkpasses. 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, someasurementis 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
marketingc15t category: existing cookie/localStorage decisions fromwww.kernel.share honored; if none exist, it calls/api/c15t/show-consent-bannerand 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_CONFIGwithrespectDNTandanonymizeIP, and patcheshistory.pushState/replaceStatepluspopstateto emitpage_viewon client-side docs navigation.Reviewed by Cursor Bugbot for commit 05231c2. Bugbot is set up for automated code reviews on this repo. Configure here.