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: 6 additions & 0 deletions .github/workflows/app-discovery-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
- scripts/build_catalog.py
- site_counts.json
- tests/catalog-growth.test.mjs
- scripts/publish_app_discovery.sh
- scripts/check_app_discovery_live.sh
- .github/workflows/app-discovery-qa.yml
push:
branches: [main]
Expand All @@ -21,6 +23,8 @@ on:
- scripts/build_catalog.py
- site_counts.json
- tests/catalog-growth.test.mjs
- scripts/publish_app_discovery.sh
- scripts/check_app_discovery_live.sh
- .github/workflows/app-discovery-qa.yml
permissions:
contents: read
Expand All @@ -38,5 +42,7 @@ jobs:
python-version: '3.12'
- 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
- name: Verify search, referral links and guide listings
run: node --test tests/catalog-growth.test.mjs
53 changes: 53 additions & 0 deletions docs/APP_DISCOVERY_GROWTH_RELEASE_2026-10-27.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# App acquisition release and experiment plan (2026-10-27)

## Source of truth / deployment reality

- App catalog: **92** non-hidden Android listings in `apps_data.json` as of site count 2026-10-12. Do not call all 117 created apps Play-published. Hidden entries must never be promoted automatically.
- Production `appdevelopsk.com` is served by nginx on the VPS (`/var/www/html`). GitHub repository `appdevelopsk/appdevelopsk.github.io` **main is not itself the live site**. GitHub Pages publishes from a separate `pages-blank` branch and is not a replacement for the VPS.
- The source PR #4 introduced in `/apps` a searchable, accessible catalog and two new canonical SEO guides. **No successful HTTPS production deployment was established by merging it.**

## Deployment with the existing local `pickly-vps` SSH alias

From a **fresh main** checkout of this site repository, after checking working tree status:

```bash
git pull --ff-only
bash scripts/publish_app_discovery.sh --dry-run
# Review the four changed destination paths. Never pass --delete.
bash scripts/publish_app_discovery.sh --apply
bash scripts/check_app_discovery_live.sh
```

Only these files transfer: `apps.html`, `sitemap.xml`, `guides/quick-games/index.html`, `guides/daily-habits/index.html`. Every other VPS file, including privacy, support, policy, payment and ads assets, stays untouched. To override environment use `APP_SITE_DEPLOY_HOST` and `APP_SITE_DEPLOY_ROOT` with a known-safe absolute path. The script refuses unknown flags/invalid targets.

**Publish gate:** a dry-run is not publication. Only mark live once `check_app_discovery_live.sh` reports HTTP 200 and matches `id="find-app"`, both guide canonical URLs, guide click event and sitemap entries. After manual publication consider Google Search Console URL inspection and sitemap submission if the `appdevelopsk.com` property is available.

## Growth experiment — acquisition, not vanity pageviews

| Funnel stage | Event / source | Decision |
|---|---|---|
| Website entry | GA4 landing page, organic / referral / direct, `appdevelopsk.com/apps` and `/guides/*` | New landing sessions and engaged sessions by source |
| Discovery | `catalog_search_used`, `catalog_filter_used` | What proportion of visitors use discovery controls? These events must not include raw search terms |
| Store intent | `catalog_store_click`, `guide_store_click` with app slug | Store clicks by guide and category vs catalog views |
| Play install | Google Play Console listing visitors, acquisitions and Install Referrer campaign (`apps_catalog`, `quick_games`, `daily_habits`) | Conversion separately for each campaign, geography and released package |
| First app value | App GA4 events (first post, first game result, first photo, etc.) | Do newly acquired users finish core action? |
| Repeat value | Mature D1/D7 cohort with sufficient n, crash-free | Keep acquisition spend/effort only if app delivers retention |

Do not sum the 117 apps' 7-day active-user counts as unique people. Do not interpret clicks as downloads, installs as activated users, or app-source merges as store releases. If a campaign has fewer than ~100 store visitors or D7 cohort n<30, call it directional/inconclusive. Monitor the first 7 days to catch broken links; compare at least 28-day periods for conversion trends while accounting for rollout date and channel mix.

## Safe search-indexing / content integrity

- All guides recommend only 4 existing **non-hidden** catalog apps each; their descriptions are sourced from the public catalog. Guides have no made-up star ratings, screenshots, money claims or app feature promises.
- All cards remain server-rendered so crawlers and no-JavaScript users still see published links. Filters run only when JS works.
- URLs use Play Install Referrer with guide-specific campaigns. Keep referrer and GA4 attribution separate; the actual install remains subject to Google Play behavior.
- If an app is delisted or renamed, edit `apps_data.json`, `scripts/build_catalog.py` and the affected guide together, then run Node tests and `python3 scripts/build_catalog.py --check`.

## Release checklist

