From 9bab74e37d5a75e2b736583f5483c9fcabf9be21 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Sat, 7 Nov 2026 09:04:59 +0900 Subject: [PATCH 1/2] fix(analytics): distinguish guide referrals from direct category deep links --- apps.html | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) 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 }); } })(); From a1dd2d10401622b95d0a9e9e098a64f3199aca2d Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Sat, 7 Nov 2026 09:05:18 +0900 Subject: [PATCH 2/2] test(analytics): keep guide referral classification honest and privacy safe --- scripts/test_guide_catalog_links.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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)