diff --git a/apps.html b/apps.html
index ecc4486..7b2b775 100644
--- a/apps.html
+++ b/apps.html
@@ -1437,6 +1437,13 @@
Chaos - Stranger Missions
const src = card.querySelector('img')?.getAttribute('src') || '';
return src.split('/').pop()?.replace(/\.png$/, '') || '';
};
+ // Keep the selected purpose shareable across reloads without exposing search text.
+ const syncGoalUrl = () => {
+ const url = new URL(window.location.href);
+ if (goal === 'all') url.searchParams.delete('goal');
+ else url.searchParams.set('goal', goal);
+ window.history.replaceState(window.history.state, '', url.pathname + url.search + url.hash);
+ };
const fire = (event, data) => {
if (typeof window.gtag === 'function') window.gtag('event', event, data);
};
@@ -1476,6 +1483,7 @@ Chaos - Stranger Missions
goal = 'all';
lastReportedSearch = '';
buttons.forEach(button => button.setAttribute('aria-pressed', String(button.dataset.goal === 'all')));
+ syncGoalUrl();
render();
input.focus();
fire('catalog_search_reset', {source: 'empty_state'});
@@ -1483,6 +1491,7 @@ Chaos - Stranger Missions
buttons.forEach(button => button.addEventListener('click', () => {
goal = button.dataset.goal;
buttons.forEach(b => b.setAttribute('aria-pressed', String(b === button)));
+ syncGoalUrl();
fire('catalog_filter_used', {goal, results: render()});
}));
document.querySelectorAll('[data-detail-app]').forEach(link => {
diff --git a/scripts/test_guide_catalog_links.py b/scripts/test_guide_catalog_links.py
index b013a70..a8c9281 100644
--- a/scripts/test_guide_catalog_links.py
+++ b/scripts/test_guide_catalog_links.py
@@ -55,6 +55,23 @@ def test_missing_baseline_app_is_rejected(self):
cards = '' * (MIN_CATALOG_CARDS - 1)
self.assertFalse(catalog_card_count_ok(cards))
+ def test_manual_filter_syncs_shareable_goal_url(self):
+ catalog = (ROOT / "apps.html").read_text(encoding="utf-8")
+ self.assertIn("const syncGoalUrl = () =>", catalog)
+ self.assertIn("url.searchParams.set('goal', goal)", catalog)
+ self.assertIn("url.searchParams.delete('goal')", catalog)
+ self.assertIn("window.history.replaceState(", catalog)
+ self.assertIn("url.pathname + url.search + url.hash", catalog)
+ self.assertEqual(catalog.count(" syncGoalUrl();"), 2)
+
+ def test_filter_url_does_not_capture_private_search_text(self):
+ catalog = (ROOT / "apps.html").read_text(encoding="utf-8")
+ start = catalog.index("const syncGoalUrl = () =>")
+ end = catalog.index(" const fire =", start)
+ sync_code = catalog[start:end]
+ self.assertNotIn("input.value", sync_code)
+ self.assertNotIn("find-app", sync_code)
+
def test_catalog_cards_and_details_preserved(self):
catalog = (ROOT / "apps.html").read_text(encoding="utf-8")
self.assertTrue(catalog_card_count_ok(catalog), "app catalog fell below the 92-card baseline")