# UI-02 — Offline / no-connection skeleton map **Depends on** UI-01 · **Size** S · **Status** Not started ## Goal When the map has no tiles to show — no cached tiles for the current view and no network to fetch fresh ones — show an animated skeleton in place of a broken or blank map, on every tab, since UI-01 makes the map a permanent background presence rather than something a rider opts into per-screen. ## Context By explicit instruction: *"If we do not have connection for the map, we should just show an animated skeleton map so it doesn't look weird."* Once UI-01 ships, the map is always on screen somewhere — a blank grey rectangle or a grid of broken-image icons behind every tab, all the time, on a phone with no signal, is a much worse look than it was when the map only appeared on the one screen a rider explicitly opened. This composes with existing infrastructure rather than duplicating it: `CachedTileProvider` (V3-11) already tries the offline tile cache before the network, so "no connection" in practice means *both* the cache and the network failed for the tiles currently in view. ## Design Detect at the `CachedTileProvider`/`TileLayer` level, not by asking the OS for connectivity state directly — a connectivity API can report "online" while the actual tile fetch still times out (captive portal, degraded connection), and the tile fetch's own success/failure is the only thing that actually matters here. - Track a rolling failure count for in-flight tile fetches (cache miss **and** network fetch failed) over a short window. Crossing a small threshold (e.g. 3 consecutive failures) flips the map into skeleton mode; a single successful fetch flips it back. - Skeleton mode replaces the `TileLayer` with a static, non-fetching placeholder — never a widget that itself keeps trying and failing in a loop. - Placeholder look: a shimmer/gradient-sweep animation over a neutral grid pattern (reuse the "technical grid overlay" treatment already present in the Stitch exports' `map-grid-overlay` CSS — 40px faint grid lines — as the static base under the shimmer), not a spinner. A map-shaped thing that is clearly still loading, not an error state. - Recheck periodically (not on every frame) so the map recovers automatically the moment connectivity returns, without the rider having to do anything. ## Implementation 1. Wrap `CachedTileProvider` (or add a sibling) that reports fetch outcomes to a small `MapConnectivityState` — rolling window, threshold, debounced flip. 2. `SkeletonMapLayer` widget: the grid + shimmer, sized to fill the same space a `TileLayer` would. 3. `RideMap`/`PersistentMapBackground` (from UI-01) watches `MapConnectivityState` and swaps `TileLayer` for `SkeletonMapLayer` when in skeleton mode — markers/polylines (the rider's own path, planned route) keep rendering on top either way, since those come from local data, not tiles. 4. A cap on retry frequency while in skeleton mode — this must not turn into a tile- fetch retry loop that itself violates OSM's usage policy the way V3-11's design explicitly guards against. ## Acceptance criteria - [ ] With no cached tiles and no network, the map area shows an animated skeleton, not blank space or broken-image icons - [ ] Recovery is automatic: once tiles become fetchable again, the skeleton is replaced without user action - [ ] The rider's live position/path and any planned route still render on top of the skeleton (only the base tiles are missing) - [ ] Skeleton mode does not itself hammer the tile server with retries - [ ] Behaves correctly across all four tabs (UI-01's shared background map), not just the Map tab ## Tests - Unit: the failure-count/threshold/debounce logic that decides skeleton vs. live, exercised with fakes (no real network) — mirrors V3-11's `tile_downloader_test.dart` pattern of injecting fake fetch outcomes - Widget: `SkeletonMapLayer` renders when connectivity state says offline; `TileLayer` renders when it says online - Widget: markers/polylines still render in skeleton mode ## Risks - Flapping between skeleton and live on a marginal connection would be worse than either state alone — the debounce window is what prevents this; tune it based on real behavior, not a guess (echoes V3-13's "measure, don't tune blind" discipline). - Must not let skeleton-mode retries become the kind of bulk/rapid tile request V3-11's design explicitly exists to avoid. ## Out of scope General offline-mode UX beyond the map itself (e.g. graying out map-dependent buttons). Manual retry controls — automatic recovery is the whole point.