BACK_TO_BLOG
·3 min read·Building / Engineering / AI

Curvy loops, GraphHopper, and a week of on-device fixes

Switchback got its flagship planner feature — a real curvy loop generator — plus four navigation warnings powered by GraphHopper details.

Almost everything this week landed in kickstand-web — the Switchback Moto codebase. 83 PRs. The two things worth calling out are the planner rewrite around GraphHopper and a long tail of on-device fixes from actually riding with it.

GraphHopper, and what it unlocks

The web and mobile planners now route through GraphHopper's flexible engine for anything above curviness 0 (#391). ORS could only avoid highways; GraphHopper's custom model actually seeks twisty backroads, which is what Detecht and Cardo Ride have had for years and we didn't.

Once every routed request runs through GraphHopper, the per-segment details it returns become free features:

  • Posted speed limits in Live Nav (#391) — a US-style sign plus an over-limit warning, parsed from OSM max_speed.
  • "Unpaved ahead" voice cue (#400) — 500 m out, once per stretch, realigned on reroute.
  • "Toll / ferry ahead" (#411) — same pattern, different details fields.
  • "Steep climb / descent ahead" (#412) — grade computed on an ~80 m window so elevation noise doesn't trigger it.
  • Avoid tolls / avoid ferries (#394) — same soft-penalty custom model as avoid-unpaved.
  • Curvy round-trip loop generator (#390, #415, #425) — pick a length, hit shuffle, get a real loop back to your start. The first attempt applied a fixed 0.62 shrink to counter GH's overshoot; measurements across terrain showed the overshoot is wildly variable, so #425 replaces it with a calibrated iterative approach.

The Scenic Off default (#379 from last week) surfaced a bug I hadn't seen: building an AI route silently flipped the rider back to Light because onPlan applied the AI's chosen curviness to the Scenic control. Fixed in #383. Related, the AI was still grounding its waypoints on scored atlas roads even at Scenic Off, so a "direct" request would bend onto a named road — prompt fix in #387.

The Apple rejection

Build 1.0(6) got rejected under Guideline 3.1.1 for external-purchase steering. The IAP paywall itself is compliant; the violations were in-app CTAs pointing at switchback-moto.com. Scrubbed in #384 — except #384 missed two blurbs on reachable screens ("Upgrade on the web to ride together" on group rides, same phrasing on bike maintenance). Caught and fixed in #445, plus a CI guard so the exact rejected phrasing can't come back.

That's the kind of thing that pays for CI. The quality job had been failing on every PR (#448 — two pre-existing lint errors and a stale test), so it produced no signal, so a regex-checkable regression slipped past.

Mobile map on Mapbox

The mobile app moved off react-native-maps onto the native Mapbox SDK (#427), so it now uses the same Studio-hosted switchback-dark style as the web planner. Every real map on mobile funnels through one component (components/real-route-map.tsx) and its resolver — the prop interface was preserved byte-for-byte, so nothing downstream had to change. #431 removed the dead react-native-maps stack and the vestigial "Native maps" toggle in Settings.

Everything else

An admin Social Studio that turns a POI or blog into a branded social image, overlay, and caption pack (#422) — plus a moving route-line / rally-flag animated centerpiece (#426, #429, #433, #435). A morning trend-research agent that emails a grounded daily social brief (#423, #424). First-run onboarding with a coach-mark tour (#432, #440, #443, #446). An admin-controlled system-status banner across web and mobile (#436). Ride Report finally shows the ride on a real basemap on both platforms (#396, #397).

Ship the flagship, then spend the rest of the week fixing what the flagship revealed. That's the shape of most weeks that matter.