- [x] Source changes merged to main (PR #4)
- [x] Static website tests and build-catalog consistency passed on PR #4
- [ ] VPS rsync dry-run reviewed by deploy operator
- [ ] VPS content uploaded, actual HTTPS endpoint verified
- [ ] Sitemap submitted/indexing checked in Search Console
- [ ] GA4 DebugView events observed without search query payload
- [ ] Google Play listing traffic / acquisition comparison recorded after meaningful sample
28 changes: 28 additions & 0 deletions scripts/check_app_discovery_live.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Read-only production smoke. Run after the VPS rsync; no deploy or credentials.
set -euo pipefail
base="${APP_SITE_PUBLIC_BASE:-https://appdevelopsk.com}"
[[ "$base" == 'https://appdevelopsk.com' ]] || { echo 'Expected canonical https://appdevelopsk.com' >&2; exit 2; }
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
ua='Mozilla/5.0 (compatible; SKAppsGrowthCheck/1.0)'
fetch() {
local route="$1" filename="$2"
local code
code=$(curl -sSL --compressed -A "$ua" --max-time 20 --retry 2 --retry-delay 2 \
-o "$tmp/$filename" -w '%{http_code}' "$base$route")
[[ "$code" == 200 ]] || { echo "FAIL HTTP $code $base$route" >&2; exit 1; }
}
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; }
for slug in quick-games daily-habits; 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; }
grep -Fq 'guide_store_click' "$tmp/$slug.html" || { echo "FAIL $slug click measurement missing" >&2; exit 1; }
done
fetch '/sitemap.xml' 'sitemap.xml'
for slug in quick-games daily-habits; do
grep -Fq "https://appdevelopsk.com/guides/$slug/" "$tmp/sitemap.xml" || { echo "FAIL sitemap missing $slug" >&2; exit 1; }
done
echo 'PASS: VPS catalog, two guides, click-tracking markers and sitemap all current (HTTP 200)'
29 changes: 29 additions & 0 deletions scripts/publish_app_discovery.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Publish *only* the app-discovery growth files to the VPS backing appdevelopsk.com.
# Default is a non-destructive dry-run. --apply is an intentional human action.
set -euo pipefail
cd "$(dirname "$0")/.."
mode="${1:---dry-run}"
case "$mode" in
--dry-run) flags=(--dry-run) ;;
--apply) flags=() ;;
*) echo 'Usage: scripts/publish_app_discovery.sh [--dry-run|--apply]' >&2; exit 2 ;;
esac
host="${APP_SITE_DEPLOY_HOST:-pickly-vps}"
root="${APP_SITE_DEPLOY_ROOT:-/var/www/html}"
# Never evaluate an arbitrary SSH target or remote shell path.
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=(apps.html sitemap.xml guides/quick-games/index.html guides/daily-habits/index.html)
for file in "${files[@]}"; do
[[ -s "$file" ]] || { echo "Missing source file: $file" >&2; exit 2; }
done
echo "Source checkout: $(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
echo "Target: $host:$root; mode: $mode"
# --relative recreates only the listed nested file paths. No --delete.
rsync -avR --itemize-changes "${flags[@]}" -- "${files[@]}" "$host:$root/"
if [[ "$mode" == '--apply' ]]; then
echo 'Files transferred. Check CDN/VPS output (the publish command does not prove it is live):'
echo ' bash scripts/check_app_discovery_live.sh'
fi
2 changes: 1 addition & 1 deletion sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<url><loc>https://appdevelopsk.com/contact</loc><lastmod>2026-08-18</lastmod><changefreq>yearly</changefreq><priority>0.4</priority></url>
<url><loc>https://appdevelopsk.com/privacy</loc><lastmod>2026-08-18</lastmod><changefreq>yearly</changefreq><priority>0.4</priority></url>
<url><loc>https://appdevelopsk.com/terms</loc><lastmod>2026-08-18</lastmod><changefreq>yearly</changefreq><priority>0.4</priority></url>
<url><loc>https://appdevelopsk.com/apps</loc><lastmod>2026-08-18</lastmod><changefreq>weekly</changefreq><priority>0.9</priority></url>
<url><loc>https://appdevelopsk.com/apps</loc><lastmod>2026-10-27</lastmod><changefreq>weekly</changefreq><priority>0.9</priority></url>
<url><loc>https://appdevelopsk.com/support/</loc><lastmod>2026-08-18</lastmod><changefreq>weekly</changefreq><priority>0.6</priority></url>
<url><loc>https://appdevelopsk.com/privacy-policy/</loc><lastmod>2026-08-18</lastmod><changefreq>weekly</changefreq><priority>0.3</priority></url>
<url><loc>https://appdevelopsk.com/support/30sec-challenge/</loc><lastmod>2026-08-18</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
Expand Down
Loading