Add UI-09: monochrome dark map tiles

Today's OSM tiles are the default tan/cream basemap, which looks nothing like the mockups regardless of HUD styling. Recommends switching to CARTO's free Dark Matter raster tiles over a client-side color filter on OSM tiles, since the mockups depend on the map itself being dark, not just tinted.

Flags a real interaction with V3-11's offline tile cache: it's keyed by (z,x,y) with no provider tag, so switching tile sources without a cache-busting step would silently serve stale OSM tiles under CARTO's URL. That's folded into this ticket's implementation, not left as a followup bug.
This commit is contained in:
2026-08-23 18:29:59 -05:00
parent 822003ed38
commit 3e7878b6e1
2 changed files with 113 additions and 5 deletions

View File

@@ -27,6 +27,7 @@ v3 held to.
| [UI-06](UI-06-plan-and-route-planning.md) | Plan & Route Planning redesign | M | UI-01, UI-03 | Not started | | [UI-06](UI-06-plan-and-route-planning.md) | Plan & Route Planning redesign | M | UI-01, UI-03 | Not started |
| [UI-07](UI-07-rides-history.md) | Rides History redesign | M | UI-01, UI-03 | Not started | | [UI-07](UI-07-rides-history.md) | Rides History redesign | M | UI-01, UI-03 | Not started |
| [UI-08](UI-08-theme-modern-professional-dark.md) | Theme migration to Modern Professional Dark | S | — | Not started | | [UI-08](UI-08-theme-modern-professional-dark.md) | Theme migration to Modern Professional Dark | S | — | Not started |
| [UI-09](UI-09-monochrome-dark-map-tiles.md) | Monochrome dark map tiles | S | — | Not started |
## Dependencies ## Dependencies
@@ -38,8 +39,11 @@ UI-01 ──┬──► UI-02
UI-08 ──► UI-03 ──┬──► UI-04 ──► UI-05 UI-08 ──► UI-03 ──┬──► UI-04 ──► UI-05
├──► UI-06 ├──► UI-06
└──► UI-07 └──► UI-07
UI-09 ──┬──► UI-05
├──► UI-06
└──► UI-07
no dependencies: UI-01 · UI-08 no dependencies: UI-01 · UI-08 · UI-09
``` ```
## Suggested order ## Suggested order
@@ -49,15 +53,17 @@ no dependencies: UI-01 · UI-08
2. **UI-08** — theme. Every screen ticket after this needs the palette settled once, not 2. **UI-08** — theme. Every screen ticket after this needs the palette settled once, not
patched per-screen. Supersedes V3-16's safety-orange direction — see that ticket's patched per-screen. Supersedes V3-16's safety-orange direction — see that ticket's
note. note.
3. **UI-02** — skeleton map. Small, and exercises the background-map plumbing UI-01 just 3. **UI-09** — dark map tiles. Small, no dependencies, and every screen with a map looks
wrong without it regardless of how correct the HUD chrome on top is.
4. **UI-02** — skeleton map. Small, and exercises the background-map plumbing UI-01 just
built while it's fresh. built while it's fresh.
4. **UI-03** — the component kit (`GlassPanel`, `FloatingPill`, `PulsingLocationMarker`). 5. **UI-03** — the component kit (`GlassPanel`, `FloatingPill`, `PulsingLocationMarker`).
Build once, consumed by every screen ticket. Build once, consumed by every screen ticket.
5. **UI-04** — the customizable HUD widget system (drag/resize/persist layout, a 6. **UI-04** — the customizable HUD widget system (drag/resize/persist layout, a
visibility toggle in Settings). This is its own ticket, not folded into UI-05, because visibility toggle in Settings). This is its own ticket, not folded into UI-05, because
drag-and-resize-with-persisted-layout is a genuinely separate piece of engineering drag-and-resize-with-persisted-layout is a genuinely separate piece of engineering
from "redesign the record screen" — a small widget-layout editor in miniature. from "redesign the record screen" — a small widget-layout editor in miniature.
6. **UI-05 / UI-06 / UI-07** — the three screen redesigns, in any order once everything 7. **UI-05 / UI-06 / UI-07** — the three screen redesigns, in any order once everything
above is done. Each is independently shippable. above is done. Each is independently shippable.
## Things named explicitly by the person who commissioned this, not inferred from the mockups ## Things named explicitly by the person who commissioned this, not inferred from the mockups
@@ -77,6 +83,10 @@ no dependencies: UI-01 · UI-08
mockup's tap-to-expand interaction is a fixed layout with one temporary focus state; mockup's tap-to-expand interaction is a fixed layout with one temporary focus state;
this goes further — the rider chooses the layout and which stats exist on screen at this goes further — the rider chooses the layout and which stats exist on screen at
all, and it persists. all, and it persists.
- **The map itself is monochrome dark** (UI-09) — today's default OSM tan/cream tiles are
nothing like the mockups regardless of how correct the HUD chrome on top is. A real
tile-provider decision, not a color filter thrown on as an afterthought — see that
ticket for the trade-off against just filtering OSM's existing tiles.
## The Map screen is the design north star ## The Map screen is the design north star

View File

