diff --git a/.github/workflows/app-discovery-qa.yml b/.github/workflows/app-discovery-qa.yml new file mode 100644 index 00000000..de838efa --- /dev/null +++ b/.github/workflows/app-discovery-qa.yml @@ -0,0 +1,42 @@ +name: Public App Discovery and SEO QA +on: + pull_request: + branches: [main] + paths: + - apps.html + - apps_data.json + - guides/** + - sitemap.xml + - scripts/build_catalog.py + - site_counts.json + - tests/catalog-growth.test.mjs + - .github/workflows/app-discovery-qa.yml + push: + branches: [main] + paths: + - apps.html + - apps_data.json + - guides/** + - sitemap.xml + - scripts/build_catalog.py + - site_counts.json + - tests/catalog-growth.test.mjs + - .github/workflows/app-discovery-qa.yml +permissions: + contents: read +jobs: + discovery-quality: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Confirm catalog generator remains idempotent + run: python3 scripts/build_catalog.py --check + - name: Verify search, referral links and guide listings + run: node --test tests/catalog-growth.test.mjs diff --git a/apps.html b/apps.html index f95e1b77..16c56d2e 100644 --- a/apps.html +++ b/apps.html @@ -27,6 +27,18 @@ .store-badge { display:inline-block; margin-top:.4rem; font-size:.78rem; font-weight:600; color:var(--accent); } footer { text-align:center; color:var(--sub); font-size:.85rem; padding:2rem; } footer a { color:var(--accent); } + /* Browse purpose or search without hiding cards from non-JS crawlers. */ + .finder{margin:0 0 1.5rem;padding:1.1rem;background:var(--card);border:1px solid var(--line);border-radius:14px} + .finder label{display:block;font-weight:700;margin-bottom:.5rem} + .finder input{width:100%;font:inherit;color:var(--ink);background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:.8rem .9rem} + .finder input:focus-visible,.finder button:focus-visible,.finder a:focus-visible{outline:2px solid var(--accent);outline-offset:3px} + .finder-filters{display:flex;flex-wrap:wrap;gap:.5rem;margin:1rem 0 .6rem} + .finder-filters button{cursor:pointer;font:inherit;font-size:.85rem;border:1px solid var(--line);border-radius:999px;padding:.4rem .8rem;background:var(--bg);color:var(--ink)} + .finder-filters button[aria-pressed="true"]{background:var(--accent);color:white;border-color:var(--accent)} + .finder-meta{color:var(--sub);font-size:.82rem;margin:.4rem 0} + .finder-guides{margin-top:.6rem;display:flex;flex-wrap:wrap;gap:1rem;font-size:.9rem} + .finder-guides a{color:var(--accent);font-weight:600} + [hidden]{display:none !important} @@ -39,6 +51,13 @@ + +
@@ -47,6 +66,23 @@

All Apps

92 free apps on Google Play — productivity, education, health, games and more.

+
+ + +
+ + + + + +
+

Showing all apps

+ +
+
HangTrue - Level Checker icon @@ -789,5 +825,62 @@

Chaos - Stranger Missions

+ diff --git a/guides/daily-habits/index.html b/guides/daily-habits/index.html new file mode 100644 index 00000000..1e85fcad --- /dev/null +++ b/guides/daily-habits/index.html @@ -0,0 +1,44 @@ + + + + + +Daily habit apps for journaling, reflection and routines | SK Apps + + + + + + + + + + + + + + +
+ +

Daily habit apps for journaling, reflection and routines

+

A repeatable routine starts with a small action you can finish. Choose one habit to start with, rather than setting up several trackers at once.

+
+
+

How to choose

  1. Prefer to record a moment visually? Try taking a daily photo and see how a timeline develops.
  2. +
  3. Prefer writing and reflection? Use future-self prompts or a gratitude journal.
  4. +
  5. Need a simple reminder? Try a hydration reminder instead of a longer journaling ritual.
+

Apps to explore

+

Each option is different. Choose one that matches the kind of break or routine you want, then check its Google Play listing for supported features and availability.

+
Daylapse: Daily Timelapse app icon

For one photo a day

Daylapse: Daily Timelapse

Take one photo a day, keep your streak and watch it become a timelapse

See on Google Play
+
FutureMe - AI Prediction app icon

For future-self reflections

FutureMe - AI Prediction

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

See on Google Play
+
Water Reminder - Hydration app icon

For a hydration routine

Water Reminder - Hydration

Water tracker and hydration reminders to drink enough water every day

See on Google Play
+
Daily Reflect app icon

For gratitude and daily reflection

Daily Reflect

Daily journal for gratitude, wins & mood — build a positive daily habit

See on Google Play
+
+

Already know what you want? Search all published apps or explore quick games.

+
+
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.
+ + + diff --git a/guides/quick-games/index.html b/guides/quick-games/index.html new file mode 100644 index 00000000..5e7f9fe2 --- /dev/null +++ b/guides/quick-games/index.html @@ -0,0 +1,44 @@ + + + + + +Quick mobile games for a short break | SK Apps + + + + + + + + + + + + + + +
+ +

