CoverDex is being converted from a React/Capacitor PWA into a native
Android app: Kotlin, Jetpack Compose, Material 3, Room, Hilt,
ViewModel/StateFlow. GitHub Pages and the whole web-release path are
removed. The reference implementation for every architectural decision is
the sibling project Hall of Memories (marcogn/hall-of-memories),
checked out alongside this repo during planning at ../Hall-Of-Memories.
These files are written to be executed by a coding agent, one phase per session, with no further design work required. Everything that could be decided in advance has been decided; where a phase leaves something open it says so explicitly and tells you what to do about it.
| Phase | File | Ships |
|---|---|---|
| 0 | phase-0-foundation.md |
Native Gradle project, Hilt, Compose, theme, i18n, navigation skeleton, CI. Capacitor and the Pages release path deleted; the web app parked in legacy-web/ |
| 1 | phase-1-dataset-sync.md |
The fast dataset sync, Room cache, sprite resolver, Settings → Data |
| 2 | phase-2-teams-and-roster.md |
Room user schema, teams list, team detail, slot editing, custom roster |
| 3 | phase-3-analysis.md |
Coverage engine port + the seven-section analysis screen |
| 4 | phase-4-suggestions-and-generator.md |
Suggestion engine, composite scoring, Surprise Me generator |
| 5 | phase-5-import-export-and-settings.md |
Showdown import/export, settings, theme and language, local backup |
| 6 | phase-6-release.md |
Signing, release pipeline, docs rewrite, legacy-web/ deleted |
| 7 | phase-7-accuracy-and-customization.md |
Base stats + correct English ability/move names in the dataset, canonical-vs-custom ability picking, held items (defensive subset), BST tie-break for suggestions, configurable suggestion count, and the ability-effect gaps the Phase 7 audit found |
Read before starting any phase: ../../CLAUDE.md,
native-spec.md, this file, the phase file itself, and
reference-pokedata.md if the phase touches the
dataset or sprites.
Do not start a phase before the previous one is merged. Each phase assumes the previous one's code exists and compiles.
You are working with two references, and they are used for different things. Confusing them is the main way this plan goes wrong.
Hall of Memories (../Hall-Of-Memories) is the architectural reference.
Copy its Gradle setup, its version catalogue, its Hilt modules, its Room +
Flow + combine() ViewModel shape, its HttpURLConnection client style,
its sprite fallback composable, its SAF backup code, its three CI workflows,
its CLAUDE.md conventions. Six shipped phases of the same stack by the same
author. Copy its patterns rather than inventing new ones. Read its
CLAUDE.md "Known gotchas" section in full before writing any Kotlin — every
entry there cost someone real debugging time.
legacy-web/ is the behavioural reference. It is this repo's own React
code, moved aside in Phase 0 and deleted in Phase 6. It is the specification
for what the app does: the coverage maths, the suggestion ranking, the
composite score weights, the Showdown format contract, every UI string in
both languages. Its Vitest suite (23 files, 175 tests) is the oracle for
expected values when porting an engine — when the Kotlin and the TypeScript
disagree about a number, the TypeScript is right unless this plan explicitly
says otherwise.
cd legacy-web && npm ci && npm test # run the oraclelegacy-web/ is excluded from CI and is never edited. If you find a bug in
it, note it in the phase file and fix it in the Kotlin.
- Stay inside the phase. If you spot something worth doing that belongs
to a later phase, write it into that phase's file as a
> NOTE from phase N:line instead of implementing it. - Copy Hall of Memories, don't invent. See above. Matching its patterns is worth more than a marginally better idea.
- No new dependencies beyond the catalogue pinned in Phase 0. If a phase seems to need one, stop and flag it in the PR description instead of adding it. CSV parsing, the HTTP client, the sprite fallback chain and the placeholder art are all hand-rolled, exactly as in the sibling app.
- No hardcoded user-visible strings. Every one goes through
stringResource()(Compose) orcontext.getString()(ViewModel, with@ApplicationContextinjected) and lands in bothres/values/strings.xml(Italian, the default locale) andres/values-en/strings.xml(English) in the same commit. A key present in only one silently falls back to Italian. The source of the wording islegacy-web/src/i18n/locales/{it,en}.json— port the text, do not reinvent it. - Room migrations are additive and numbered.
fallbackToDestructiveMigration()is banned outright. Phase 1 creates schema v1; anything after it that changes a table writes a numberedMIGRATION_x_yand bumps the version. - Pure logic lives in
domain/with no Android imports, so it is unit-testable on the plain JVM without Robolectric: the coverage engine, the suggestion engine, the team generator, the Showdown parser, the CSV parser, sprite URL resolution, ability effects.data/owns everything that touches Android, Room or the network. This split is what makes the ported engines testable againstlegacy-web's expected values. - Changelog as you go. Every phase adds its entries to
CHANGELOG.md's## [Unreleased]section, in the convention described inCLAUDE.md(one bold lead-in per user-facing change) — the release workflow reads exactly those lead-ins. - Update the docs the phase touches.
docs/test-plan.mdgets a new section per phase;docs/implementation-decisions.mdgets an entry for every non-obvious choice you had to make; this file's phase-status list andCLAUDE.md's get ticked.
This is a rewrite of a working app, not a new app. For every engine you
port, the phase file names the legacy-web test file that defines its
behaviour, and the Kotlin test suite must assert the same expected values
— ported case by case, not re-derived. Where a Kotlin test cannot express a
case (probabilistic generator runs, React rendering), the phase says so.
There is exactly one intentional behavioural change in the whole plan:
the generation filter moves from hardcoded id ranges to the real
generation_id. It is described in
reference-pokedata.md §4. Anything else that
differs is a bug.
- Everything in the phase's "Deliverables" exists and compiles.
- The unit tests listed in the phase are written and pass.
-
./gradlew testDebugUnitTest lintDebug assembleDebugis green — or, if the environment has no Android SDK (see below), theAndroid CIworkflow run for the pushed branch is green. -
CHANGELOG.md[Unreleased]updated. -
docs/test-plan.mdhas this phase's manual-verification section. - The phase-status list in this file and in
CLAUDE.mdis updated. - Committed and pushed to the working branch; draft PR opened or updated.
./gradlew needs both the Maven repositories (dl.google.com,
repo1.maven.org) and an Android SDK. In the planning session the
network was reachable but ANDROID_HOME was unset and no SDK was installed.
Check, in this order, and do not burn time fighting it:
curl -sfo /dev/null -w '%{http_code}\n' https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/8.13.0/gradle-8.13.0.pom
echo "$ANDROID_HOME"; command -v sdkmanagerIf there is no SDK and none can be installed, say so plainly in the PR,
push, and let the Android CI workflow do the verification — then read its
logs and fix forward. Never report a build as passing that you did not
run.
Some things have no meaningful test on this stack: the real network sync end
to end, AppCompatDelegate language switching, real sprite rendering and
its 404 fallbacks, the SAF file pickers, clipboard interaction. Those go
into docs/test-plan.md as manual on-device steps for the user, and every
real bug found that way gets an entry in that file's "Known regressions".
- Small, logical commits with imperative English messages
(
Add the CSV dataset client and its cache tables). - Branch: the one named in the session instructions.
- Push, then open a draft PR whose description lists what the phase delivered, what is deliberately not done yet, and anything you had to decide that the plan did not cover.
- Phase 0 — Foundation: ✅ done
- Phase 1 — Dataset sync: ✅ done
- Phase 2 — Teams and roster: ✅ done
- Phase 3 — Analysis: ✅ done
- Phase 4 — Suggestions and generator: ✅ done
- Phase 5 — Import/export and settings: ✅ done
- Phase 6 — Release: ✅ done
- Phase 7 — Engine accuracy, abilities/items, BST ranking: ✅ done —
see
phase-7-accuracy-and-customization.md