@@ -0,0 +1,98 @@
# UI-09 — Monochrome dark map tiles
**Depends on** nothing · **Size** S · **Status** Not started
## Goal
Replace the tan/cream default OpenStreetMap raster style with a monochrome dark map, so
the map actually looks like it belongs in a dark-mode HUD instead of a light basemap
punched through a dark overlay.
## Context
`RideMap`, the Plan/Route Planning canvas, and every Stitch mockup all assume a dark or
near-monochrome map underneath the HUD. Today's `TileLayer` (`ride_map.dart`) points at
`tile.openstreetmap.org`, OSM's standard light/tan default style — the actual on-device
result is nothing like the mockups, regardless of how dark the chrome on top of it is.
This affects every screen that shows a map (Map HUD, Plan, Route Planning, and UI-01's
dimmed background on every other tab), so it's worth settling once, early, rather than
each screen ticket discovering the same mismatch independently.
## Design
Two real options, not one obvious answer:
**Option A — switch tile provider to CARTO's "Dark Matter" basemap.**
`https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png` (subdomains a-d, retina
`{r}` variant available, max native zoom 20). Purpose-built dark, desaturated/monochrome
cartography — closest to the mockups with the least engineering. Free for reasonable
usage same as OSM's own tiles, but it's a **different third party with its own usage
policy and attribution requirement** ("© OpenStreetMap contributors © CARTO", not just
OSM's own attribution) — a real dependency to add, not a style tweak.
**Option B — keep OSM tiles, apply a color filter client-side.** Wrap `TileLayer` in a
`ColorFiltered` widget using a `ColorFilter.matrix` that desaturates and inverts/darkens
the tan basemap into a monochrome dark look. No new third party, no new attribution, and
V3-11's offline tile cache keeps working against the exact tiles it already knows about
— but the result is a filtered light map, not tiles actually designed for dark
presentation, and will read as slightly "off" next to CARTO's purpose-built version
(road/building contrast tuned for light backgrounds doesn't always invert cleanly).
**Recommendation: Option A.** The mockups' whole aesthetic depends on the map itself
being dark, not just tinted dark, and CARTO's dark tiles are a well-established, free,
no-API-key option already widely used in the Flutter/Leaflet ecosystem for exactly this.
The added attribution line and a second host to trust are a small, one-time cost.
**V3-11 interaction:** the offline tile cache (`FileTileCache`/`CachedTileProvider`) is
already keyed by `TileKey(z, x, y)` with no provider-specific data baked in — but that
means a cache built against OSM tiles and a cache built against CARTO tiles are
**indistinguishable to the cache** despite being visually different. Switching providers
without a plan here would silently serve stale, wrong-looking cached OSM tiles once
CARTO's URL is live. This needs a cache-busting or provider-tagging fix as part of this
ticket, not a separate one — see Implementation.
## Implementation
1. Add a `provider`-qualified cache key (or a cache-version bump that invalidates
existing V3-11 cache contents wholesale) so switching tile sources can't silently
mix cached tiles from two visually different sources.
2. Update `RideMap`'s `TileLayer` (and the Route Planner's separate `TileLayer` instance
— see `route_planner_screen.dart`) to CARTO's dark tile URL template, correct
`subdomains`, and `maxNativeZoom: 20`.
3. Update the attribution requirement — check whether `flutter_map`'s
`RichAttributionWidget`/`AttributionWidget` is in use anywhere already, or add one;
OSM-only attribution is no longer sufficient once CARTO tiles are in the mix.
4. Update `tileUserAgent`/any OSM-specific comments in `ride_map.dart` that assumed OSM's
tile servers specifically (the "respect OSM's usage policy" reasoning still applies in
spirit to CARTO's own policy, but the specific server being addressed changes).
5. Re-verify V3-11's tile math/download flow still works end-to-end against the new URL
template (tile enumeration and caching are provider-agnostic already; only the fetch
URL construction needs updating).
## Acceptance criteria
- [ ] The map renders in a dark, desaturated/monochrome style matching the Stitch
mockups, not OSM's default tan basemap
- [ ] Correct attribution for both OpenStreetMap and CARTO is shown wherever the map is
shown
- [ ] Switching tile providers does not silently serve stale, visually-mismatched
cached tiles from before the switch (V3-11's cache is invalidated or
provider-tagged)
- [ ] Both `RideMap` and the Route Planner's independent `TileLayer` are updated
consistently — one dark style everywhere, not just on the screens that happened to
get touched first
## Tests
- Existing `ride_map`/route-planner widget tests updated for the new tile URL and still
passing
- Unit: cache-key/versioning change is exercised the same way V3-11's eviction tests
are — a cache built under the old scheme does not get served for the new provider
## Risks
- **A second third-party tile host is a real dependency, not a free color swap** — its
own usage policy, its own risk of being blocked if misused, and its own attribution
obligation. Treat it with the same care V3-11 already applies to OSM (rate-limit
respecting, no bulk prefetch, real user agent).
- If CARTO's free tier ever proves insufficient or is discontinued, Option B (a color
filter over OSM tiles) is the fallback with no new third-party dependency — worth
keeping in mind as a documented Plan B, not re-deriving from scratch later.
## Out of scope
A fully custom/self-hosted vector tile style — much larger scope, and not needed to hit
"looks like the mockups" today.