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
8 changes: 6 additions & 2 deletions .github/workflows/app-discovery-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
12 changes: 12 additions & 0 deletions .github/workflows/live-acquisition-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 * * *'
Expand Down
14 changes: 7 additions & 7 deletions apps.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ <h3>SheetDecode: Excel Formula Fix</h3>
<img src="icons/apps/debate-me.png" alt="Debate Me - AI Debate Practice icon" loading="lazy" width="64" height="64">
<div class="app-card-body">
<h3>Debate Me - AI Debate Practice</h3>
<p>AI debate practice to sharpen your arguments and win any discussion</p>
<p>Practice debating with an AI opponent, get feedback and share your results</p>
<span class="store-badge">Google Play &rarr;</span>
</div>
</a>
<a class="app-card" href="https://play.google.com/store/apps/details?id=jp.appdevelopsk.roast_me_app&amp;utm_source=appdevelopsk&amp;utm_medium=web&amp;utm_campaign=apps_catalog&amp;referrer=utm_source%3Dappdevelopsk%26utm_medium%3Dweb%26utm_campaign%3Dapps_catalog" target="_blank" rel="noopener">
<img src="icons/apps/roast-me.png" alt="RoastMe - Photo Roast &amp; Rate icon" loading="lazy" width="64" height="64">
<img src="icons/apps/roast-me.png" alt="RoastMe - Community Roasts icon" loading="lazy" width="64" height="64">
<div class="app-card-body">
<h3>RoastMe - Photo Roast &amp; Rate</h3>
<p>Post a photo and get roasted and rated by an anonymous global community</p>
<h3>RoastMe - Community Roasts</h3>
<p>Post a topic, get community feedback and write a kind or blunt roast</p>
<span class="store-badge">Google Play &rarr;</span>
</div>
</a>
Expand Down Expand Up @@ -194,7 +194,7 @@ <h3>Gratitude Challenge</h3>
<img src="icons/apps/idea-market.png" alt="Idea Market icon" loading="lazy" width="64" height="64">
<div class="app-card-body">
<h3>Idea Market</h3>
<p>Business idea marketplace: buy &amp; sell startup ideas or find your next venture</p>
<p>Explore startup ideas, read public previews and post your own business concept</p>
<span class="store-badge">Google Play &rarr;</span>
</div>
</a>
Expand Down Expand Up @@ -378,7 +378,7 @@ <h3>Daily Reflect</h3>
<img src="icons/apps/future-me.png" alt="FutureMe - AI Prediction icon" loading="lazy" width="64" height="64">
<div class="app-card-body">
<h3>FutureMe - AI Prediction</h3>
<p>AI future predictions: write letters to your future self about love &amp; career</p>
<p>Explore playful AI future scenarios, revisit predictions and plan tomorrow</p>
<span class="store-badge">Google Play &rarr;</span>
</div>
</a>
Expand Down Expand Up @@ -578,7 +578,7 @@ <h3>Travel Safety Map &amp; Scam Alert</h3>
<img src="icons/apps/instant-judge.png" alt="Instant Judge: Vote in 3s icon" loading="lazy" width="64" height="64">
<div class="app-card-body">
<h3>Instant Judge: Vote in 3s</h3>
<p>Vote on photos &amp; scenarios in 3 seconds and see what the community thinks</p>
<p>Vote on community dilemmas, post your own and see results when revealed</p>
<span class="store-badge">Google Play &rarr;</span>
</div>
</a>
Expand Down
12 changes: 6 additions & 6 deletions apps_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
{
Expand Down Expand Up @@ -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
},
{
Expand Down Expand Up @@ -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
},
{
Expand Down Expand Up @@ -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
},
{
Expand Down
7 changes: 7 additions & 0 deletions scripts/check_app_discovery_live.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
94 changes: 94 additions & 0 deletions scripts/publish_stale_policy_pages.sh
Original file line number Diff line number Diff line change
@@ -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"<head[\s\S]*?</head>|<script[\s\S]*?</script>|<style[\s\S]*?</style>", " ", 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
58 changes: 58 additions & 0 deletions tests/catalog-positioning-regression.test.mjs
Original file line number Diff line number Diff line change
@@ -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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#x27;');

test('catalog cards reproduce the canonical visible app titles and descriptions', () => {
const visible = apps.filter(a=>!a.hidden);
const cards = [...page.matchAll(/<a class="app-card" href="([^"]+)"[\s\S]*?<h3>([^<]+)<\/h3>\s*<p>([^<]*)<\/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('<p>'+encode(desc)+'</p>'));
}
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'),/<html/i);
}
assert.match(script,/--dry-run/);
assert.match(script,/--check-live/);
assert.match(script,/--apply/);
assert.match(script,/rsync -avR --itemize-changes/);
assert.match(script,/python3 - "\$file" "\$tmp"/);
assert.doesNotMatch(script,/rsync[^\n]*--delete/);
assert.doesNotMatch(script,/rsync[^\n]*-e\s+sh/);
});
3 changes: 3 additions & 0 deletions tests/search-landing-growth.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ test('daily live acquisition check is read-only, scheduled and fails visibly on
const workflow=read('.github/workflows/live-acquisition-smoke.yml');
assert.match(workflow,/cron: '40 0 \* \* \*'/);
assert.match(workflow,/workflow_dispatch:/);
assert.match(workflow,/push:\s*\n\s*branches: \[main\]/);
assert.match(workflow,/support\/\*\*/);
assert.match(workflow,/apps_data\.json/);
assert.match(workflow,/contents: read/);
assert.match(workflow,/bash scripts\/check_app_discovery_live\.sh/);
assert.doesNotMatch(workflow,/publish_app_discovery\.sh --apply\s*\n\s*(?:git|rsync|bash)/);
Expand Down
Loading