Synthesize a rental deep link when the feed publishes none (#2158) - #2161
Draft
bmander wants to merge 1 commit into
Draft
Synthesize a rental deep link when the feed publishes none (#2158)#2161bmander wants to merge 1 commit into
bmander wants to merge 1 commit into
Conversation
#2156's "Open in Lime" chip never opened Lime on a vehicle: `rentalUris` is null on all 12,958 rental vehicles the Puget Sound OTP2 deployment serves, and the iOS survey of all 48 Lime systems in MobilityData's catalog found the same worldwide. The chip fell through to launching the app at its home screen, dropping the rider on a map they must re-find their own bike on. Ported from onebusaway-ios' `RentalDeepLink`: when the feed publishes no link, build one from the operator's own URI shape and the vehicle's id, ranked below a feed-published URI and above the plain app launch. The shape is Lime's own rather than reverse-engineered. Lime publishes `rental_uris.android` in the cities where it turns the field on — all 7,097 vehicles in its Washington DC feed carry `limebike://map?selected_vehicle_id=<id>&generated_at=<seconds>`, under the `android` key and identical to `.ios` — which settles both the Android scheme (iOS's `limebike://` does transfer) and `generated_at`'s unit, neither of which the app now has to infer. Bird gets no synthesis: it takes no vehicle id, and its own Android `discovery_uri` is `bird://charger-onboarding`, a link for people who charge scooters. - URIs are assembled with `Uri.Builder` from held components, so an id carrying `&`/`=` stays one parameter, and re-resolved at the tap so the stamp isn't the one from when the sheet was opened. - `TripVehicleRental.kind` records which of OTP's two rental shapes an endpoint came from, so a dock's station id is never sent as a vehicle id — structural, not read off whether a name was published. Human sign-off gate (CLAUDE.md, "No unsanctioned heuristics"), with a device result against it: Lime's published links carry a short code, not the GBFS `bike_id` UUID that OTP hands us, and tapping a link built from the UUID on a Pixel 7 Pro with Lime installed produced Lime's "Resource not found" error screen. So the graceful-failure premise this port rests on — and that the iOS original rests on — does not hold: Lime claims the scheme, accepts the URI and fails inside its own app, past the point any fallback can see. The URI is byte-identical to what iOS emits for the same vehicle, and iOS's tests assert only that the URL is well-formed, so neither platform has shown the link selecting a bike. Draft for that conversation, not for merge as it stands.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2158 — or argues that it can't be closed this way. Draft on purpose: it works end to end, and on the device it makes the tap worse. Opening it so the finding can be taken to the iOS side, which ships the same link.
What it does
Ports onebusaway-ios'
RentalDeepLink. When a rental publishes norentalUris— which is all 12,958 vehicles the Puget Sound OTP2 deployment serves — the app builds the operator's own "show me this vehicle" URI from its scheme and the vehicle's id, ranked feed URI > synthesized > operator app > operator site.RentalLink.Synthesizedholds URI components, not a string:Uri.Builderescapes each query value, so a vehicle id carrying&or=stays one parameter. It's assembled at the moment of the tap, sogenerated_atisn't a stamp from whenever the sheet happened to load.TripVehicleRental.kind(VEHICLE/STATION) records which of OTP's two rental shapes an endpoint came from, set at the adapter where OTP states it. A dock's station id can't be sent as aselected_vehicle_id, and that's structural rather than inferred from whether a name was published.mayNeedTheirApp/namesTheVehiclemoved ontoRentalLink, so the fallback filter and the chip's wording stop type-testing the variants.The sourcing came out better than the issue expected
The issue flagged the scheme as reverse-engineered (ubahnverleih/WoBike) and
generated_at's unit as inferred. Both turn out to be first-party. Lime publishesrental_uris.androidin the cities where it turns the field on — on 2026-08-03 all 7,097 vehicles indata.lime.bike/api/partners/v1/gbfs/washington_dc/free_bike_statuscarried:under the
androidkey, byte-identical to.ios. Solimebike://is not an iOS-only scheme, andgenerated_atis epoch seconds read off published values rather than guessed from magnitude. Seattle simply has the field switched off.Bird gets no synthesis, on its own evidence: its GBFS
rental_apps.android.discovery_uriisbird://charger-onboarding— for people who charge scooters — and nothing there takes a vehicle id. iOS carries abird://app-launch entry; Android has no use for one, sinceOperatorApp("co.bird.android")already does that and sends a rider without the app to the Play listing instead of nowhere.🚩 Why this is a draft
The id is the one step nothing published can source, and the device says it's wrong.
Lime's own links carry a short code (
IBO2JSMUXZVUQ). The GBFSbike_idbeside it in the same record is a UUID — and the UUID is all OTP hands us, sincevehicleIdis thatbike_idunder its network prefix. Tapping a link built from the UUID on a Pixel 7 Pro with Lime installed produced Lime's "Resource not found" error screen. Not its map.That breaks the premise the whole approach rests on, on both platforms. "Failure is graceful" covers the case where no app claims the scheme —
ExternalIntents.openFeedUrireturns false and the caller falls back. It does not cover this: Lime claims the scheme, accepts the URI, and fails inside its own app, past the point any fallback can see. On today's evidence a rider is left worse off than with #2156's plain app launch.What this means for onebusaway-ios
The URI is byte-identical to what
RentalDeepLink.target(for:)emits for the same vehicle — same scheme, host, parameter, timestamp unit, same strip-through-the-first-colon on the same OTPnetwork:id. iOS reads its rentals from the same OTP2 deployment, so it sends the same UUID.RentalDeepLinkTests.swiftis 15 tests, every one of them an assertion about URL construction — escaping, ranking, the station case. None can observe what Lime does with the URL. The synthesis has never been shown to select a bike on either platform; what's verified is that the string is well-formed.Caveat worth stating: the Lime iOS app is a different client from the Lime Android app tested here. Near-certainly the same backend lookup, but not proven.
Options from here
Testing
compileObaGoogleDebugKotlin -PwarningsAsErrors=trueclean,spotlessCheckclean, unit suite green.RentalPickupsTestcovers the ordering, the dock fall-through, the id stripping and Bird's deliberate absence; the new instrumentedRentalDeepLinksTestcovers theUriassembly (delimiters in an id, epoch-seconds truncation) and has not been device-run. The behaviour above was verified by hand on a Pixel 7 Pro.