Because:
- After scanning the Sync pairing QR code, the desktop device approval page
(/pair/auth/allow) had no entrypoint query param, so Glean's
session.entrypoint was empty on cad_approve_device.view and .submit.
- Firefox opens that page as a brand-new navigation carrying only client_id,
scope, email, uid, channel_id and redirect_uri, so the web app had no
entrypoint to propagate.
- Two in-app paths dropped the entrypoint before pairing even began: /pair's
sync sign-in round trip, and the connect-another-device redirect.
This commit:
- Adds lib/pairing-attribution.ts, which stashes the attribution params
(entrypoint, entrypoint_experiment, entrypoint_variation and the five utm_*)
in namespaced localStorage with a 30 minute TTL when /pair hands control to
Firefox, then restores them onto the pairing-authority URL.
- Restores at bootstrap, before BrowserRouter mounts, because UrlQueryData
writes via a raw history.replaceState that react-router never observes, so a
later write would be clobbered by the first navigateWithQuery().
- Defaults entrypoint to 'preferences' when nothing was stashed, covering
pairings started straight from Firefox's about:preferences dialog.
- Merges the attribution into /pair's sync OAuth handoff URL so it survives the
sign-in round trip, which also repairs /pair's own post-signin metrics.
- Makes the connect-another-device redirect to /pair carry its query params,
matching the four sibling hardNavigate('/pair', {}, true) call sites.
- Adds unit coverage for the stash, TTL, merge and bootstrap rewrite, a
Playwright spec asserting the approval URL and the cad_approve_device.view
ping, and first-time coverage of the CAD bootstrap redirect.