# Roadmap Foto Dup Finder is built against a fixed milestone plan (**M0–M10**). This file is the single source of truth for what is done and what remains. Before this file existed the plan lived only in developer notes, and the [CHANGELOG](../CHANGELOG.md) refers to milestone labels (`M7`, `M8`). Legend: ✅ done · 🚧 in progress · ⏳ not started · 💡 candidate (proposed, not scheduled) ## Milestones | # | Milestone | Status | Evidence / notes | |---|---|---|---| | M0 | Toolchain & scaffold | ✅ | User-local SDK, Gradle 9.3.1 + AGP 9.1.0, wrapper and version catalog, empty debug APK builds | | M1 | Pure-Kotlin duplicate engine + JVM tests | ✅ | SHA-256 quick/full hashing, pHash + RGB dHash, union-find grouping, Exact/Balanced/Aggressive policies; tests under `app/src/test` | | M2 | Data layer | ✅ | MediaStore query, coroutine-parallel hashing, incremental SQLite hash index, permission + partial-access handling | | M3 | Compose UI | ✅ | Scan progress, grouped results with thumbnails, two-step staging, selection helpers | | M4 | Safe deletion | ✅ | `MediaStore.createDeleteRequest` / `createTrashRequest`; legacy direct delete on Android 10 | | M5 | Performance pass | ✅ | Size short-circuit, provider-thumbnail hashing, batched hashing (folded into M2/M3) | | M6 | Release config & Play readiness | ✅ | R8/minify, optional signing, `.aab`, [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md), [PRIVACY.md](../PRIVACY.md), README | | M7 | On-device validation & hardening | ✅ | Foreground `dataSync` service, one failure boundary per file, `core/DecodeMath`, hostile fixtures ([EDGE_CASE_TESTING](EDGE_CASE_TESTING.md)), `versionName` → 1.0.0 | | M8 | Localisation groundwork | ✅ | Every user-visible string in `res/values/strings.xml` with real ``, shared `text/TextRes.kt`; **English is the only locale** | | M9 | Monetisation | ⏳ | No billing code. Blocked on a product decision (free forever vs. one-time unlock vs. limited free scans); changes the Data Safety answers | | M10 | Store assets | ✅ | `tools/make-store-assets.sh` renders the 512×512 icon and the 1024×500 feature graphic from the app's own vectors; `tools/make-store-screenshots.sh` normalises device captures to Play's 2:1 limit (1080×2160); all listing copy is drafted and length-checked in [STORE_LISTING](STORE_LISTING.md). The captured screenshots still show the permission banner — see blockers | ## Release blockers (work that is not a numbered milestone) | Item | Detail | |---|---| | ~~Large-library scan stall~~ ✅ **fixed — verified on device** | The 5,363-photo library used to freeze at *“74 / 100 · Comparing file signatures…”* (a **blocked syscall**: zero CPU, workers sleeping). `BoundedCall` now abandons a stuck provider call **and** trips a stall breaker after three consecutive timeouts. **Verified on the real test device (Android 12 / MIUI): the scan completes — 20.2 s cold, 11.0 s warm, 15 groups, 6 unreadable — where it used to hang.** Covered by `BoundedCallTest` and `ScanEngineEndToEndTest` | | ~~Unreleased `BoundedCall` fix~~ ✅ | The provider-call deadline shipped in **1.0.1** (`versionCode` 2) | | **Play Console: foreground-service declaration** | Declaring the `dataSync` type (App content › Foreground service permissions) is release-blocking. **[code]** the manifest already declares it; the ready-to-paste justification is in [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) step 5 — this is a Console action, not a code change | | **Remaining Play paperwork** | Keystore / Play App Signing, Data Safety form, hosted privacy policy and store listing — see [PLAY_RELEASE_CHECKLIST](PLAY_RELEASE_CHECKLIST.md) | | **Store screenshots still show the permission banner** | The three captures were taken with the debug-only `MANAGE_EXTERNAL_STORAGE` app-op, so the app's own permission check (correctly) still reported *no access* and the banner is in frame. Re-shoot on a clean install after tapping **Grant access** — the same run that closes the permission-flow verification gap. Files, specs and caveats: [STORE_LISTING](STORE_LISTING.md) | | **Device-only verifications** | RAW/DNG files, a *real* iPhone HEIC with a non-upright EXIF orientation, permission revoked mid-scan, and the Android 14 “Selected photos” flow. ✅ The **index-reuse** check is done: after a scan the index holds **5,363 rows, 5,357 with a perceptual hash**, and a second scan runs in **11.0 s vs 20.2 s** cold | ## Candidate milestones (proposed, not scheduled) | Candidate | Rationale | |---|---| | **Colour signature** | Flat-colour images (blank scans, dark UI screenshots) hash identically regardless of colour, so they are reported as mutual duplicates — see the “Known gap” in [EDGE_CASE_TESTING](EDGE_CASE_TESTING.md). A small colour histogram is orthogonal to the grayscale structure hash, and the benchmark harness already has an implementation to measure against | | **Video support** | Only images are scanned today (README “Known limitations”) | | **“Similar shots” section** | Bursts / same-subject shots as a separate, never-pre-selected list. Needs a different algorithm and must not feed the deletion flow | | **Warm-scan speed** | A second scan is faster (11.0 s) but its “similar” stage still costs ~10 s even though all 5,357 perceptual hashes are cached — the per-photo `withTimeoutOrNull` wrapper still runs for all 5,363 photos. Profiling this could make an unchanged library re-scan in well under a second | ## Suggested order 1. ✅ **Done** — the scan-stall hardening and the `BoundedCall` fix shipped as **1.0.1** (`versionCode` 2). Only the on-device re-check of the 5,363-photo scan remains. 2. Finish the Play Console paperwork and roll out through **internal → closed → production**. 3. ✅ **Done** — **M10 store assets**: the icon and feature graphic are generated from the app's own vectors, the screenshot normaliser enforces Play's 2:1 limit, and the listing copy is drafted and length-checked. The only remainder is the screenshot re-shoot after a real permission grant (see blockers). 4. **Colour signature** — the flat-colour false-positive fix. 5. **M9 — monetisation**, once the product decision is made. Deliberately deferred: **video support** and the **“similar shots”** section.