Skip to content

fix(mobile): nav-start crash + stale-origin + missing recenter FAB - #65

Merged
cafca merged 1 commit into
mainfrom
claude/hungry-ishizaka-28f189
Apr 25, 2026
Merged

fix(mobile): nav-start crash + stale-origin + missing recenter FAB#65
cafca merged 1 commit into
mainfrom
claude/hungry-ishizaka-28f189

Conversation

@cafca

@cafca cafca commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Summary

Three coupled bugs in the iOS navigation flow, all surfaced during a 20-minute real-device ride.

1. EXC_BREAKPOINT crash on nav start (GlitchTip BEEBEEBIKE-APP-4)

Int(p.durationRemaining * 1000) in Serialization.swift traps when ferrostar emits a non-finite duration on the first progress tick. GlitchTip captured the dict key duration_remaining_ms as the trap's value, pinpointing the line. Likely cause inside ferrostar: 0/0 from snapped speed=0 or a missing GraphHopper duration field. Root cause inside the Rust core not pursued — the FFI bridge shouldn't crash on degenerate input regardless.

Guarded:

  • Int(durationRemaining * 1000) → finite + bounded check (NaN/Inf/overflow → 0)
  • UInt16(courseDeg) → drop CLLocation's -1 sentinel and out-of-range values
  • distance fields → fall back to 0 when non-finite

Defense-in-depth: maplibreToUserLocation in Dart also filters invalid bearing/speed/accuracy before they cross the boundary.

2. New routes started 20-30m back from current GPS

Geolocator.getLastKnownPosition() reads the system-wide cache, which lags MapLibre's own location stream by up to half a minute at cycling speed. Added a userLocationProvider (StateProvider<UserLocation?>) populated from MapLibre's onUserLocationUpdated — same source as the blue dot. Used as the preferred input when seeding nav, picking route origins (_handleMapTap, _navigateHome), and refreshing the preview after a reroute. Geolocator stays as a fallback for the cold-start case.

3. Recenter FAB and compass never appeared during nav

The awaitingFirstFix → following transition was wired to ferrostar's snapped_location stream, which only emits once a tick lands. On real devices that didn't happen for the entire 20-minute ride, so the camera stayed in awaitingFirstFix and the FAB never rendered. Now driven off the cached MapLibre fix at session start (synchronously) with onUserLocationUpdated as the fallback for the no-cache edge case. Renamed onFirstFixonNavStart per design feedback ("first" was misleading mid-session).

iOS deployment target 13.0 → 16.0

Podfile already declared 16.0; pbxproj had been stale at 13.0. Pod-only sim builds let the mismatch slide, but SPM device-build resolution caught it (the ferrostar Swift Package requires .iOS(.v16)).

Test plan

  • just test-mobile — 174 passing (3 new converter tests)
  • just test-ferrostar-flutter-plugin — 21 passing
  • flutter analyze clean
  • flutter build ios --simulator --no-codesign clean
  • Real-device ride: start nav, verify (1) no crash, (2) route originates from current GPS not 20-30m back, (3) recenter FAB + compass surface after panning the map mid-ride
  • Confirm no new EXC_BREAKPOINT issues land in GlitchTip after a few sessions

Three coupled bugs in the navigation flow:

1. EXC_BREAKPOINT crash on session start (GlitchTip BEEBEEBIKE-APP-4):
   `Int(p.durationRemaining * 1000)` in Serialization.swift traps when
   ferrostar emits a non-finite duration on the first progress tick.
   Guard the FFI bridge against NaN/Inf/overflow on duration + distance
   fields, and against CLLocation's -1 sentinel for course (UInt16 init
   trap). Defense-in-depth: also filter bearing/speed/accuracy at the
   Dart layer in maplibreToUserLocation before they cross the boundary.

2. New routes started 20-30m back from current GPS. Geolocator's
   last-known cache lagged behind MapLibre's stream. Cache the live
   MapLibre fix in userLocationProvider (same source as the blue dot)
   and prefer it when seeding nav, picking route origins, or recentering
   after reroute.

3. Recenter FAB / compass never appeared during real-device nav. The
   first-fix transition was wired to ferrostar's snapped_location
   stream, which doesn't emit until well into the session. Drive the
   awaitingFirstFix → following transition off the cached MapLibre
   location at session start instead, with onUserLocationUpdated as the
   fallback. Renamed onFirstFix → onNavStart for clarity.

Also bump iOS deployment target 13.0 → 16.0 (Podfile already declared
16.0; pbxproj had been stale and SPM device builds caught the mismatch
where pod-only sim builds let it slide).
@cafca
cafca merged commit 9b96a5d into main Apr 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant