- Skip MapLibre on Android <29 (Karoo): SELinux denies kgsl-3d0 access
from untrusted_app context, crashing the GPU driver on any OpenGL
surface. Replace with SvgRouteView — equirectangular SVG route trace
using react-native-svg, no native GL surface needed.
- Add +/- zoom buttons to full-screen MapLibre map on modern devices
via Camera ref and onRegionDidChange.
- Skip PyodideWebView on Android <29: same GPU driver conflict; set
_engineUnavailable at module init via API level gate (< 29).
- Add engine_unavailable fast path in PyodideWebView: post message
immediately if WebAssembly.Global is absent (Chrome <69) instead of
attempting 30 MB Pyodide download.
- Add server-side extraction fallback (extractServer.ts): when engine
unavailable, POST raw file as base64 to /api/upload/raw; server runs
full Python pipeline and returns extracted data.
- Add /api/upload/raw endpoint in server.py.
- Add pre-flight auth check (checkServerAuth) before batch import so
an expired token errors immediately rather than after N files.
- Fix uploadLocalActivities in sync.ts: was reading original_path as
JSON (binary FIT file, always threw), silently skipping every upload.
Now reads detail_json from DB directly.
- Redesign Feed header: replace single Sync button with Upload /
Download / Refresh. Pull-to-refresh and Refresh button are local-only.
Auto-refresh on tab focus via useFocusEffect.
- Replace ActivityIndicator with plain Text everywhere (native animation
also crashes Karoo GPU driver).
- Raise macOS open-file limit in dev_test.py to prevent EMFILE errors
from Astro file watcher.
- Document all Karoo hardware constraints in docs/mobile-app.md.
Diagnosed via on-device debug: build_timeseries produces 3271 points
correctly, but the installed wheel's write_activity has a silent
exception path that skips writing the timeseries file. The workaround
calls build_timeseries directly and writes the file if missing.
Also moves useTheme import to @/ThemeContext across all tab screens.
- theme.ts: useTheme() hook with race calendar (May–Sep windows),
auto-detects Giro/Tour/Vuelta by date; stores override in SQLite
- All screens (feed, import, activity, tab bar) now use accent/dim
from useTheme() instead of hardcoded #60a5fa
- Settings: Palette section with Auto/Default/Giro/Tour/Vuelta buttons
to override the auto-detected palette for testing
- Detail screen: Delete button (top-right, red) with confirmation alert;
deletes SQLite row and original file via expo-file-system
- Feed screen: long-press card to enter select mode; checkbox + blue
border on selected cards; bottom action bar with bulk Delete N button;
header switches to show count + Cancel
- db/queries: deleteActivity (returns original_path) and deleteActivities
(bulk, returns all original paths)
In Hermes release builds, useEffect captures closure values from the first
render. If instanceUrl or token were empty at that moment (before SQLite reads
complete), no fetch ran and map/graphs never loaded. Adding them to the
dependency array ensures the effect re-runs once the values are available;
guards on existing geojson/timeseries state prevent double-fetching.
Also add @maplibre/maplibre-react-native to the Expo plugins array so that
expo prebuild applies the library's required Gradle property configuration to
the Android project.
- Add /api/activity/{id}/geojson and /api/activity/{id}/timeseries endpoints
(bearer-token-gated, falls back from _merged to raw activities dir)
- Rewrite activity detail screen with MapLibreGL v11 API (Map, Camera,
GeoJSONSource, Layer) and react-native-svg area chart with gradient fill
- On-demand fetch for remote activities that have no local geojson/timeseries
- Add react-native-svg dependency; requires dev build (npx expo run:android)