med-det-samme/CLAUDE.md
Ole-Morten Duesund b0ff434257 CLAUDE.md: toolchain pins, load-bearing contracts, command quirks from build session
Captures what cost time this session: AGP 9 built-in Kotlin (KGP-bundled
version pinning), KSP's standalone versioning blocking Kotlin 2.4,
JAVA_HOME requirement, release signing flow, emulator verification
recipes, and the new code contracts (DoseActions single-path, backup
DTO/enum names as file-format contract, CCTV gate on kage bumps).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:46:21 +02:00

6.1 KiB

«Med det samme» — Claude Code project memory

Local-first Android medication reminder. My personal MyTherapy replacement: no ads, no account, no analytics, no cloud service of yours. Single user, single device. Reminders are the entire point — reliability beats polish wherever they trade off. The name says the brief: nag me to take the dose now.

  • Package / applicationId: no.naiv.meddetsamme
  • Display name: Med det samme (in strings.xml, referenced from the manifest)
  • minSdk 26, targetSdk 35, JVM 17, Kotlin + Compose (Material3)
  • Stack: Room (+KSP), WorkManager, OkHttp, kotlinx-serialization, kage (age crypto). Gradle Kotlin DSL + version catalog. No AWS SDK, no DI framework.

Working agreement

  • Senior dev, Linux host. Be concise; explain why for non-obvious choices in a line or two (XAI, not essays). 24h clocks everywhere.
  • Never claim it builds without running the build. Verify; don't assert.
  • Verify current stable library versions yourself before bumping — don't trust memory.
  • Smallest change that works. No speculative abstraction.
  • Ask before anything irreversible (schema migrations, data deletion).
  • This is a medication app: wrong schedule/dose math is a real harm. Keep schedule logic pure and unit-tested before anything depends on it.

Decisions — don't relitigate (flag and wait if you think one's wrong)

  1. Native, not PWA — PWA notification reliability is unacceptable for meds.
  2. Exact alarms (USE_EXACT_ALARM), always gated on canScheduleExactAlarms() with an inexact fallback; setExactAndAllowWhileIdle so Doze doesn't eat doses.
  3. Re-arm every alarm on BOOT_COMPLETED + MY_PACKAGE_REPLACED (state is wiped then).
  4. Surface a one-time battery-optimisation exemption prompt — biggest cause of dropped reminders on aggressive OEMs.
  5. No Google Auto Backup (allowBackup="false", excluded from cloud-backup + transfer).
  6. Own backup: versioned JSON → encrypted → S3-compatible PUT to self-hosted Garage, path-style, hand-rolled SigV4. One serializer for export, import, and auto-backup.
  7. Crypto target is age (passphrase/scrypt) so backups are age -d-decryptable and never lock me into this app. JCE AES-GCM is the baseline behind an interface; verify age against the age test vectors before trusting it.
  8. FEST, not Felleskatalogen, and not as a live API: its open Rekvirent extract is a SOAP/WCF M30 XML dump. The phone reads a slim pre-flattened JSON synced from my server (that job lives outside this repo) and does autocomplete offline.
  9. Refill is derived from inventory + consumption. Prescription renewal (rxExpiryEpochDay, refillsRemaining) is tracked separately from stock.

Toolchain (verified 2026-06, re-verify before bumping)

  • AGP 9 has built-in Kotlin: applying org.jetbrains.kotlin.android is an ERROR. The compose/serialization plugin versions must match AGP's bundled KGP (2.3.10 for AGP 9.2), not the newest Kotlin.
  • Kotlin stays 2.3.x until KSP supports 2.4 (KSP versions standalone since 2.3.0: plain "2.3.9").
  • compileSdk 37 (AndroidX hard-requires), targetSdk 35 — separate knobs, keep both.
  • Room 3 (androidx.room3) is alpha — stay on Room 2.x. room-ktx is merged into room-runtime.
  • androidx security-crypto is fully deprecated — SettingsStore is our own Keystore wrapper instead.
  • No appcompat: ?attr/colorControlNormal doesn't exist; Compose tints icons via LocalContentColor.

Load-bearing — change with care

  • The reminder subsystem is the reliability core. After any change, re-verify: alarms re-arm on boot/update, escalation cancels on Taken, the next occurrence is always armed, Taken decrements inventory.
  • Escalation contract: due dose → PENDING log + notify, re-nag every 10 min (cap ~6 / ~1 h) until Taken or Snooze; Snooze pushes the next nag 15 min and stays PENDING.
  • The S3 SigV4 signer: if a PUT fails it's almost always clock skew or a non-path-style endpoint, not the maths. Don't "fix" the signer first.
  • The schedule engine is pure — add tests here before touching the math.
  • DoseActions is the ONLY implementation of Taken/Snooze/Skip/Undo, shared by the notification receiver and UI. Never add a second path — they'd disagree about inventory.
  • Backup DTO field names AND enum constant names (MedForm/DoseStatus) are part of the backup-file contract; renaming breaks restore of old backups. Bump BackupFile.VERSION instead.
  • kage is trusted only because CctvScryptVectorTest passes — keep the vendored CCTV vectors green when bumping kage.
  • Doctor PDF formatting is locale-pinned to nb; the default locale prints English month names.

Out of scope — don't add without asking

Caregiver/"Team" alerts, multi-profile, Health Connect, streaks/gamification, injection-site tracking, symptom/mood diary. (Modelling "trackable" generically is fine if cheap; build no diary UI.)

Commands

All gradlew calls need JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 — system java is a JDK 25 EA.

  • Build debug: ./gradlew assembleDebug
  • Unit tests: ./gradlew test
  • Lint: ./gradlew lint
  • Release: ./gradlew assembleRelease — signs via gitignored keystore.properties (keystore in ~/.keystores/; file absent → unsigned build, don't "fix" that)
  • adb/emulator are not on PATH: ~/android-sdk/platform-tools/adb, ~/android-sdk/emulator/emulator
  • Emulator verify: create a throwaway AVD from system-images;android-35;google_apis;x86_64, delete after. NEVER use the personal tilfluktsrom AVD.
  • Inspect device DB: adb shell run-as no.naiv.meddetsamme sqlite3 databases/med-det-samme.db "'SQL'"
  • Verify alarms: adb shell dumpsys alarm | grep meddetsammewindow=0 means exact
  • The phone (Pixel 7 Pro) has a RELEASE-signed install: adb install of debug builds is rejected on signature; use release builds for the phone, emulator for debug.