# Listen — Agent Instructions Personal Android podcast app, `org.terst.listen`. Sibling to FeeDreader (`/workspace/feedreader`), built for Peter's own use. Greenfield, hands-on-testing-rounds workflow: sideload a debug build, use it, batch feedback. Design plan: https://claude.ai/code/artifact/85137e78-3718-4d84-8ab8-f72885170653 ## What this app is, and isn't Replacing Pocket Casts completely. The distinguishing features are audio-based intro/outro skipping, slow-drip archive subscriptions, and rule-driven auto-queueing. **Non-goals, deliberately:** anything social, new-episode notifications (or any alert — the media notification is a transport control and is mandatory for background audio, which is a different thing), favourites, bookmarks. Discovery and video were both cut from scope. ## Relationship to FeeDreader Separate repos, no shared module. That was a considered call, not an accident: FeeDreader's `FeedParser` treats `` as an image source and its `FilterRuleEngine` is 46 lines of substring matching that doesn't generalise to queue rules, so only `Opml.kt` (~96 lines) genuinely transferred. **Standing instruction:** watch for things that do generalise, and avoid pointless drift between the two apps. If a change here would obviously improve FeeDreader too, say so. Don't force sharing where the problems are actually different — but don't let two solutions to the same problem quietly diverge either. ## Test-driven development is mandatory Same rule as FeeDreader, same reasons. Every change to `app/src/main/` follows red-green-refactor, in that literal order: 1. **Write the test first**, for the specific behaviour you're about to add or change. 2. **Run it. Confirm it fails, and fails for the right reason** — an assertion mismatch showing the missing behaviour, not a compile error. 3. Write the minimal production code to pass. 4. Run again. Confirm green. 5. Refactor if needed, keeping it green. 6. Run the full quality gate before committing. **If production code was written before or without a preceding failing test**, discard the diff and redo it test-first. Do not backfill tests onto existing code — a test written against working code confirms what the code does, not what it should do. **Scoping exception:** pure resource/config/manifest changes with no unit-testable behaviour don't get a red step; the static-analysis gate covers those. ## Verification — mandatory **Never conclude a build or test result from grepped console output**, and never verify a launch with `adb shell monkey`. Use the shared wrappers, which report from exit codes, parsed JUnit XML, and a logcat capture started before launch: ``` /workspace/devtools/bin/gradle-test /workspace/listen -- --rerun-tasks /workspace/devtools/bin/android-launch-check org.terst.listen \ --apk app/build/outputs/apk/debug/app-debug.apk --settle 15 ``` See `/workspace/devtools/README.md`. These exist because grepped console text has produced wrong claims three separate times across these repos. ## Quality gate — every commit 1. **Build** — `./gradlew compileDebugKotlin` succeeds. 2. **Tests** — `./gradlew testDebugUnitTest` passes in full. 3. **Static analysis** — `./gradlew detekt lintDebug` clean: zero lint errors, zero new detekt findings beyond the baseline. 4. **Coverage must not decrease** against `.coverage-baseline` (line coverage over `app/src/main/java` excluding `ui/**`). Set `COVERAGE_EXCEPTION="reason"` for justified drops, e.g. deleting covered dead code. 5. **Manifest permission diff** — if `` changed, the commit message says why. 6. **Schema migrations** — if `AppDatabase.kt`'s `version` changed, a migration test must be touched in the same diff. Room tracks by version; a wrong migration corrupts a real, non-reproducible library. ## Domain notes worth keeping - **Playback position is the crown jewel of the data model.** It must survive process death. Save on a timer, on pause, and on lifecycle transitions — not just on stop. - **Never assume audio.** An enclosure carries a MIME type. Video was cut from scope, but the model stays media-agnostic so it isn't a retrofit. - **GUIDs move.** Publishers rewrite feeds; match episodes on GUID first, then enclosure URL, then title+date. An episode that loses its identity loses its playback position. - **Retention is auto-delete-once-played** with a grace period, so a mis-tap doesn't destroy an episode you were still listening to. - **YouTube never touches this app.** Audio arrives via podkit running in Termux on the phone, writing to a local folder Listen imports. No extractor code here, nothing to break when Google ships a change.