med-det-samme/med-det-samme-build-prompt.md
Ole-Morten Duesund 7256c49112 Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set
Milestone 1 of the build brief. Version catalog pins verified against
Maven Central / Google Maven on 2026-06-10; Kotlin held at 2.3.10 to
match AGP 9.2's bundled KGP (built-in Kotlin) because KSP has no
Kotlin 2.4 release yet. compileSdk 37 (forced by core-ktx 1.19),
targetSdk 35 per brief. allowBackup=false + full dataExtractionRules
opt-out; own encrypted backup comes in milestone 5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:31:39 +02:00

9.1 KiB

Build prompt — "Med det samme" (Android medication reminder)

You are building a native Android app from scratch. Read this whole brief first, restate your build plan as milestones, and wait for my go-ahead before writing code.

What and why

A local-first medication reminder — my personal replacement for MyTherapy, which I left over ads and tracking. Single user, single device, no account, no analytics, no cloud service of yours. Reminders are the entire point, so reliability beats polish everywhere there's a trade-off. The name "Med det samme" ("right away") is the design brief in three words: it nags you to take the dose now.

  • applicationId / package: no.naiv.meddetsamme
  • Display name: Med det samme (in strings.xml, referenced from the manifest)
  • minSdk 26, targetSdk 35, JVM 17, Kotlin + Jetpack Compose

