Files
rippr/docs/ui-redesign/UI-02-offline-skeleton-map.md
uhryniuk 822003ed38 UI redesign: 8 tickets from the Stitch export analysis
Same shape as docs/v3/: Goal/Context/Design/Implementation/Acceptance/Tests/Risks/Out-of-scope, written before implementing.

UI-01: persistent 4-tab shell (StatefulShellRoute) replacing the current push/pop stack rooted at Record, with a single shared background map dimmed behind every tab and no header anywhere.
UI-02: animated skeleton map for the no-cache/no-network case, since the map is now a permanent background presence rather than opt-in per screen.
UI-03: shared GlassPanel/FloatingPill/PulsingLocationMarker component kit.
UI-04: draggable, resizable HUD telemetry widgets with a Settings visibility toggle -- split out from the screen redesign itself since it's a genuinely separate piece of engineering (a small widget-layout system, not a reskin).
UI-05: Map HUD/Record screen redesign -- the design north star every other screen ticket restyles toward.
UI-06: Plan & Route Planning -- correct in principle, explicitly restyled toward UI-05 rather than copying the Stitch export's drifted styling.
UI-07: Rides History -- live dimmed map background (not the mockup's static image), search/filter, real per-ride map thumbnails, and real distinct summary stats (fixing the export's 4-identical-cards generation artifact).
UI-08: theme migration to Modern Professional Dark, superseding V3-16's safety-orange direction.

README documents the dependency order, and two categories worth keeping separate: what was named explicitly by the person commissioning this (map-on-every-tab, no header, offline skeleton, customizable HUD) versus what the mockups themselves got inconsistent (nav icon count, duplicate summary cards, wrong active-tab highlight) and should not be copied as intentional.
2026-08-23 18:25:35 -05:00

4.6 KiB

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.