Skip to content

feat(core): TRAC-813 Gate analytics cookies behind shopper consent#3046

Merged
chanceaclark merged 1 commit into
canaryfrom
chancellorclark/ltrac-846-gate-catalyst-analytics-cookies-behind-shopper-consent
Jun 12, 2026
Merged

feat(core): TRAC-813 Gate analytics cookies behind shopper consent#3046
chanceaclark merged 1 commit into
canaryfrom
chancellorclark/ltrac-846-gate-catalyst-analytics-cookies-behind-shopper-consent

Conversation

@chanceaclark

@chanceaclark chanceaclark commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Jira: TRAC-813

What/Why?

catalyst.visitorId, catalyst.visitId, and currencyCode were set unconditionally, ignoring the shopper's consent decision. This gates them behind consent.

Consent decision logic (new hasConsentFor(category) in lib/consent-manager): the c15t-consent cookie is the source of truth. When no consent cookie exists yet, consent is treated as not given — the client-side consent manager writes the cookie once the shopper decides (or c15t auto-grants when consent is disabled), and startVisit handles recording the visit at that point.

Category mapping mirrors scripts-transformer's BC-to-c15t map: the visit cookies are ANALYTICS → measurement; currencyCode is a preference → functionality. Without functionality consent, switching currency still updates the cart but the preference isn't persisted.

Withdrawal: the proxy deletes both analytics cookies when a consent cookie exists without measurement granted, so identifiers don't linger for the cookie's remaining 400-day lifetime after the shopper opts out.

Firing the visit event after mid-session consent (the open question in the ticket): the proxy only starts visits on full-page navigations (prefetch/RSC requests are skipped), so accepting the banner wouldn't record a visit until the next hard reload. A new startVisit server action sets the cookies and fires visitStartedEvent, triggered by a small client listener when measurement consent flips to granted. Two non-obvious details:

  • The listener polls document.cookie directly before calling the action — c15t updates its React state before persisting the cookie, so the action (which validates consent from the cookie server-side) would no-op if called immediately after the state change.
  • The proxy skips visit creation for server-action POSTs (Next-Action header). Cookies set in the proxy aren't visible to the action handler's cookie store; without this, accepting the banner fired two visitStartedEvents with different visit IDs.

Also fixed a latent bug in two e2e expiry tests: they looked up visitId/visitorId instead of catalyst.visitId/catalyst.visitorId, so their assertions never executed.

Refs TRAC-813

Testing

pnpm lint, pnpm typecheck, and all 6 analytics-session.spec.ts e2e tests pass locally (tests now seed accept/decline consent cookies, so they're deterministic regardless of the store's consent setting; new tests cover the declined and withdrawal paths).

Verified against a local dev server connected to a store with cookie consent enabled:

Request state Result
Consent cookie with c.measurement:1 visitorId + visitId set, one VisitStarted mutation
Consent cookie without measurement no analytics cookies set
Consent withdrawn, stale analytics cookies sent both cookies expired via Set-Cookie
No consent cookie no analytics cookies set

Browser flow via Playwright: loading the home page sets no analytics cookies, clicking "Accept All" sets both cookies without a reload and fires exactly one VisitStarted mutation, and a subsequent reload keeps the same visitId (sliding-window refresh, no duplicate event).

Migration

None. No files moved; withAnalyticsCookies is back in its original position in the proxy chain.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5c8aab0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Jun 11, 2026 10:45pm

Request Review

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from 3cec674 (2026-06-11).

Per-Route First Load JS

Route Baseline Current Delta
/(default)/(auth)/change-password/page 318.4 kB 318.8 kB +0.4 kB (+0.1%)
/(default)/(auth)/login/forgot-password/page 317.5 kB 318 kB +0.5 kB (+0.2%)
/(default)/(auth)/login/page 318 kB 318.4 kB +0.4 kB (+0.1%)
/(default)/(auth)/register/page 354.1 kB 354.6 kB +0.5 kB (+0.1%)
/(default)/(faceted)/brand/[slug]/page 330.1 kB 330.5 kB +0.4 kB (+0.1%)
/(default)/(faceted)/category/[slug]/page 338.6 kB 339 kB +0.4 kB (+0.1%)
/(default)/(faceted)/search/page 330.1 kB 330.5 kB +0.4 kB (+0.1%)
/(default)/[...rest]/page 313.1 kB 313.5 kB +0.4 kB (+0.1%)
/(default)/account/addresses/page 357.6 kB 358 kB +0.4 kB (+0.1%)
/(default)/account/orders/[id]/page 321.2 kB 321.6 kB +0.4 kB (+0.1%)
/(default)/account/orders/page 322.2 kB 322.6 kB +0.4 kB (+0.1%)
/(default)/account/settings/page 328.7 kB 329.2 kB +0.5 kB (+0.2%)
/(default)/account/wishlists/[id]/page 336.1 kB 336.4 kB +0.3 kB (+0.1%)
/(default)/account/wishlists/page 331.1 kB 331.5 kB +0.4 kB (+0.1%)
/(default)/blog/[blogId]/page 313.1 kB 313.5 kB +0.4 kB (+0.1%)
/(default)/blog/page 314.1 kB 314.5 kB +0.4 kB (+0.1%)
/(default)/cart/page 333.9 kB 334.3 kB +0.4 kB (+0.1%)
/(default)/compare/page 325.2 kB 325.7 kB +0.5 kB (+0.2%)
/(default)/gift-certificates/balance/page 317 kB 317.5 kB +0.5 kB (+0.2%)
/(default)/gift-certificates/page 313.1 kB 313.5 kB +0.4 kB (+0.1%)
/(default)/gift-certificates/purchase/page 356.6 kB 357.1 kB +0.5 kB (+0.1%)
/(default)/page 330.2 kB 330.7 kB +0.5 kB (+0.2%)
/(default)/product/[slug]/page 385.2 kB 385.4 kB +0.2 kB (+0.1%)
/(default)/webpages/[id]/contact/page 355 kB 355.5 kB +0.5 kB (+0.1%)
/(default)/webpages/[id]/normal/page 321.2 kB 321.6 kB +0.4 kB (+0.1%)
/(default)/wishlist/[token]/page 326 kB 326.3 kB +0.3 kB (+0.1%)
/maintenance/page 306.7 kB 307.1 kB +0.4 kB (+0.1%)

Threshold: 5% increase. Routes with ⚠️ exceed the threshold.

@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-846-gate-catalyst-analytics-cookies-behind-shopper-consent branch from 3d151e9 to 6441b26 Compare June 11, 2026 22:16
@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-846-gate-catalyst-analytics-cookies-behind-shopper-consent branch from 6441b26 to 5a7a44f Compare June 11, 2026 22:25
@chanceaclark

Copy link
Copy Markdown
Contributor Author
Screen.Recording.2026-06-11.at.16.27.34.mov

When the store's cookie consent setting is enabled, the catalyst.visitorId
and catalyst.visitId analytics cookies are no longer set or refreshed until
the shopper grants measurement consent, and leftover cookies are deleted
once consent is withdrawn. The currencyCode preference cookie now requires
functionality consent. With no consent decision recorded yet, cookies are
only allowed when the store does not require cookie consent.

The proxy only starts visits on full page navigations, so granting consent
mid-session now triggers a startVisit server action that sets the cookies
and fires the server-side visitStartedEvent immediately. Server action
POSTs no longer start visits in the proxy, which would otherwise duplicate
the event fired by the action.

Fixes TRAC-813
Co-Authored-By: Claude <noreply@anthropic.com>
@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-846-gate-catalyst-analytics-cookies-behind-shopper-consent branch from 5a7a44f to 5c8aab0 Compare June 11, 2026 22:44
@chanceaclark chanceaclark marked this pull request as ready for review June 11, 2026 22:48
@chanceaclark chanceaclark requested a review from a team as a code owner June 11, 2026 22:48
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 90 93 92 95

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 74 90 75 85
Accessibility 95 95 95 95
Best Practices 100 100 100 100
SEO 88 88 88 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 3.7 s 3.6 s 3.6 s 4.4 s
CLS 0.037 0 0.05 0
FCP 1.2 s 1.2 s 1.2 s 1.2 s
TBT 10 ms 0 ms 10 ms 10 ms
Max Potential FID 70 ms 40 ms 60 ms 60 ms
Time to Interactive 3.7 s 3.7 s 6.0 s 4.4 s

Full Unlighthouse report →

@chanceaclark chanceaclark added this pull request to the merge queue Jun 12, 2026
Merged via the queue into canary with commit 5034ea3 Jun 12, 2026
23 of 29 checks passed
@chanceaclark chanceaclark deleted the chancellorclark/ltrac-846-gate-catalyst-analytics-cookies-behind-shopper-consent branch June 12, 2026 17:32
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.

3 participants