Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ From a clean checkout of the intended production commit:
```bash
python3 -m unittest discover -s scripts -p 'test_*.py' -v
node --test scripts/test_catalog_search_events.cjs
node --test scripts/test_guide_store_events.cjs
python3 scripts/diagnose_growth_deploy.py --root . --output growth-deploy-diagnosis.json
python3 scripts/diagnose_growth_routes.py --output growth-route-diagnosis.json
python3 scripts/check_growth_live.py > growth-live-smoke.json
Expand Down
10 changes: 8 additions & 2 deletions guides/ai-tools/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,14 @@ <h2 id="app-choices">Apps to explore</h2>
</main>
<footer>Links open Google Play. Features, pricing and availability may change by region and app version. The page does not claim any download or ranking performance.</footer>
<script>
document.querySelectorAll('a.play').forEach(a=>a.addEventListener('click',()=>{
if(typeof window.gtag==='function')window.gtag('event','guide_store_click',{guide:'ai-tools',app:a.dataset.app});
document.querySelectorAll('a.play').forEach(link => link.addEventListener('click', () => {
if (typeof window.gtag !== 'function') return;
// Preserve historical event while adding the unified outbound-store funnel.
window.gtag('event', 'guide_store_click', { guide: 'ai-tools', app: link.dataset.app });
window.gtag('event', 'download_click', {
store: 'google_play', location: 'guide', placement: 'guide_card',
app_slug: link.dataset.app, guide: 'ai-tools'
});
}));
</script>

Expand Down
12 changes: 11 additions & 1 deletion guides/daily-habits/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,17 @@ <h1>Daily habit apps for journaling, reflection and routines</h1>
<p>Already know what you want? <a href="/apps?goal=habits">Search all published apps</a> or explore <a href="/guides/quick-games/">quick games</a>.</p>
</main>
<footer>These are apps by SK Apps. The links open Google Play; availability and features depend on your region and installed version. No ranking or performance claim is implied.</footer>
<script>document.querySelectorAll('a.store').forEach(a=>a.addEventListener('click',()=>{if(typeof gtag==='function')gtag('event','guide_store_click',{guide:'daily-habits',app:a.dataset.app})}));</script>
<script>
document.querySelectorAll('a.store').forEach(link => link.addEventListener('click', () => {
if (typeof window.gtag !== 'function') return;
// Preserve historical event while adding the unified outbound-store funnel.
window.gtag('event', 'guide_store_click', { guide: 'daily-habits', app: link.dataset.app });
window.gtag('event', 'download_click', {
store: 'google_play', location: 'guide', placement: 'guide_card',
app_slug: link.dataset.app, guide: 'daily-habits'
});
}));
</script>

<script>
// Reuse the site's app-detail funnel; never send search text or personal data.
Expand Down
10 changes: 8 additions & 2 deletions guides/decision-helpers/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,14 @@ <h2 id="app-choices">Apps to explore</h2>
</main>
<footer>Links open Google Play. Features, pricing and availability may change by region and app version. The page does not claim any download or ranking performance.</footer>
<script>
document.querySelectorAll('a.play').forEach(a=>a.addEventListener('click',()=>{
if(typeof window.gtag==='function')window.gtag('event','guide_store_click',{guide:'decision-helpers',app:a.dataset.app});
document.querySelectorAll('a.play').forEach(link => link.addEventListener('click', () => {
if (typeof window.gtag !== 'function') return;
// Preserve historical event while adding the unified outbound-store funnel.
window.gtag('event', 'guide_store_click', { guide: 'decision-helpers', app: link.dataset.app });
window.gtag('event', 'download_click', {
store: 'google_play', location: 'guide', placement: 'guide_card',
app_slug: link.dataset.app, guide: 'decision-helpers'
});
}));
</script>

Expand Down
12 changes: 11 additions & 1 deletion guides/quick-games/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,17 @@ <h1>Quick mobile games for a short break</h1>
<p>Already know what you want? <a href="/apps?goal=quick-games">Search all published apps</a> or explore <a href="/guides/daily-habits/">daily habits</a>.</p>
</main>
<footer>These are apps by SK Apps. The links open Google Play; availability and features depend on your region and installed version. No ranking or performance claim is implied.</footer>
<script>document.querySelectorAll('a.store').forEach(a=>a.addEventListener('click',()=>{if(typeof gtag==='function')gtag('event','guide_store_click',{guide:'quick-games',app:a.dataset.app})}));</script>
<script>
document.querySelectorAll('a.store').forEach(link => link.addEventListener('click', () => {
if (typeof window.gtag !== 'function') return;
// Preserve historical event while adding the unified outbound-store funnel.
window.gtag('event', 'guide_store_click', { guide: 'quick-games', app: link.dataset.app });
window.gtag('event', 'download_click', {
store: 'google_play', location: 'guide', placement: 'guide_card',
app_slug: link.dataset.app, guide: 'quick-games'
});
}));
</script>

<script>
// Reuse the site's app-detail funnel; never send search text or personal data.
Expand Down
66 changes: 66 additions & 0 deletions scripts/test_guide_store_events.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';
// Run from repository checkout: node --test scripts/test_guide_store_events.cjs
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');

const GUIDES = {
'quick-games': 'a.store',
'daily-habits': 'a.store',
'decision-helpers': 'a.play',
'ai-tools': 'a.play',
};

function exercise(guide, withGtag = true) {
const html = fs.readFileSync(
path.join(__dirname, '..', 'guides', guide, 'index.html'), 'utf8'
);
const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/g)]
.map(m => m[1]);
const handlerSource = scripts.filter(s => s.includes("'guide_store_click'"));
assert.equal(handlerSource.length, 1, 'only one store analytics handler');
const callbacks = {};
const events = [];
const anchor = {
dataset: { app: 'known-app' },
addEventListener(name, fn) { callbacks[name] = fn; },
};
const document = {
querySelectorAll(selector) {
assert.equal(selector, GUIDES[guide]);
return [anchor];
},
};
const window = {};
if (withGtag) window.gtag = (...args) => events.push(args);
vm.runInNewContext(handlerSource[0], { document, window });
assert.equal(typeof callbacks.click, 'function');
callbacks.click();
return events;
}

for (const guide of Object.keys(GUIDES)) {
test(guide + ': store click keeps legacy and unified event', () => {
const events = exercise(guide);
assert.equal(events.length, 2, 'exactly two distinct event names per click');
assert.equal(events[0][0], 'event');
assert.equal(events[0][1], 'guide_store_click');
assert.equal(events[0][2].app, 'known-app');
assert.equal(events[0][2].guide, guide);
assert.equal(events[1][0], 'event');
assert.equal(events[1][1], 'download_click');
assert.equal(events[1][2].app_slug, 'known-app');
assert.equal(events[1][2].guide, guide);
assert.equal(events[1][2].store, 'google_play');
assert.equal(events[1][2].location, 'guide');
assert.equal(events[1][2].placement, 'guide_card');
assert.equal(Object.hasOwn(events[1][2], 'link_url'), false);
});

test(guide + ': no GA4 is a safe no-op', () => {
const events = exercise(guide, false);
assert.equal(events.length, 0);
});
}
Loading