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
6 changes: 4 additions & 2 deletions scripts/check_app_discovery_live.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ grep -Fq 'data-position="hero"' "$tmp/dotchi.html" || { echo 'FAIL Dotchi store
grep -Fq 'const FEAT_T={en:{0:"Choose A or B on a question."' "$tmp/dotchi.html" || { echo 'FAIL Dotchi translated feature claims still stale' >&2; exit 1; }
# Read-only release gate for the recently corrected Debate Me SEO landing.
fetch '/support/debate-me/' 'debate-me.html'
grep -Fq 'AI Debate Practice & Argument Skills' "$tmp/debate-me.html" || grep -Fq 'AI Debate Practice & Argument Skills' "$tmp/debate-me.html" || { echo 'FAIL Debate Me product title is not live' >&2; exit 1; }
# #78 retitled the page from GSC evidence: "AI Debate Practice & Feedback".
grep -Fq 'AI Debate Practice & Feedback' "$tmp/debate-me.html" || grep -Fq 'AI Debate Practice & Feedback' "$tmp/debate-me.html" || { echo 'FAIL Debate Me product title is not live' >&2; exit 1; }
grep -Fq 'data-position="hero"' "$tmp/debate-me.html" || { echo 'FAIL Debate Me first-screen Play CTA not live' >&2; exit 1; }
grep -Fq 'data-position="details"' "$tmp/debate-me.html" || { echo 'FAIL Debate Me details Play CTA missing' >&2; exit 1; }
grep -Fq 'support_debate_me_hero' "$tmp/debate-me.html" || { echo 'FAIL Debate Me Play attribution missing' >&2; exit 1; }
Expand Down Expand Up @@ -111,5 +112,6 @@ if grep -Eiq 'Letters to future self|Time capsule feature|FutureMe Premium.*[0-9
echo 'FAIL FutureMe support page contains outdated feature or pricing claims' >&2; exit 1
fi
fetch '/robots.txt' 'robots.txt'
grep -Fq 'Allow: /icons/apps/' "$tmp/robots.txt" || { echo 'FAIL Google image crawl allow not live' >&2; exit 1; }
# #53 broadened the allow to /icons/ (covers /icons/apps/).
grep -Eq '^Allow: /icons/(apps/)?$' "$tmp/robots.txt" || { echo 'FAIL Google image crawl allow not live' >&2; exit 1; }
echo 'PASS: catalog, guides, priority search landings, store CTAs, icon crawl policy and sitemap current (HTTP 200)'
5 changes: 4 additions & 1 deletion scripts/check_growth_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ def check(path, key, valid):
check("/apps", "store_click_tracking", "catalog_store_click" in catalog and "download_click" in catalog)
check("/apps", "detail_click_tracking", "catalog_app_detail_click" in catalog)
check("/apps", "finalized_search_analytics",
"input.addEventListener('input', render)" in catalog and
# #77: the input handler re-renders and clears the dedupe key, but never reports per keystroke.
"input.addEventListener('input', () => {" in catalog and
"if (!clean(input.value)) lastReportedSearch = '';" in catalog and
"input.addEventListener('input', reportSearch)" not in catalog and
"input.addEventListener('blur', reportSearch)" in catalog and
"if (event.key === 'Enter') reportSearch()" in catalog and
"query === lastReportedSearch" in catalog and
Expand Down
2 changes: 1 addition & 1 deletion scripts/publish_app_discovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ root="${APP_SITE_DEPLOY_ROOT:-/var/www/html}"
if [[ ! "$host" =~ ^[a-zA-Z0-9_.@-]+$ || ! "$root" =~ ^/var/www/[a-zA-Z0-9_./-]+$ ]]; then
echo 'Invalid APP_SITE_DEPLOY_HOST or APP_SITE_DEPLOY_ROOT' >&2; exit 2
fi
files=(index.html apps.html sitemap.xml robots.txt guides/quick-games/index.html guides/daily-habits/index.html guides/decision-helpers/index.html guides/ai-tools/index.html support/brain-burst/index.html support/snaplingo/index.html support/face-reaction-battle/index.html support/twotaku/index.html support/debate-me/index.html support/idea-market/index.html support/blur/index.html support/instant-judge/index.html support/roast-me/index.html support/future-me/index.html support/tappitsu-battle/index.html)
files=(index.html apps.html sitemap.xml robots.txt guides/quick-games/index.html guides/daily-habits/index.html guides/decision-helpers/index.html guides/ai-tools/index.html support/brain-burst/index.html support/snaplingo/index.html support/face-reaction-battle/index.html support/twotaku/index.html support/debate-me/index.html support/idea-market/index.html support/blur/index.html support/instant-judge/index.html support/roast-me/index.html support/future-me/index.html support/tappitsu-battle/index.html support/taletime/index.html support/dog-breeds-encyclopedia/index.html)
for file in "${files[@]}"; do
[[ -s "$file" ]] || { echo "Missing source file: $file" >&2; exit 2; }
done
Expand Down
5 changes: 3 additions & 2 deletions scripts/test_growth_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def valid_pages():
pages["/apps"] = (
'<script type="application/ld+json">' + json.dumps(listing) + '</script>' +
cards + " catalog_store_click download_click catalog_app_detail_click " +
"input.addEventListener('input', render) input.addEventListener('blur', reportSearch) " +
"input.addEventListener('input', () => { if (!clean(input.value)) lastReportedSearch = ''; render(); }); " +
"input.addEventListener('blur', reportSearch) " +
"if (event.key === 'Enter') reportSearch() query === lastReportedSearch " +
"lastReportedSearch = query " +
"const app = slug(card), content = clean((card.textContent || '') + ' ' + app; app.replace(/-/g, ' ')" +
Expand Down Expand Up @@ -217,7 +218,7 @@ def test_store_schema_package_mismatch_fails(self):
def test_first_keystroke_analytics_rejected(self):
data = valid_pages()
data["/apps"] = data["/apps"].replace(
"input.addEventListener('input', render)",
"input.addEventListener('input', () => { if (!clean(input.value)) lastReportedSearch = ''; render(); }); ",
"input.addEventListener('input', reportSearch)")
self.assertFalse(inspect(data)["passed"])

Expand Down
6 changes: 3 additions & 3 deletions tests/catalog-growth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ test('filters work client-side without tracking raw search text',()=>{
const source=scripts.find(s=>s.includes('catalog_store_click'));
assert.ok(source); new Script(source);
function el(extra={}) {return {events:{},hidden:false,...extra,addEventListener(name,fn){this.events[name]=fn;},setAttribute(key,value){this[key]=value;}}}
const input=el({value:''}),count=el({textContent:''}),empty=el();
const input=el({value:''}),count=el({textContent:''}),empty=el(),reset=el({focus(){}});
const buttons=['all','quick-games','habits','decisions','ai'].map(goal=>el({dataset:{goal}}));
const cards=[
el({textContent:'Blur: Guess the Image Guess the photo',querySelector:()=>({getAttribute:()=> 'icons/apps/blur.png'})}),
el({textContent:'FutureMe - AI Prediction write future letters',querySelector:()=>({getAttribute:()=> 'icons/apps/future-me.png'})}),
el({textContent:'Food Roulette pick a restaurant',querySelector:()=>({getAttribute:()=> 'icons/apps/food-roulette.png'})}),
];
const events=[];
runInNewContext(source,{document:{getElementById(id){return {'find-app':input,'finder-count':count,'finder-empty':empty}[id];},querySelectorAll(q){return q==='.grid .app-card'?cards:buttons;}},window:{gtag(...args){events.push(args);}}});
runInNewContext(source,{document:{referrer:'',getElementById(id){return {'find-app':input,'finder-count':count,'finder-empty':empty,'finder-reset':reset}[id];},querySelectorAll(q){return q==='.grid .app-card'?cards:q==='[data-detail-app]'?[]:buttons;}},URLSearchParams,URL,window:{location:{search:'',href:'https://appdevelopsk.com/apps',origin:'https://appdevelopsk.com'},history:{state:null,replaceState(){}},gtag(...args){events.push(args);}}});
assert.equal(count.textContent,'3 apps shown');
buttons[1].events.click();
assert.equal(count.textContent,'1 apps shown');
Expand Down Expand Up @@ -155,5 +155,5 @@ test('home features public Dotchi instead of an unavailable app, with 17 languag
assert.equal(typeof translations[lang].twotaku,'string',lang+' must localize Dotchi caption');
assert.ok(translations[lang].twotaku.length>=12,lang+' missing meaningful caption');
}
assert.match(home,/gtag\('event', 'app_view'/,'homepage app-card clicks remain measurable');
assert.match(home,/gtag\('event', 'app_detail_click', \{[^}]*placement: 'homepage_card'/,'homepage app-card clicks remain measurable');
});
2 changes: 1 addition & 1 deletion tests/debate-me-support-growth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function dictionary(name, after) {
return runInNewContext('('+match[1]+')');
}
test('first paint shows real AI debate and never a food spinner', () => {
assert.match(html, /<title>Debate Me: AI Debate Practice for Android/);
assert.match(html, /<title>Debate Me – AI Debate Practice &(amp;)? Feedback/);
assert.match(html, /<h1>Debate Me<\/h1>/);
assert.match(html, /<div class="hero-emoji">🗣️<\/div>/);
assert.match(html, /<div class="hero-cat">Education<\/div>/);
Expand Down
3 changes: 2 additions & 1 deletion tests/dotchi-support-growth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ test('Dotchi support publication is part of the non-destructive VPS release gate
assert.match(release,/support\/twotaku\/index\.html/);
assert.match(live,/support\/twotaku/);
assert.match(live,/FAIL Dotchi anime-only support content still live/);
assert.match(sitemap,/https:\/\/appdevelopsk\.com\/support\/twotaku\/<\/loc><lastmod>2026-10-27/);
const dotchiLastmod=sitemap.match(/https:\/\/appdevelopsk\.com\/support\/twotaku\/<\/loc><lastmod>(\d{4}-\d{2}-\d{2})/)?.[1];
assert.ok(dotchiLastmod&&dotchiLastmod>='2026-10-27','Dotchi sitemap lastmod must not regress before 2026-10-27');
});
4 changes: 2 additions & 2 deletions tests/search-landing-growth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ test('unavailable Face Reaction Battle stays unavailable and offers distinct pub

test('public catalog icons are crawlable and explicit VPS release is non-destructive',()=>{
const robots=read('robots.txt');
assert.match(robots,/Allow: \/icons\/apps\//);
assert.match(robots,/Allow: \/icons\/(apps\/)?$/m);
const pub=read('scripts/publish_app_discovery.sh');
assert.match(pub,/--dry-run/);
assert.match(pub,/support\/brain-burst\/index\.html/);
Expand All @@ -65,7 +65,7 @@ test('public catalog icons are crawlable and explicit VPS release is non-destruc
assert.doesNotMatch(pub,/^rsync[^\n]*--delete/m);
const live=read('scripts/check_app_discovery_live.sh');
assert.match(live,/data-position="hero"/);
assert.match(live,/Allow: \/icons\/apps\//);
assert.ok(live.includes("Allow: /icons/(apps/)?"));
});

test('daily live acquisition check is read-only, scheduled and fails visibly on stale VPS',()=>{
Expand Down
Loading