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)