Files
Europa/Nuvolari/docs/roadmap.md
T
Alby96andClaude Opus 5 7c0a1bd180 Disable emulator quickboot snapshots
The AVD's quickboot snapshot had grown to 2.6 GB, which took this machine down
to 1.4 GB free. Saving a minute of boot time is not worth that here, so cold
boot is now the default and the snapshot directory is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 12:28:03 +02:00

164 lines
6.9 KiB
Markdown

# Roadmap
Each milestone ends with `tool/verify.ps1` green and one commit. A milestone is not
done until its acceptance criteria hold.
Legend: ✅ done · 🔨 in progress · ⛔ blocked on credentials from the project owner
---
## M0 — Toolchain and repository hygiene ✅
Flutter 3.47.3 stable, Android SDK (platform-tools, platform 36, build-tools 36.0.0),
JDK 21, environment variables. Commit the removal of the old Xamarin skeleton.
**Accepts when:** `flutter doctor -v` reports no blocking Android toolchain error and
`git status` is clean.
**Done.** `flutter doctor -v` reports "No issues found!". Installed: Flutter 3.47.3 at
`C:\src\flutter` (telemetry disabled), Android SDK at `C:\Android\Sdk` with
platform 36, build-tools 36.0.0, platform-tools and all licences accepted, and
`JAVA_HOME` pointing at the JDK 21 a previous Visual Studio install had already left
on the machine.
## M1 — Scaffold, Italian l10n, region config, CI ✅
`flutter create` with applicationId `it.nuvolari.app`; `flutter_localizations` + `intl`
with `app_it.arb` as template and no hardcoded UI strings; `RegionConfig` loaded from
`assets/regions/piemonte.json` with parsing tests; `.gitignore`, `env.example.json`,
`tool/verify.ps1`, `.gitea/workflows/ci.yml`.
**Accepts when:** `flutter analyze` reports 0 issues, `flutter test` passes,
`flutter build appbundle --debug` succeeds.
**Done.** All four stages green; the AAB carries applicationId `it.nuvolari.app`,
minSdk 24, targetSdk 36. 50 tests, including assertions against the shipped Piemonte
asset and a captured copy of the live ARPA CAP feed.
> The Gitea instance may have no Actions runner. The workflow file is written to be
> GitHub-Actions compatible, but `tool/verify.ps1` is the verification that must pass.
## M2 — Map, attribution, Sources screen ✅
MapLibre with the style from `MAP_STYLE_URL`, falling back to a local minimal style.
Permanent OSM/ODbL attribution. Sources / Licenses / Disclaimer screen covering
Radar-DPC (CC BY-SA), MET Norway, ARPA, OSM, with the explicit "not an official app"
disclaimer.
**Accepts when:** the map opens centred on Piedmont, attribution is visible at all
times, and the Sources screen is reachable and complete.
**Done**, with one gap. The fallback style is generated from the region bounding box
rather than loaded from an asset, so it needs no third-party boundary dataset and no
network. The attribution bar lists only the sources actually rendered — crediting
OpenStreetMap while showing the fallback would be a false attribution — and sits below
the map rather than floating over it so no map control can occlude it.
**Verified on an emulator** (`nuvolari`, API 36.1, x86_64, GPU host). Running it caught
two things the unit tests could not: the configured initial zoom put the viewport
entirely inside the region so the map read as a blank expanse, and the attribution bar
sat behind the system gesture pill. Both fixed — the camera now fits the region bounds
at runtime, which works on any screen size, and the bar is inside a SafeArea.
## M3 — Animation and timeline 🔨
`RadarSource` with `MockRadarSource` (synthetic frames in assets) and `DpcRadarSource`.
Timeline scrubber, play/pause, adjacent-frame prefetch, `FrameCache` LRU, animation
suspended in background, graceful degradation with a data-age banner.
**Accepts when:** animation runs smoothly in demo mode **with the network off**, and
prefetch, LRU eviction and the fallback paths are covered by tests.
## M4 — Forecast
`MetNoForecastSource` with the mandatory identifying User-Agent, honouring `Expires`
and `If-Modified-Since`. `IconIt2Source` as a flagged fallback. Hourly and daily views.
**Accepts when:** a forecast renders for a Piedmont location, caching respects the
response headers, and fixture-based tests pass.
## M5 — UMP consent and AdMob ⛔
UMP consent form before any ad request; non-personalised ads when consent is declined.
Anchored adaptive banner — never over the map, never an interstitial during animation.
**Test ad unit IDs only** until real ones are supplied.
**Blocked on:** AdMob App ID and ad unit IDs.
## M6 — Rain notifications and alerts ⛔
`ArpaCapAlertSource` reading the CAP feed through the backend; zones `Piem-A``Piem-M`
with levels shown verbatim and a link to the official bulletin. Rain notifications via
FCM topics per geographic cell, subscribed from the device.
**Blocked on:** Firebase project and `google-services.json`.
## M7 — Android home widget
Glance widget showing the latest frame and the next rain, refreshed by WorkManager.
**Accepts when:** the widget renders a real frame on the home screen and updates.
## M8 — Backend worker ⛔
`dpc_client` (with the `origin` header), `crop` (bbox + reproject to EPSG:3857),
`palette` (dBZ colormap, legend exported into the manifest), `render` (RGBA PNG,
transparent below threshold), `manifest`, and `publisher/` with `LocalPublisher` and
`S3Publisher`. WebSocket trigger with cron fallback. pysteps nowcast present but
disabled behind a flag.
**Accepts when:** `python -m pytest` passes and one full run produces PNGs plus a
`manifest.json` that the app consumes from a local server.
**Blocked on (publishing only):** VPS / object storage endpoint and credentials.
Development proceeds against `LocalPublisher`.
## M9 — Play Store release preparation ⛔
Signing config reading `key.properties`, release AAB, target API 36, privacy policy
draft, store listing copy, Data safety draft, prominent disclosure for location.
**Blocked on:** upload keystore and Play Console account.
---
## Pending inputs from the project owner
| Needed for | Item |
|---|---|
| M2 (quality) | MapTiler API key — until then the local fallback style is used |
| M5 | AdMob App ID and ad unit IDs |
| M6 | Firebase project and `google-services.json` |
| M8 | VPS / object storage endpoint and credentials |
| M9 | Play Console account and upload keystore |
---
## Verifying on the emulator
An AVD named `nuvolari` is set up on this machine: API 36.1, `google_apis`, x86_64,
GPU passthrough to the host. It uses a 2 GB data partition and no SD card because disk
is tight, and quickboot snapshots are disabled — they cost 2.6 GB to save a few seconds
of boot, which is the wrong trade here. Expect a cold boot of a minute or so.
```bash
# Boot it
"$ANDROID_HOME/emulator/emulator" -avd nuvolari -no-audio -no-boot-anim -gpu host
# Build, install, launch
cd app
flutter build apk --debug
adb install -r build/app/outputs/flutter-apk/app-debug.apk
adb shell am start -n it.nuvolari.app/.MainActivity
# Capture what it looks like
adb exec-out screencap -p > screen.png
```
`flutter run -d emulator-5554` works too and gives hot reload; the steps above are what
a non-interactive session uses.
> An unrelated AVD, `pixel_7_-_api_35`, is present but broken — its system image
> (`android-35/google_apis_playstore`) is not installed, so it cannot boot. It occupies
> about 4.8 GB. It predates this project and has been left alone.