Files
Europa/Nuvolari/docs/roadmap.md
T
Alby96andClaude Opus 5 85b2c99949 Scaffold the Flutter app with Italian l10n and region configuration
Creates the app skeleton and the seam everything else in the project hangs
off: region-specific data lives in an asset file, not in code, so adding a
region later is a new JSON file rather than a refactor.

Notable choices:

- applicationId and namespace are it.nuvolari.app rather than the doubled
  it.nuvolari.nuvolari that `flutter create` produces, with the Kotlin package
  and the iOS bundle identifiers moved to match.
- SDK levels are pinned instead of inherited from `flutter.*`. Google Play
  requires API 36, and that is a release blocker rather than something to let a
  Flutter upgrade change silently. minSdk 24 is the highest floor the planned
  dependencies impose.
- Riverpod and Dio-free for now, no code generation: see docs/stack-decisions.md.
- Italian is the l10n source language, so app_it.arb is the template rather
  than a translation of an English original.

The region parser rejects rather than repairs. An inverted bounding box, a map
centre outside its own bounds, an unknown adapter name, a duplicate zone code
or an empty attribution list all throw with the offending field named. Each of
those would otherwise fail silently and visibly wrong: a swapped latitude and
longitude renders the radar in the wrong place, an unknown adapter falling back
to mock would show demo frames where live data was expected, and a missing
attribution is a licence violation rather than a cosmetic gap.

Tests run against the asset that actually ships and against a captured copy of
the live ARPA CAP feed, so the eleven zone codes in the config are checked
against the eleven the feed really emits rather than against a list retyped
from documentation.

Verified: dart format clean, flutter analyze 0 issues, 50 tests passing,
flutter build appbundle --debug produces an AAB with applicationId
it.nuvolari.app, minSdk 24, targetSdk 36.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 11:24:26 +02:00

118 lines
4.8 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.
> 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.
## 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 |