diff --git a/.github/workflows/app-discovery-qa.yml b/.github/workflows/app-discovery-qa.yml index df61a62..566082c 100644 --- a/.github/workflows/app-discovery-qa.yml +++ b/.github/workflows/app-discovery-qa.yml @@ -29,8 +29,10 @@ on: - scripts/build_catalog.py - site_counts.json - tests/catalog-growth.test.mjs + - tests/catalog-positioning-regression.test.mjs - tests/search-landing-growth.test.mjs - scripts/publish_app_discovery.sh + - scripts/publish_stale_policy_pages.sh - scripts/check_app_discovery_live.sh - .github/workflows/app-discovery-qa.yml - .github/workflows/live-acquisition-smoke.yml @@ -63,8 +65,10 @@ on: - scripts/build_catalog.py - site_counts.json - tests/catalog-growth.test.mjs + - tests/catalog-positioning-regression.test.mjs - tests/search-landing-growth.test.mjs - scripts/publish_app_discovery.sh + - scripts/publish_stale_policy_pages.sh - scripts/check_app_discovery_live.sh - .github/workflows/app-discovery-qa.yml - .github/workflows/live-acquisition-smoke.yml @@ -85,6 +89,6 @@ jobs: - name: Confirm catalog generator remains idempotent run: python3 scripts/build_catalog.py --check - name: Verify non-destructive release scripts have valid shell syntax - run: bash -n scripts/publish_app_discovery.sh scripts/check_app_discovery_live.sh + run: bash -n scripts/publish_app_discovery.sh scripts/check_app_discovery_live.sh scripts/publish_stale_policy_pages.sh - name: Verify search, referral links and guide listings - run: node --test tests/catalog-growth.test.mjs tests/search-landing-growth.test.mjs tests/dotchi-support-growth.test.mjs tests/debate-me-support-growth.test.mjs tests/idea-market-support-growth.test.mjs tests/blur-support-growth.test.mjs tests/instant-judge-support-growth.test.mjs tests/roastme-support-growth.test.mjs tests/futureme-support-growth.test.mjs + run: node --test tests/catalog-growth.test.mjs tests/catalog-positioning-regression.test.mjs tests/search-landing-growth.test.mjs tests/dotchi-support-growth.test.mjs tests/debate-me-support-growth.test.mjs tests/idea-market-support-growth.test.mjs tests/blur-support-growth.test.mjs tests/instant-judge-support-growth.test.mjs tests/roastme-support-growth.test.mjs tests/futureme-support-growth.test.mjs diff --git a/.github/workflows/live-acquisition-smoke.yml b/.github/workflows/live-acquisition-smoke.yml index a977385..b5d7472 100644 --- a/.github/workflows/live-acquisition-smoke.yml +++ b/.github/workflows/live-acquisition-smoke.yml @@ -2,6 +2,18 @@ name: Live App Acquisition Pages Smoke on: workflow_dispatch: + # Alert immediately when source/main changes, not only at the daily 09:40 JST cron. + # This is deliberately read-only; a failed run means the VPS is stale. + push: + branches: [main] + paths: + - apps.html + - apps_data.json + - support/** + - guides/** + - scripts/check_app_discovery_live.sh + - scripts/publish_app_discovery.sh + - .github/workflows/live-acquisition-smoke.yml schedule: # 09:40 JST daily. Scheduled GitHub jobs may run later than cron. - cron: '40 0 * * *' diff --git a/apps.html b/apps.html index 72c079d..d1e29bc 100644 --- a/apps.html +++ b/apps.html @@ -106,15 +106,15 @@

SheetDecode: Excel Formula Fix

Debate Me - AI Debate Practice icon

Debate Me - AI Debate Practice

-

AI debate practice to sharpen your arguments and win any discussion

+

Practice debating with an AI opponent, get feedback and share your results

Google Play →
- RoastMe - Photo Roast & Rate icon + RoastMe - Community Roasts icon
-

RoastMe - Photo Roast & Rate

-

Post a photo and get roasted and rated by an anonymous global community

+

RoastMe - Community Roasts

+

Post a topic, get community feedback and write a kind or blunt roast

Google Play →
@@ -194,7 +194,7 @@

Gratitude Challenge

Idea Market icon

Idea Market

-

Business idea marketplace: buy & sell startup ideas or find your next venture

+

Explore startup ideas, read public previews and post your own business concept

Google Play →
@@ -378,7 +378,7 @@

Daily Reflect

FutureMe - AI Prediction icon

FutureMe - AI Prediction

-

AI future predictions: write letters to your future self about love & career

+

Explore playful AI future scenarios, revisit predictions and plan tomorrow

Google Play →
@@ -578,7 +578,7 @@

Travel Safety Map & Scam Alert

Instant Judge: Vote in 3s icon

Instant Judge: Vote in 3s

-

Vote on photos & scenarios in 3 seconds and see what the community thinks

+

Vote on community dilemmas, post your own and see results when revealed

Google Play →
diff --git a/apps_data.json b/apps_data.json index c2b8440..2d92f41 100644 --- a/apps_data.json +++ b/apps_data.json @@ -17,14 +17,14 @@ "app": "debate-me", "pkg": "jp.appdevelopsk.debate_me", "title": "Debate Me - AI Debate Practice", - "desc": "AI debate practice to sharpen your arguments and win any discussion", + "desc": "Practice debating with an AI opponent, get feedback and share your results", "installs": 28 }, { "app": "roast-me", "pkg": "jp.appdevelopsk.roast_me_app", - "title": "RoastMe - Photo Roast & Rate", - "desc": "Post a photo and get roasted and rated by an anonymous global community", + "title": "RoastMe - Community Roasts", + "desc": "Post a topic, get community feedback and write a kind or blunt roast", "installs": 25 }, { @@ -94,7 +94,7 @@ "app": "idea-market", "pkg": "jp.appdevelopsk.ideamarket", "title": "Idea Market", - "desc": "Business idea marketplace: buy & sell startup ideas or find your next venture", + "desc": "Explore startup ideas, read public previews and post your own business concept", "installs": 16 }, { @@ -279,7 +279,7 @@ "app": "future-me", "pkg": "com.futureme.future_me", "title": "FutureMe - AI Prediction", - "desc": "AI future predictions: write letters to your future self about love & career", + "desc": "Explore playful AI future scenarios, revisit predictions and plan tomorrow", "installs": 14 }, { @@ -470,7 +470,7 @@ "app": "instant-judge", "pkg": "com.instantjudge.instant_judge", "title": "Instant Judge: Vote in 3s", - "desc": "Vote on photos & scenarios in 3 seconds and see what the community thinks", + "desc": "Vote on community dilemmas, post your own and see results when revealed", "installs": 13 }, { diff --git a/scripts/check_app_discovery_live.sh b/scripts/check_app_discovery_live.sh index 380924b..250dc7d 100644 --- a/scripts/check_app_discovery_live.sh +++ b/scripts/check_app_discovery_live.sh @@ -20,6 +20,13 @@ fetch '/apps' 'apps.html' grep -Fq 'id="find-app"' "$tmp/apps.html" || { echo 'FAIL catalog search is stale' >&2; exit 1; } grep -Fq 'catalog_store_click' "$tmp/apps.html" || { echo 'FAIL store-click attribution is stale' >&2; exit 1; } grep -Fq 'Dotchi: This or That Polls' "$tmp/apps.html" || { echo 'FAIL Dotchi catalog still presents an anime-only app' >&2; exit 1; } +# Source metadata is not proof that the public catalog was updated. +# Check the actual live HTML for each corrected high-priority app description. +grep -Fq 'Practice debating with an AI opponent, get feedback and share your results' "$tmp/apps.html" || { echo 'FAIL catalog description not deployed: Practice debating with a' >&2; exit 1; } +grep -Fq 'Post a topic, get community feedback and write a kind or blunt roast' "$tmp/apps.html" || { echo 'FAIL catalog description not deployed: Post a topic, get commun' >&2; exit 1; } +grep -Fq 'Vote on community dilemmas, post your own and see results when revealed' "$tmp/apps.html" || { echo 'FAIL catalog description not deployed: Vote on community dilemm' >&2; exit 1; } +grep -Fq 'Explore playful AI future scenarios, revisit predictions and plan tomorrow' "$tmp/apps.html" || { echo 'FAIL catalog description not deployed: Explore playful AI futur' >&2; exit 1; } +grep -Fq 'Explore startup ideas, read public previews and post your own business concept' "$tmp/apps.html" || { echo 'FAIL catalog description not deployed: Explore startup ideas, r' >&2; exit 1; } for slug in quick-games daily-habits decision-helpers ai-tools; do fetch "/guides/$slug/" "$slug.html" grep -Fq "https://appdevelopsk.com/guides/$slug/" "$tmp/$slug.html" || { echo "FAIL guide $slug content/canonical mismatch" >&2; exit 1; } diff --git a/scripts/publish_stale_policy_pages.sh b/scripts/publish_stale_policy_pages.sh new file mode 100644 index 0000000..bd2da87 --- /dev/null +++ b/scripts/publish_stale_policy_pages.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Recover only the 17 PUBLIC policy pages flagged STALE (HTTP 200) by +# GitHub Actions 40608832623 on 2026-10-28. The app acquisition publisher +# deliberately does NOT cover privacy policies. +# +# --dry-run (default): safe rsync preview, no remote changes. +# --apply: user-owned VPS rsync, never deletes anything. +# --check-live: read-only compare of visible text on canonical HTTPS pages. +set -euo pipefail +cd "$(dirname "$0")/.." +mode="${1:---dry-run}" +case "$mode" in --dry-run|--apply|--check-live) ;; *) + echo 'Usage: scripts/publish_stale_policy_pages.sh [--dry-run|--apply|--check-live]' >&2 + exit 2 ;; +esac +slugs=( + battery-charging-map + dinnerhomeroulette + drift + ear-train + gratitude-log + heritageatlas + heritagetrace + mood-log + name-maker + nowsnap + posture-reminder + rental-inspector + smile-score + solace + tappitsu-battle + wait-time + water-reminder +) +files=() +for slug in "${slugs[@]}"; do + path="privacy-policy/$slug/index.html" + [[ -s "$path" ]] || { echo "Missing source file: $path" >&2; exit 2; } + files+=("$path") +done + +if [[ "$mode" == '--check-live' ]]; then + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + ua='Mozilla/5.0 (compatible; SKAppsPolicyRecovery/1.0)' + failed=0 + for file in "${files[@]}"; do + url="https://appdevelopsk.com/${file%index.html}" + code=$(curl -sSL --compressed -A "$ua" --max-time 20 --retry 2 --retry-delay 2 \ + -o "$tmp" -w '%{http_code}' "$url") || code=000 + if [[ "$code" != 200 ]]; then + echo "FAIL HTTP $code $url" >&2 + failed=$((failed+1)) + continue + fi + if ! python3 - "$file" "$tmp" "$url" <<'PY' +import html, re, sys +def visible(path): + with open(path, encoding="utf-8", errors="replace") as f: + source = f.read() + source = re.sub(r"||", " ", source, flags=re.I) + return re.sub(r"\s+", " ", html.unescape(re.sub(r"<[^>]+>", " ", source))).strip() +if visible(sys.argv[1]) != visible(sys.argv[2]): + print("FAIL STALE", sys.argv[3]) + sys.exit(1) +PY + then + failed=$((failed+1)) + else + echo "PASS $url" + fi + sleep 0.35 + done + echo "Checked ${#files[@]} public policy pages; failures: $failed" + (( failed == 0 )) + exit $? +fi + +host="${APP_SITE_DEPLOY_HOST:-pickly-vps}" +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 +flags=(--dry-run) +[[ "$mode" == '--apply' ]] && flags=() +echo "Policy recovery: ${#files[@]} already public paths, target $host:$root; mode $mode" +echo "Source checkout: $(git rev-parse --short HEAD 2>/dev/null || echo unknown)" +# Non-destructive. Never use --delete, and never publish unpublished paths. +rsync -avR --itemize-changes "${flags[@]}" -- "${files[@]}" "$host:$root/" +if [[ "$mode" == '--apply' ]]; then + echo 'Upload attempted. Confirm live pages with:' + echo ' bash scripts/publish_stale_policy_pages.sh --check-live' +fi diff --git a/tests/catalog-positioning-regression.test.mjs b/tests/catalog-positioning-regression.test.mjs new file mode 100644 index 0000000..cd02b6f --- /dev/null +++ b/tests/catalog-positioning-regression.test.mjs @@ -0,0 +1,58 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +const read = path => readFileSync(new URL('../'+path,import.meta.url),'utf8'); +const apps = JSON.parse(read('apps_data.json')); +const page = read('apps.html'); +const highPriority = { + 'debate-me': 'Practice debating with an AI opponent, get feedback and share your results', + 'roast-me': 'Post a topic, get community feedback and write a kind or blunt roast', + 'instant-judge': 'Vote on community dilemmas, post your own and see results when revealed', + 'future-me': 'Explore playful AI future scenarios, revisit predictions and plan tomorrow', + 'idea-market': 'Explore startup ideas, read public previews and post your own business concept', +}; +const encode = value => value.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); + +test('catalog cards reproduce the canonical visible app titles and descriptions', () => { + const visible = apps.filter(a=>!a.hidden); + const cards = [...page.matchAll(/([^<]+)<\/h3>\s*

([^<]*)<\/p>[\s\S]*?<\/a>/g)]; + assert.equal(cards.length,visible.length); + for (const a of visible) { + const match=cards.filter(m=>m[1].includes('id='+a.pkg)); + assert.equal(match.length,1,a.app+' missing or duplicate'); + assert.equal(match[0][2],encode(a.title),a.app+' title drift'); + assert.equal(match[0][3],encode(a.desc),a.app+' description drift'); + assert.match(match[0][1],/referrer=utm_source%3Dappdevelopsk/); + } +}); +test('five high-traffic-intent entries describe genuinely implemented core value',()=>{ + for (const [slug,desc] of Object.entries(highPriority)) { + const a=apps.find(x=>x.app===slug&&!x.hidden); + assert.ok(a,'published app missing: '+slug); + assert.equal(a.desc,desc); + assert.ok(page.includes('

'+encode(desc)+'

')); + } + const titles=Object.fromEntries(apps.map(x=>[x.app,x.title])); + assert.equal(titles['roast-me'],'RoastMe - Community Roasts'); + assert.doesNotMatch(apps.filter(x=>Object.hasOwn(highPriority,x.app)).map(x=>x.title+' '+x.desc).join('\n'),/win any discussion|write letters to your future self|Post a photo and get roasted and rated|buy & sell startup ideas/i); +}); +test('production smoke must verify these five catalog descriptions, not only source',()=>{ + const live=read('scripts/check_app_discovery_live.sh'); + for(const desc of Object.values(highPriority)) assert.ok(live.includes(desc.slice(0,Math.min(35,desc.length))),'live smoke misses '+desc); + assert.match(live,/FAIL catalog description not deployed/); +}); + +test('privacy-policy recovery is constrained to 17 observed stale public routes',()=>{ + const script=read('scripts/publish_stale_policy_pages.sh'); + for(const slug of ["battery-charging-map","dinnerhomeroulette","drift","ear-train","gratitude-log","heritageatlas","heritagetrace","mood-log","name-maker","nowsnap","posture-reminder","rental-inspector","smile-score","solace","tappitsu-battle","wait-time","water-reminder"]) { + assert.ok(script.includes(' '+slug+'\n'), 'missing verified stale policy: '+slug); + assert.match(read('privacy-policy/'+slug+'/index.html'),/