summaryrefslogtreecommitdiff
path: root/project_doot_widget_build.md
diff options
context:
space:
mode:
authorClaude Code <claude@terst.org>2026-08-15 17:59:12 +0000
committerClaude Code <claude@terst.org>2026-08-15 17:59:12 +0000
commiteb0932bddad0daa47c36cd20599944c3dc45482a (patch)
tree4b0013e72af9a56ce496d8aeede7068d36fe4389 /project_doot_widget_build.md
Initial commit: tungsten's memory tree
Diffstat (limited to 'project_doot_widget_build.md')
-rw-r--r--project_doot_widget_build.md30
1 files changed, 30 insertions, 0 deletions
diff --git a/project_doot_widget_build.md b/project_doot_widget_build.md
new file mode 100644
index 0000000..232cf84
--- /dev/null
+++ b/project_doot_widget_build.md
@@ -0,0 +1,30 @@
+---
+name: project-doot-widget-build
+description: "How to build and publish the doot Android widget APK from a fresh sandbox (toolchain bootstrap, signing choice, publish location)"
+metadata:
+ node_type: memory
+ type: project
+ originSessionId: f8b83784-e0ed-462c-8310-7de6aadc9650
+---
+
+**Always `git fetch github && git merge --ff-only github/master` before building.** First time this was done, the sandbox's checkout was 3 days stale and missing 90 commits (a full widget refactor plus recurrence/labels-projects/budgets/multi-day-events/text-size features) — the APK got built and published from that stale tree with none of it. Don't trust `git status`'s "ahead of X by N commits" framing without checking which remote — the `local` remote (`/site/git.terst.org/repos/doot.git`) hits a "dubious ownership" error in sandboxes and its stale tracking ref can make the branch look ahead when it's actually just behind `github/master`. Check divergence against `github/master` specifically (`git log --oneline master..github/master`), and don't run `git config --global --add safe.directory` to silence the `local` remote error without asking first — fixing it isn't necessary since `github` is the remote that matters for this workflow.
+
+**Build environment is not preinstalled in a fresh sandbox** — bootstrap before building:
+- Gradle: `android/gradlew` prefers a system Gradle at `/opt/gradle/gradle-8.6/bin/gradle` and only falls back to the repo's embedded wrapper jar if that's missing. The fallback path is broken in this environment (`NoClassDefFoundError: org/gradle/wrapper/IDownload`), so always install Gradle 8.6 to that exact path first: download `https://services.gradle.org/distributions/gradle-8.6-bin.zip`, unzip to `/opt/gradle/`.
+- Android SDK: install to `/opt/android-sdk` (matches `android/local.properties` → `sdk.dir=/opt/android-sdk`). Use the cmdline-tools zip (`commandlinetools-linux-*_latest.zip` from `dl.google.com/android/repository/`), place under `/opt/android-sdk/cmdline-tools/latest/`, then `sdkmanager --sdk_root=/opt/android-sdk "platform-tools" "platforms;android-34" "build-tools;34.0.0"` (accept licenses with `yes |`). As of 2026-08-06 also installed `"build-tools;36.0.0"` and `"platforms;android-36"` / `"system-images;android-36;google_apis;x86_64"` — see the target-device note below for why.
+
+**Target device is Android 16 (API 36) — minSdk=targetSdk=compileSdk=36 is correct, not a bug to "fix" toward broader compatibility.** This is a single-user app sideloaded onto exactly one phone, never distributed broadly, so "what does the widest range of devices support" is the wrong framing entirely — corrected 2026-08-06 after a prior pass (2026-08-05) wrongly walked minSdk back to 31 assuming "most real devices" needed supporting, without checking what device this actually runs on. Don't re-apply that "fix" — ask or check first if a future session is tempted to lower these again. AGP 8.2.0's bundled D8 prints `API level of 36 is not supported by this compiler` at compileSdk/targetSdk=36 regardless of build-tools;36.0.0 being installed (AGP doesn't delegate to the standalone SDK build-tools binary) — this is harmless noise, not a real problem: verified by actually installing and launching the resulting APK on a real API 36 target (see below), confirmed no crash, `target_sdk_version=36` correctly applied. A genuine fix would mean upgrading AGP itself, a bigger and riskier change not currently justified.
+
+**APK file size jump (11MB -> 27MB) is from minSdk=36 alone, NOT code bloat -- don't re-diagnose this as a Compose/Glance/dependency problem.** Isolated and verified 2026-08-06 by rebuilding the pre-minSdk-36 commit (0400e59) in a throwaway git worktree, changing ONLY minSdk 26->36 with zero other changes, and diffing the packaged APK: `classes.dex` goes from 63%-compressed (Deflate) to 0% (Stored) the moment minSdk crosses whatever threshold AGP uses to auto-switch to uncompressed dex packaging (ART on modern API levels can mmap dex directly from the APK without a decompression step at install time -- larger file, faster/lower-memory install, a deliberate platform tradeoff). An earlier same-day attempt to explain this via `apkanalyzer dex packages` (measuring Compose/Glance/DataStore's share of the dex class tree) was a dead end -- that metric stayed ~11MB in both the small and large builds, because it measures reachable-class-tree size, not the packaged/compressed byte size that actually determines the APK file size. If asked again "what got bigger," check compression method via `unzip -v <apk> classes.dex` (Cmpr column) before reaching for a dependency-based explanation.
+
+**Dedicated API 36 test AVD: `doot_test_api36`** (`system-images;android-36;google_apis;x86_64`, created 2026-08-06, separate from [[project-feedreader-emulator|feedreader_test]] -- don't reuse that one for doot, it's a different project's dedicated resource). Boot with `emulator -avd doot_test_api36 -no-window -no-audio -no-boot-anim`, then poll both `getprop sys.boot_completed` AND `getprop dev.bootcomplete` (both must be `1`) before `adb install` -- installing too early fails with `Can't find service: package` or `Error: device is still booting.` even after `sys.boot_completed=1` alone. Use this AVD to verify installability/launch after any build.gradle.kts SDK-version change, the same way the minSdk=36→31 mistake was originally caught: don't just declare a target API and assume it works, actually install onto a real matching target and confirm no crash via `logcat | grep FATAL`.
+
+**Build debug, not release:**
+- `android/app/build.gradle.kts` has a `release` signingConfig pointing at `android/doot-widget.keystore` — that file is gitignored and not present in any sandbox seen so far (only exists wherever the original release key was generated, possibly the deploy host or a personal machine — never found it).
+- Even if it were available, **prefer the debug build anyway**: sideloaded release-signed APKs from a low-reputation self-signed key get flagged hard by Google Play Protect ("app blocked as harmful"). Sibling projects [[project-doot-prod|can-can and nav]] avoid this by never wiring a release signingConfig at all and just shipping debug-signed APKs for sideloading (nav even has a checked-in `app-debug.apk`). Matched that pattern for doot's widget.
+- Command: `cd /workspace/doot/android && export ANDROID_HOME=/opt/android-sdk && ./gradlew :app:assembleDebug`
+- Output: `android/app/build/outputs/apk/debug/app-debug.apk`
+
+**Publish location:** copy the built APK to `/site/static.terst.org/public/files/doot-widget.apk`, then `chown www-data:www-data` it. A `doot-widget.apk` already existed there from a prior session (this is the established convention/URL the user installs from), so this is an overwrite-in-place, not a new path.
+
+**How to apply:** This publish step is a plain static-file write, not a service restart or DB migration — it does **not** fall under the "only deploy on tungsten" rule in [[project-doot-prod]] (deploy host migrated from titanium to tungsten 2026-07-17). That rule is specifically about the doot *server* (systemctl restart, dashboard.db migrations). Publishing the widget APK to the static file server is safe to do from any sandbox as long as `/site/static.terst.org/public/files/` is reachable.