Status: Phase 3 scaffolding committed. Toolchain decision locked in 2026-05-12: devcontainer stays lean, Windows host gets cmdline-tools only, releases build via GitHub Actions. No Android Studio. No emulator.
Read this once. The same workflow handles Phase 4 (native session reliability) and Phase 5 (polish + device testing) without further setup.
┌──────────────────────────────────────────────────────────────────┐
│ WSL devcontainer │
│ └─ bun run dev ← Nuxt HMR server on :3000 │
│ └─ bun run android:dev ← prints the LAN URL + Windows commands │
└─────────────────┬────────────────────────────────────────────────┘
│ HMR over LAN
▼
┌──────────────────────────────────────────────────────────────────┐
│ Real Android phone (Android 12+, on the same Wi-Fi) │
│ └─ Ta-Da! debug APK, WebView pointed at http://<wsl-ip>:3000 │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Windows host (one-time installer only) │
│ └─ JDK 17 + Android cmdline-tools │
│ └─ ./gradlew installDebug │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ GitHub Actions (release builds) │
│ └─ Tag v*-android → builds signed AAB + debug APK │
└──────────────────────────────────────────────────────────────────┘
You only need this so you can run gradlew installDebug once to put a debug build on your phone. After that, the devcontainer does almost all the work.
Powershell as admin:
winget install Microsoft.OpenJDK.17Restart your shell, then verify:
java -version # should show "openjdk version 17.x.x"-
Download from
https://developer.android.com/studio#command-line-tools-only— the "Command line tools only" zip (Windows). -
Extract somewhere temporary first — the zip contains a single
cmdline-tools/folder. You need to end up with this exact layout (Google's hard-coded convention):C:\android-sdk\ cmdline-tools\ latest\ bin\ sdkmanager.bat avdmanager.bat lib\ NOTICE.txt source.propertiesThe most common mistake is dragging the whole
cmdline-tools/folder intolatest/, which gives youC:\android-sdk\cmdline-tools\latest\cmdline-tools\bin\...— one level too deep.sdkmanager.batmust be atcmdline-tools\latest\bin\sdkmanager.batexactly.If you've already done that, fix in place:
Move-Item C:\android-sdk\cmdline-tools\latest\cmdline-tools\* C:\android-sdk\cmdline-tools\latest\ Remove-Item C:\android-sdk\cmdline-tools\latest\cmdline-tools
-
Set environment variables (System Properties → Environment Variables):
ANDROID_HOME=C:\android-sdk- Add to
PATH:%ANDROID_HOME%\cmdline-tools\latest\bin,%ANDROID_HOME%\platform-tools
-
Close every Powershell window and open a fresh one so the new env vars are picked up. Then verify:
where.exe sdkmanager.bat # should print C:\android-sdk\cmdline-tools\latest\bin\sdkmanager.bat
-
Install the SDK pieces we need:
sdkmanager --licenses # accept all sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
That's the entire native toolchain. ~1GB total.
- On your phone: Settings → About phone → tap Build number 7 times.
- Settings → Developer options → toggle USB debugging.
- Plug phone into laptop via USB. On Windows Powershell:
adb devices # should list your phoneApprove the RSA fingerprint prompt on the phone the first time.
After pulling main to your Windows checkout:
cd path\to\tada\app
bun install # picks up Capacitor devDeps
bun run android:sync # build:capacitor + cap sync
cd android
./gradlew installDebugTa-Da! shows up on the phone's app drawer. The bundled build hits https://tada.living/api/* for now (good for smoke-testing login + offline cache).
This is the workflow you'll use 95% of the time.
cd app
bun run dev # starts Nuxt dev server on :3000 with HMRcd app
bun run android:dev # prints the LAN URL the phone needs + the exact commandsSample output:
Dev-server URL the phone WebView should load: http://172.17.0.4:3000
Step 2. On Windows (Powershell, in the app/ directory):
$env:CAP_SERVER_URL = "http://172.17.0.4:3000"
bunx cap sync android
cd android
./gradlew installDebug
Paste the commands the helper printed. The phone reinstalls with the dev URL baked in.
Open Ta-Da! on the phone. Save a file in the devcontainer. The phone updates in <1s.
Two common causes:
- Phone left Wi-Fi. Reconnect.
- Windows firewall woke up. The helper script prints the exact
netsh interface portproxy+New-NetFirewallRulecommands to run in elevated Powershell. One-off.
$env:CAP_SERVER_URL = "" # or just `Remove-Item Env:CAP_SERVER_URL`
bunx cap sync android
cd android
./gradlew installDebugSame flow, no live reload. Useful when you're not actively iterating and want the phone to keep working when the laptop's off.
The workflow at .github/workflows/android-release.yml does the work. Two flavours:
Push any v*-android tag and CI builds a debug APK as an artifact. Useful for closed-tester distribution before signing is set up:
git tag v0.7.0-alpha1-android
git push origin v0.7.0-alpha1-androidWait ~5 min. Download tada-debug-apk from the workflow run's Artifacts. Send to testers via any channel.
For Play Store uploads. You'll do this once you've generated the signing keystore — not yet.
Repo secrets needed:
ANDROID_SIGNING_KEY— base64-encoded contents ofrelease.keystoreANDROID_KEY_ALIAS— the alias name (e.g.tada)ANDROID_KEY_PASSWORD— key passwordANDROID_STORE_PASSWORD— keystore password
Generate the keystore once on Windows:
keytool -genkey -v -keystore tada-release.keystore -alias tada -keyalg RSA -keysize 2048 -validity 10000Back the keystore up to two places off your laptop before doing anything else (password manager + encrypted cloud backup). Losing it means a new Play Store app listing forever — no recovery.
Base64-encode it for the GitHub secret:
[Convert]::ToBase64String([IO.File]::ReadAllBytes("tada-release.keystore")) | Set-ClipboardPaste into the repo secret. Add the alias/password secrets. Push a v0.7.0-android tag → CI builds the signed AAB and attaches it to the GitHub release. Upload that AAB to Play Console.
The workflow has a workflow_dispatch input for apiBaseUrl — handy if you want to build an APK that points at a self-host backend (or staging). Actions tab → Android Release → Run workflow → fill in the URL.
- ✅ Signup + identity verification (already done by you).
- ⏳ Recruit 12 closed-testers. New individual accounts must run a closed-testing track with ≥12 opted-in testers for 14 continuous days before promoting to production. Family + IRL friends + anyone who'll press buttons on a beta. Doesn't block anything yet, but you want the list ready before the first AAB upload.
- ⏳ App listing — created after the first AAB upload. Bundle ID
living.tada.appreserves the name. - ⏳ App content questionnaire — privacy policy URL (
tada.living/privacy✅), data-collection declarations (we collect entries, account email, optional payment via Stripe), target audience (17+, no third-party tracking).
- No paid signup; community-run.
- Submission flow: fork
https://gitlab.com/fdroid/fdroiddata, addmetadata/living.tada.app.ymlpointing at a GitHub release, open an MR. Reviews take weeks to months. - Action when you have a v1.0 GitHub release: skim a few existing recipes in fdroiddata (
CapacitororCordovaprojects), draft the metadata YAML, sit on it until after the Play Store version is out. - F-Droid only accepts reproducible builds. Our GitHub Actions workflow is the reproducible-build trail — same toolchain pinning, same source. Stripe and other proprietary deps load only in cloud mode, so we should be clean.
- Phase 4 (native session reliability). The
useSessionNotificationscomposable will detect Capacitor at runtime (viaCapacitor.isNativePlatform()) and switch from SW-scheduled bells to@capacitor/local-notifications. A small KotlinServicegets registered for the foreground service. - Phase 5 (polish + device matrix). Real-device testing on a few OEMs — Samsung A-series and Xiaomi are the battery-killers we most need to confirm. You'll lend / borrow / Buy-Used the test fleet.
- Phase 6 (distribution). Signing keystore generation, assetlinks.json on tada.living for App Link verification, Play Console upload, F-Droid MR.
None of these require Android Studio.
- Phone can't reach
http://<wsl-ip>:3000— run thenetsh interface portproxy+New-NetFirewallRulesnippet the helper prints. Test from Windows Powershell withcurl http://<wsl-ip>:3000first. adb devicesshows nothing — USB cable might be charge-only. Swap. Or check that USB debugging is still enabled (Android sometimes auto-disables it after a system update).- Login works on Live Reload but breaks in self-contained debug APK — the credentials/CORS fix from Phase 3.2 only takes effect after
cap syncruns. Re-runbun run android:syncfrom inside the devcontainer if you've changednuxt.config.tsor the api-client plugin. - Splash flashes white before brand colour — Phase 5 polish; Capacitor's
androidScaleType: "CENTER_CROP"should fix it. File it but don't block on it.