Skip to content

Synthesize a rental deep link when the operator's feed publishes none #2158

Description

@bmander

#2156 wired an "Open in Lime" chip that today never opens Lime.

rentalUris is null on all 13,396 rental vehicles the Puget Sound OTP2 deployment serves (checked against the live server, 2026-08-03, across both its networks). The sibling iOS app establishes that this is not a local quirk: OBAKit/Mapping/Layers/RentalDeepLink.swift records a survey of all 48 Lime systems in MobilityData's catalog (~87,000 vehicles) finding zero published rental_uris. Deep links are a Lime Transit Partnership feature; the public feed omits them worldwide.

So the deep-link path is dead by construction, not pending a feed fix. The chip falls through to opening the operator's app at its home screen, which drops the rider on a map they must re-find their own bike on.

What iOS does

RentalDeepLink.target(for:) synthesizes the link from the operator's URL scheme and the vehicle's own id when the feed publishes none, ranked below a feed-published URI and above the operator's home page:

  • Lime: limebike://map?selected_vehicle_id=<id>&generated_at=<epoch>
  • Bird: bird:// — Bird cannot target an individual vehicle, so it only ever gets the app-launch form

Android has what it needs. TripVehicleRental.id carries the network:id the raw GBFS bike_id is recoverable from (strip through the first colon), and #2156's fallback chain already degrades gracefully when nothing on the device claims a scheme.

🚩 This is a human sign-off gate

Twice over, under CLAUDE.md's "No unsanctioned heuristics":

  1. The URL schemes are reverse-engineered and undocumented by the operators — iOS cites ubahnverleih/WoBike.
  2. iOS's generated_at is an inferred format (epoch seconds against an untyped <timestamp> in the documented shape).

Tolerable there only because failure is graceful: UIApplication reports success == false when no app claims the scheme, and the caller opens the store fallback. Android's ExternalIntents.openFeedUri already reports the same thing. But this needs a human to accept the guesswork before merge — it is not a call an agent makes alone.

Tasks

  • Confirm the Android schemes. iOS's limebike:// is its iOS scheme and does not transfer; Bird's own GBFS rental_apps.android.store_uri / discovery_uri is the authority for Bird. If a scheme can't be sourced, that operator gets no synthesis rather than a guess.
  • Build with Uri.Builder, never string interpolation — iOS documents why it holds URL components: escaping a query value does not escape & or = inside it.
  • Re-resolve the target at tap time if the URI carries a timestamp (iOS does, so a sheet left open doesn't send a stale one the operator's app may reject).
  • Rank: feed-published URI > synthesized > operator app > operator site. That is RentalPickups.links(), whose ordering doc already explains each step.
  • Decide whether the row may still say "Open in Lime" for a synthesized link. It names the exact vehicle, but on a scheme the app inferred rather than one the operator published.

Related: #2150, #2138 (the reservation-handoff milestone this deliberately stops short of).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions