duolingo.id Open ↗ Has Plan
68
DOM 75 PLN 55 REV 85 EAS 60
CLAUDE.md ×
plan.md
prd.md ×
tsconfig.json ×
PREVIEW

duolingo.id — Implementation Plan

Phase 0 — CF wiring (5 min)

  • Zone exists on CF (domain registered). Account f5dbc4f1c3e4f3fbdacb83107b8088da.
  • DNS: apex A 192.0.2.1 proxied + www CNAME apex proxied (placeholders; Worker handles all).
  • Worker routes after first deploy: duolingo.id/*, www.duolingo.id/*.
  • Whitelist duolingo.id in web/xmoj.com/worker-cf/src/whitelist.js if using kie z-image piped through xmoj.

Phase 1 — Repo scaffold

Directory: web/duolingo.id/{worker,web,seed,scripts}. See prd.md for visual decisions.

Decision Choice Why
Render style React SPA + Worker API Rich lesson UX
Worker router Plain fetch URL match (no Hono) Matches astaga.net / nujum.id
React router React Router v7 Already standard (berkas.org)
Styling Tailwind + CSS vars Easy theme swap
State Zustand Light
Auth Google OIDC + WA OTP via japri.com Reuse idmu.org / japri.com infra
Audio TTS → R2 cache by SHA1(text+voice) Free, cacheable
Image kie.ai z-image via xmoj → R2 forever $0.0043/img
Mascot Custom Garuda SVG Avoid Duo IP, env-swappable
Analytics CF Web Analytics + events table No cookie banner

Phase 2 — D1 schema

See schema.sql. Core tables: scripts, characters, words, units, lessons, exercises, users, user_lessons, user_state, srs_items, league_cohorts, quests, friends, events.

Binding DBduolingo-db. Create with wrangler d1 create duolingo-db.

Phase 3 — Bindings

See wrangler.toml. Bindings: DB (D1), CACHE (KV), MEDIA (R2), AI (CF Workers AI), ASSETS (Pages-style asset binding for built SPA). Secrets: KIE_API_KEY, OPENROUTER_API_KEY, JWT_SECRET, GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, WA_RELAY_TOKEN, CRON_SECRET.

Phase 4 — Worker API routes

Path Purpose
GET /api/health smoke
POST /api/auth/google OIDC code → JWT
POST /api/auth/wa-otp/send WA OTP
POST /api/auth/wa-otp/verify verify → JWT
GET /api/me user + state
GET /api/courses list scripts
GET /api/courses/:script/path path nodes + progress
GET /api/lessons/:id lesson + exercises
POST /api/lessons/:id/submit grade attempt
GET /api/leaderboard cohort
GET /api/quests active quests
POST /api/quests/:id/claim claim reward
GET /api/shop / POST /api/shop/buy shop
GET /api/practice SRS due
POST /api/events analytics
GET /api/tts?text= TTS R2 cache
GET /img/:slug.webp kie z-image on demand
* SPA fallback via ASSETS

Phase 5 — Indonesian copy mapping

EN ID
Streak Beruntun
XP Poin
Hearts Nyawa
Gems Berlian
Path Jalur
Unit Bab
Section Babak
Quests Misi
Practice Latihan
Stories Cerita
Shop Toko
Friends Teman
Leagues Perunggu, Perak, Emas, Safir, Rubi, Zamrud, Kecubung, Mutiara, Obsidian, Intan

Phase 6 — Content authoring workflow

  1. Hardcode 23 Lontara chars in seed/02_lontara_chars.sql.
  2. AI-generate words/sentences via scripts/gen_content.mjs (OpenRouter Gemini Flash Lite).
  3. TTS each char + word via local pro/tts-service → R2 cache by SHA1.
  4. Stroke-order SVGs scraped from Wikipedia (CC-BY where applicable).
  5. Hourly cron auto-generates next lessons so users never hit a "no more content" wall.

Phase 7 — Lesson player UX

Each lesson = 6–10 exercises (~3 min). Types: translit, choose, tap-pair, listen, build-word. Each wrong = −1 nyawa. End-of-lesson: confetti + Garu cheer + XP summary.

Phase 8 — Engagement loops

  • Daily: streak meter, 3 daily Misi, web push at 19:00 WIB.
  • Weekly: league promotion Sun 23:59 WIB, weekly Misi.
  • Social: friend streaks, share-progress link.
  • Pay: Super (Rp 39k/mo, Rp 299k/yr), gems IAP.

Phase 9 — Deploy steps

cd web/duolingo.id
wrangler d1 create duolingo-db                 # → fill database_id in wrangler.toml
wrangler kv namespace create CACHE             # → fill id
wrangler r2 bucket create duolingo-media
wrangler d1 execute duolingo-db --remote --file=schema.sql
for f in seed/*.sql; do wrangler d1 execute duolingo-db --remote --file=$f; sleep 5; done
cd web && npm install && npm run build && cd ..
wrangler secret put KIE_API_KEY
wrangler secret put OPENROUTER_API_KEY
wrangler secret put JWT_SECRET
wrangler secret put CRON_SECRET
wrangler deploy

Verify:

curl https://duolingo.id/api/health   # → {ok:true}
curl -I https://duolingo.id/          # → 200, HTML

Phase 10 — Beads epic

File ucok-duo once scaffold lands. Children mirror this plan's phases.

Risks

See prd.md §10.

Step 1 done = SUCCESS criteria

  • https://duolingo.id/ serves SPA shell
  • /api/health returns {ok:true}
  • Lontara unit 1, lesson 1 loads + grades a translit exercise
  • League cohort row exists for first signup
  • Lighthouse mobile Perf ≥ 85, SEO 100

⚙ HARD CONSTRAINTS (enforced for all sites)

This domain MUST operate within these constraints — no exceptions:

  • 100% Cloudflare serverless — Workers + D1 + R2 + KV + Workers AI + Vectorize. NEVER PM2, NEVER VPS, NEVER Docker in production path.
  • 100% AI-automated — every customer interaction, every moderation decision, every transaction reconcile = AI. No manual queue, no live human chat support, no physical fulfillment.
  • 1-operator solo — one person can run the entire operation from a phone. No team meetings, no shared inbox, no shift rotation.
  • WhatsApp AI bot for all support (24/7, instant response, no SLA promises that need humans).
  • Mayar QRIS for all Indonesian payments (subscription auto-renew, no manual invoicing).
  • Indonesian UI primary — bahasa-first, English fallback only where unavoidable.
  • Privacy — opt-in only, delete-on-request honored within 24h (cron-driven).
  • No physical goods, no inventory — digital products + affiliate referrals only.

If the plan above describes any flow that violates these constraints, treat the plan as ASPIRATIONAL only and rework before building. The constraint trifecta wins.

AI ASSISTANT

Ask AI to research, improve, or generate content.

Try: "Research competitors for this niche"

Actions