Repository navigation
feat(analytics): add X pixel behind analytics consent - #982
Conversation
Loads the X base code (pixel ri3so) and sends the "Visit docs" event once per full page load, only when analytics is accepted on or after the banner text that mentions ad measurement (X_PIXEL_CONSENT_SINCE), or re-confirmed since then (cc_cookie data.adConsentAt). Auto events and form-PII matching are switched off, and the pixel's first-party cookies are expired on withdrawal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: fay@rapidata.ai <fay@rapidata.ai>
✅ No skill update needed — confirmed by @LinoGigerThis PR does not modify Before merging, pick one:
|
|
The |
uwt.js 2.4.12 re-writes _twclid for 120 s after a ?twclid= landing (at 5 s, every 10 s, and on visibilitychange->hidden) without re-checking consent, so a single delete on revoke does not hold. After a revoke, expire it every second and on hide for 130 s; a re-grant stops the sweep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: fay@rapidata.ai <fay@rapidata.ai>
|
LinoGiger, can you review this and apply the |
Simpler, as asked in review: when analytics is withdrawn on a page that loaded uwt.js, expire the X cookies and reload, which ends uwt.js's _twclid keep-alive. Every load without valid consent expires the X cookies once. The loader is now the official X base snippet; only the four twq set-false flags, which back the privacy-policy statement, stay on top of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: fay@rapidata.ai <fay@rapidata.ai>
|
CI status at 9bee0cf: |
…l cutoff From X_PIXEL_CONSENT_SINCE on, the first Allow loads the X pixel, so the later Reject reloads the page and the re-grant runs on a fresh load where heatmaps are not enabled yet. Wait for heatmaps before the post-grant click, and never move the X pixel test clock backwards. Checked with the cutoff in the past and future. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: fay@rapidata.ai <fay@rapidata.ai>
Why
GTM (Fay) needs the X ads pixel on docs.rapidata.ai to measure X campaigns and build retargeting audiences. Same design across rapidata.ai, app.rapidata.ai and docs.rapidata.ai: no new consent category, no
revisionbump. Because they share onecc_cookie, consents given to the old text (which never mentioned advertising) must not turn the pixel on. Only a consent saved on or afterX_PIXEL_CONSENT_SINCE, or a re-confirm since then, does.Flow
flowchart LR banner["cookieconsent banner / preferences"]:::changed ==>|"Allow / Save → setCookieData adConsentAt"| cc[("cc_cookie on .rapidata.ai")] js["docs/js/analytics.js"]:::changed -->|"acceptedCategory + timestamps"| cc js ==>|"twq config ri3so + event tw-ri3so-ri3tn"| x["static.ads-twitter.com/uwt.js"]:::new x ==>|"adsct"| xa["t.co / analytics.twitter.com"]:::new js -->|"capture"| ph["rapidata.ai/ingest (PostHog)"] classDef new fill:#d3f9d8,stroke:#2f9e44,color:#000 classDef changed fill:#fff3bf,stroke:#f08c00,color:#000Decisions
lastConsentTimestamp≥X_PIXEL_CONSENT_SINCEORdata.adConsentAt≥ it). cookieconsent 3.1 leaveslastConsentTimestampalone when a re-confirm changes no category. So a click on Allow/Save with analytics on stampsadConsentAtviaCookieConsent.setCookieData({ mode: "update" }). Checked in a browser: it persists to the single.rapidata.aicookie and keeps the library's expiry. No hand-written cookie fallback was needed.autoConfig,autoAdvancedMatching,dataLayerTracking,autoDwellTracking, set per pixel beforeconfig). Seen in a browser: by default uwt.js sendsauto_long_site_dwellon its own timer, even after Reject, and re-sets_twpid/_twsid.autoAdvancedMatchingalso scrapes form fields for email/phone. With these off, the only hits are the base PageView plus the Visit docs event, and nothing more is sent after Reject.?twclid=landing, uwt.js 2.4.12 keeps re-writing_twclidfrom a closure that never re-checks consent. So when analytics is withdrawn on a page where uwt.js ran, we expire_twclid/_twpid/_twsidand calllocation.reload(). uwt.js doesn't load without consent, so the reload ends the keep-alive. If uwt.js never ran on the page, there's no reload. Every page load without valid consent also expires the three cookies once. The loader is the official X base snippet verbatim; the only additions are the four set-false flags and the consent gate. All three repos do the same.nothing reaches PostHog … after Rejectpassed only because the cutoff is still in the future. I ran the whole suite with the cutoff moved to the past: the re-grant click raced PostHog's heatmaps starting up on the reloaded page. The test now waits forposthog.heatmaps.isEnabledbefore that click. Pinning the browser clock before the cutoff, as app-frontend did, isn't usable here: Playwright'sclock.setSystemTimealone breaks this test's heatmap flush. The X pixel test clock now never moves backwards._twclid,_twpid,_twsidwere observed on.rapidata.ai(_twclidonly appears with a?twclid=landing). The.twitter.com/t.cocookies are third-party, so we can't clear them.policyVersionis now2026-10-29.Preferences dialog, Analytics section: "Allow analytics to connect your visits across our sites, understand how you use Rapidata and measure our advertising. We use PostHog and the X Pixel, which tells X which of our sites you visited so we can measure our ads on X." The docs dialog had no provider sentence before, so "We use PostHog and" is new wording. Please review it alongside the other copy.
Verified
Suite run with
X_PIXEL_CONSENT_SINCEset to both2026-10-27(past) and2026-10-29(future): unit and browser tests pass in both cases.Live uwt.js check of the reload: landed with
?twclid=abc, then Allow, then Reject. The page reloaded withouttwq, and no_tw*cookie came back in 25 s. Unit tests cover three cases: revoke reloads once, there's no reload when the pixel never loaded, and the cookies are expired on any load without valid consent.I built the site with
uv run --group docs mkdocs buildand ran Playwright against the live uwt.js, with the clock set pastX_PIXEL_CONSENT_SINCE:The new CI browser test
consent.browser.test.jscovers the same cases. Reviewer: please applyskill-unchanged-approved. This PR changes onlydocs/js/analytics.jsand its tests, the SDK andsrc/rapidata/_skill/are untouched, and nothing the agent skill documents changed.Sibling PRs: RapidataAI/Website#606 (also updates the privacy policy) · RapidataAI/app-frontend#3753
🔗 Session: node-52a5e9ed
🤖 Generated with Claude Code