diff --git a/apps.html b/apps.html index 7b2b775..78cb72f 100644 --- a/apps.html +++ b/apps.html @@ -1510,11 +1510,28 @@

Chaos - Stranger Missions

}); })); const initialResults = render(); - // Track editorial-guide arrivals separately from manual category choices. - // Only the allowlisted category and count are sent; no search terms. + // An allowlisted goal alone does not prove an editorial guide referral: + // direct/bookmarked/shared filtered URLs are a separate acquisition source. + // Read referrer locally; never send its URL or any search text to analytics. if (goal !== 'all') { + const guideGoals = { + '/guides/quick-games/': 'quick-games', + '/guides/daily-habits/': 'habits', + '/guides/decision-helpers/': 'decisions', + '/guides/ai-tools/': 'ai' + }; + let landingSource = 'direct_or_shared'; + try { + const referrer = new URL(document.referrer); + if (referrer.origin === window.location.origin && + guideGoals[referrer.pathname] === goal) { + landingSource = 'editorial_guide'; + } + } catch (_) { + // A missing or malformed referrer is not evidence of a guide visit. + } fire('catalog_filter_landing', { - goal, results: initialResults, source: 'editorial_guide' + goal, results: initialResults, source: landingSource }); } })(); diff --git a/scripts/test_guide_catalog_links.py b/scripts/test_guide_catalog_links.py index a8c9281..7945a9e 100644 --- a/scripts/test_guide_catalog_links.py +++ b/scripts/test_guide_catalog_links.py @@ -44,7 +44,11 @@ def test_guide_landing_has_separate_privacy_safe_event(self): self.assertIn("const initialResults = render();", catalog) self.assertIn("if (goal !== 'all')", catalog) self.assertIn("fire('catalog_filter_landing'", catalog) - self.assertIn("goal, results: initialResults, source: 'editorial_guide'", catalog) + self.assertIn("goal, results: initialResults, source: landingSource", catalog) + self.assertIn("let landingSource = 'direct_or_shared'", catalog) + self.assertIn("landingSource = 'editorial_guide'", catalog) + self.assertIn("referrer.origin === window.location.origin", catalog) + self.assertIn("guideGoals[referrer.pathname] === goal", catalog) self.assertNotIn("query: input.value", catalog) def test_new_app_does_not_break_catalog_test(self): @@ -55,6 +59,16 @@ def test_missing_baseline_app_is_rejected(self): cards = '' * (MIN_CATALOG_CARDS - 1) self.assertFalse(catalog_card_count_ok(cards)) + def test_attribution_uses_matching_editorial_guide_only(self): + catalog = (ROOT / "apps.html").read_text(encoding="utf-8") + for path, goal in GUIDES.items(): + with self.subTest(guide=path): + self.assertIn(f"'/guides/{path}/': '{goal}'", catalog) + self.assertIn("const referrer = new URL(document.referrer)", catalog) + self.assertIn("landingSource = 'direct_or_shared'", catalog) + self.assertIn("landingSource = 'editorial_guide'", catalog) + self.assertNotIn("referrer: document.referrer", catalog) + def test_manual_filter_syncs_shareable_goal_url(self): catalog = (ROOT / "apps.html").read_text(encoding="utf-8") self.assertIn("const syncGoalUrl = () =>", catalog)