How I want you to work

  • Linux dev host. I'm a senior dev — be concise, skip hand-holding, explain why for non-obvious choices in a line or two (XAI, not essays). 24h clocks everywhere.
  • Build in the milestones below, one at a time. After each, run the build/tests and show me it actually compiles — never claim it builds without running it.
  • Verify current stable versions of every library yourself (web search if needed) before pinning them in a Gradle version catalog. Don't trust versions from memory.
  • This is a medication app: wrong schedule/dose math is a real harm. The scheduling logic must be pure and unit-tested before anything depends on it.
  • Ask before anything irreversible (schema you'll have to migrate, etc.). Prefer the smallest change that works; no speculative abstraction.

Stack

Kotlin, Compose (Material3), Room (+KSP), WorkManager, OkHttp, AndroidX security-crypto, kotlinx-serialization. Gradle Kotlin DSL with a version catalog. No AWS SDK. No third-party DI framework — manual wiring is fine at this size.

Non-negotiable decisions (don't relitigate; flag and wait if you disagree)

  1. Native, not PWA. PWA notification reliability is unacceptable for medication.
  2. Exact alarms via USE_EXACT_ALARM, always gated on canScheduleExactAlarms() with a graceful inexact fallback; use setExactAndAllowWhileIdle so Doze doesn't swallow doses.
  3. Re-arm on boot and app update (BOOT_COMPLETED + MY_PACKAGE_REPLACED). AlarmManager state is wiped then; rebuild every alarm from the DB.
  4. Battery-optimisation exemption: declare the permission and surface a one-time runtime prompt. This is the biggest cause of dropped reminders on Samsung/Xiaomi.
  5. No Google Auto BackupallowBackup="false", exclude from cloud-backup and device-transfer. We run our own backup.
  6. Own backup: versioned JSON → encrypted → S3-compatible PUT to a self-hosted Garage bucket, path-style, with a hand-rolled SigV4 signer. One serializer serves export, import, and auto-backup.
  7. Crypto: target is age (passphrase/scrypt mode) so backups are decryptable from the CLI with age -d and never lock me into this app. Ship a working dependency-free baseline first (PBKDF2-HMAC-SHA256 → AES-256-GCM, self-describing header) behind an interface, then implement the age path (Jagged or kage) and verify it against the age Community Cryptography Test Vectors before trusting it.
  8. FEST, not Felleskatalogen, for drug data — and not as a live API. See below.
  9. Refill is derived from inventory + consumption rate, not a manual reminder. Prescription renewal is tracked separately from stock (you can have pills but a dead e-resept).

Features

Core: multiple medications (tablet/capsule/liquid/injection/drops/spray/other), flexible schedules, dose logging (taken/skipped/snoozed) → adherence history, inventory tracking with derived refill warnings, encrypted export/import + automatic backup.

Plus these four (deliberately chosen from a MyTherapy feature review):

  • Escalating reminders: on a due dose, create a PENDING log and re-notify every 10 min (cap ~6 times / ~1 h) until I tap Taken or Snooze. Taken cancels escalation and decrements inventory; Snooze pushes the next nag out 15 min and leaves it PENDING.
  • Per-day / weekend-different times: model a dose-time as time-of-day + a day-of-week bitmask (+ an every-N-days option). Multiple rows per med give different times on different days. Cyclic/taper should extend from the same model.
  • Doctor summary: a human-readable one-page PDF (current meds, schedule, supply, Rx expiry, recent adherence %) via the platform PdfDocument — no PDF dependency. Distinct from the machine-readable JSON backup.
  • Rx renewal fields: prescription expiry date and refills-remaining (reiterutleveringer), independent of physical stock.

Out of scope — do not add without asking: caregiver/"Team" alerts, multi-profile, Health Connect, streaks/gamification, injection-site tracking, symptom/mood diary. (Model "trackable" generically if it's cheap, but build no UI for the diary stuff.)

Data model (anchor, not gospel — improve if warranted, but flag changes)

  • Medication: name, strength, unit, form, withFood, notes, inventoryUnits, packageSize, lowStockLeadDays, rxExpiryEpochDay?, refillsRemaining?, atcCode?, active.
  • DoseTime: medId(FK), minuteOfDay, amount, daysOfWeekMask (bit 0 = Sunday), intervalDays, anchorEpochDay.
  • DoseLog: medId, doseTimeId, scheduledAtMillis, amount, status (PENDING/TAKEN/SKIPPED/SNOOZED), actionedAtMillis?.

Put all schedule math in a pure object (next-occurrence from mask+interval, average daily consumption, days-of-supply, needs-refill, needs-renewal). The alarm layer holds at most one pending alarm per dose-time and recomputes from the DB — so reboot, update, and "taken" all just re-ask the engine. No drift.

Backup details

  • Slim S3 client: path-style PUT https://endpoint/bucket/key, SigV4 signed (host + x-amz-content-sha256 + x-amz-date), x-amz-content-sha256 = hex SHA-256 of the body. Endpoint points at my tailnet host behind Caddy (TLS terminated there).
  • WorkManager daily periodic job; inexact is fine (a late backup is harmless, unlike a dose). Each backup is a distinct timestamped object; retention is handled by a lifecycle Expiration rule on the bucket, not by the app.
  • Credentials (endpoint, region [Garage default often "garage"], bucket, access/secret) and the optional auto-backup passphrase in Keystore-backed EncryptedSharedPreferences. Be honest in comments: auto-backup must store the passphrase to run unattended, so it only protects against bucket compromise; manual export should prompt for a typed passphrase and never store it.

FEST drug lookup

FEST (the open national dataset from DMP, ex-Legemiddelverket) is the source. Felleskatalogen has no open developer API and is licensed editorial content — don't use it. FEST's open Rekvirent extract (human-use meds) is a SOAP/WCF XML dump (the M30 message), not a per-keystroke REST API — so do not call it from the phone.

App side: read a slim, pre-flattened JSON dataset (name, strength, unit, form, ATC, package size) and do offline autocomplete against it. The job that pulls the M30 on my server, flattens it, and publishes that JSON (via Caddy or into the Garage bucket) lives outside this repo — just document the expected JSON shape and where the app loads it from. Refresh is monthly-ish; FEST changes slowly.

Milestones (verify each before moving on)

  1. Project skeleton: Gradle + version catalog (current stable, verified), manifest with the permission set, Application class, empty Compose activity. assembleDebug green.
  2. Data layer: Room entities, DAO, DB, converters. A couple of DAO instrumented sanity checks if cheap.
  3. Schedule engine (pure) + unit tests: weekly mask, every-N-days, supply, renewal.
  4. Reminder subsystem: notifications (channels, Taken/Snooze actions), AlarmScheduler (next + escalation + daily supply check), DoseAlarmReceiver, DoseActionReceiver, BootReceiver. Manually verify on a device: dose fires, escalates, Taken cancels + decrements, survives reboot.
  5. Backup: versioned JSON serializer (export/import), SigV4 + S3 client, crypto interface + JCE baseline, WorkManager job, encrypted settings store.
  6. age crypto behind the interface; verify against the age test vectors.
  7. Doctor-summary PDF + a share action (FileProvider + ACTION_SEND).
  8. FEST offline lookup + autocomplete field.
  9. UI buildout: today's doses with Taken/Skip/Snooze, add/edit med, schedule editor, settings (S3 + passphrase), share-summary. TalkBack-correct semantics as you go — cheap now, painful to retrofit.

Acceptance

./gradlew assembleDebug and ./gradlew test both pass; the schedule engine has unit tests; reminders verified manually through a reboot. When the repo's stable, distill the non-negotiable decisions + working agreement above into a CLAUDE.md at the repo root so they persist across sessions.

Practical note: easiest start is for me to create an empty Compose project in Android Studio (so the Gradle wrapper and toolchain are valid), then you build everything out inside it from milestone 1. Confirm your plan and I'll do that.