Quick mobile games for a short break

+

Not every break needs a long session. Decide whether you want to spot clues, give an opinion, solve a small logic problem, or answer trivia.

+
+
+

How to choose

  1. Want a visual surprise? Choose an image-guessing game where the picture becomes clearer as you play.
  2. +
  3. Want to react and compare opinions? Choose a community voting game instead of a puzzle.
  4. +
  5. Want to think rather than react? Pick a logic challenge or a trivia quiz.
+

Apps to explore

+

Each option is different. Choose one that matches the kind of break or routine you want, then check its Google Play listing for supported features and availability.

+
Blur: Guess the Image app icon

For picture guessing

Blur: Guess the Image

Guess the photo as it slowly unblurs — a fun image guessing brain game

See on Google Play
+
Instant Judge: Vote in 3s app icon

For quick opinion votes

Instant Judge: Vote in 3s

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

See on Google Play
+
Nodo - Daily Node Puzzle app icon

For logic puzzles

Nodo - Daily Node Puzzle

Daily logic puzzle: solve number node networks and climb the leaderboard

See on Google Play
+
BrainBurst - Daily Trivia Quiz app icon

For daily trivia

BrainBurst - Daily Trivia Quiz

Daily trivia quiz with 1v1 battles and fresh AI questions to grow your knowledge

See on Google Play
+
+

Already know what you want? Search all published apps or explore daily habits.

+
+
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.
+ + + diff --git a/sitemap.xml b/sitemap.xml index 5c86a883..043a1c23 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -323,4 +323,6 @@ https://appdevelopsk.com/terms/face-vibe/2026-08-18yearly0.3 https://appdevelopsk.com/terms/smile-score/2026-08-18yearly0.3 https://appdevelopsk.com/csae-standards/2026-08-18yearly0.3 + https://appdevelopsk.com/guides/quick-games/2026-10-27monthly0.7 + https://appdevelopsk.com/guides/daily-habits/2026-10-27monthly0.7 diff --git a/tests/catalog-growth.test.mjs b/tests/catalog-growth.test.mjs new file mode 100644 index 00000000..5868ca9f --- /dev/null +++ b/tests/catalog-growth.test.mjs @@ -0,0 +1,71 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import {Script,runInNewContext} from 'node:vm'; +const read = p => readFileSync(new URL('../'+p, import.meta.url),'utf8'); +const data = JSON.parse(read('apps_data.json')); +const catalog = read('apps.html'); +const sitemap = read('sitemap.xml'); +const guides = ['quick-games','daily-habits']; + +test('catalog search retains 92 real indexed store cards and passes install referrer',()=>{ + const visible=data.filter(x=>!x.hidden); + const tags=[...catalog.matchAll(/t[1].includes('id='+entry.pkg)),entry.app); + } + assert.ok(tags.every(t=>t[1].includes('referrer=utm_source%3Dappdevelopsk'))); + assert.match(catalog,/id="find-app"/); + assert.match(catalog,/id="finder-count"[^>]*role="status"/); +}); + +test('filters work client-side without tracking raw search text',()=>{ + const scripts=[...catalog.matchAll(/]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]); + 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 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);}}}); + assert.equal(count.textContent,'3 apps shown'); + buttons[1].events.click(); + assert.equal(count.textContent,'1 apps shown'); + assert.equal(cards[0].hidden,false); + assert.equal(cards[1].hidden,true); + buttons[0].events.click(); + input.value='personal-email@example.com';input.events.input(); + assert.equal(count.textContent,'0 apps shown'); + assert.equal(empty.hidden,false); + assert.ok(!JSON.stringify(events).includes('personal-email')); + input.value='image';input.events.input(); + assert.equal(cards[0].hidden,false); + cards[0].events.click(); + assert.ok(events.some(e=>e[1]==='catalog_store_click'&&e[2].app==='blur')); +}); + +test('guide landing pages link only to listed published applications and are discoverable',()=>{ + for(const slug of guides){ + const page=read('guides/'+slug+'/index.html'); + assert.match(sitemap,new RegExp('https://appdevelopsk\\.com/guides/'+slug+'/')); + assert.match(catalog,new RegExp('href="/guides/'+slug+'/"')); + assert.match(page,//); + assert.match(page,/gtag\('event','guide_store_click'/); + const apps=[...page.matchAll(/data-app="([a-z0-9-]+)"/g)].map(m=>m[1]); + assert.equal(apps.length,4); + for(const a of apps){ + const record=data.find(x=>x.app===a); + assert.ok(record&&!record.hidden,slug+' invalid app '+a); + assert.match(page,new RegExp('id='+record.pkg.replaceAll('.','\\.'))); + } + assert.doesNotMatch(page,/aggregateRating|ratingValue|100% free|guaranteed/i); + } +});