Surface trip-plan errors as a classified, dismissible snackbar#1942
Conversation
Replace the flat String trip-plan error with a structured TripPlanError (category + severity + specific detail) and render it as a persistent, dismissible Material 3 snackbar over the directions map, in place of the old DirectionsMessageCard. - TripPlanError: a coarse Category (each with a stable header string and a Severity) plus the exact OTP1/OTP2 detail message, so no wire information is flattened away. Both planner paths classify into it via testable top-level functions (otp1ErrorFor for OTP1 REST codes, otp2ErrorFor for OTP2 GraphQL RoutingErrorCode) and throw a TripPlanException (an IOException, so the existing runCatching/monitor flows are unaffected); the ViewModel maps failures via Throwable.toTripPlanError(). - Snackbar: a consistent severity-coloured header (every no-route failure reads "Cannot find route") over a single reason line, with an explicit dismiss. Persists until dismissed or the plan state changes, so a deterministic failure isn't lost to an auto-timeout. - Severity colours use the theme error role plus new semantic warning/info text tokens (Material ships only error); values are tuned per light/dark so the header stays legible on the inverting inverseSurface bar. - Tests: TripPlanErrorMappingTest covers both classifiers exhaustively; TripPlanViewModelTest covers classified vs. Unknown-fallback failures.
|
Warning Review limit reached
Next review available in: 16 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughTrip-planning failures now use typed ChangesTrip-planning error flow
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant TripPlanner
participant TripPlanRepository
participant TripPlanViewModel
participant HomeScreen
participant DirectionsErrorSnackbar
TripPlanner->>TripPlanRepository: classify OTP failure
TripPlanRepository->>TripPlanViewModel: throw TripPlanException
TripPlanViewModel->>HomeScreen: publish PlanResult.Error
HomeScreen->>DirectionsErrorSnackbar: render TripPlanError
DirectionsErrorSnackbar->>TripPlanViewModel: clearPlanResult on dismiss
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
onebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanRepository.kt (1)
107-109: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueConsider defaulting to
NoRoutewhen the server omits an error object.If the server returns an empty itinerary list without an
errorobject,response.error?.id ?: -1defaults to-1, whichotp1ErrorFormaps toTripPlanError.Unknown. Since the companion object documentation forTripPlanError.NoRouteexplicitly notes it is for the "(defensive) empty-results case", consider using it as the fallback here so users see "Cannot find route" instead of an unclassified error.💡 Proposed defensive fallback
- if (itineraries.isNullOrEmpty()) { - throw TripPlanException(otp1ErrorFor(response.error?.id ?: -1)) - } + if (itineraries.isNullOrEmpty()) { + val errorId = response.error?.id + val error = if (errorId != null) otp1ErrorFor(errorId) else TripPlanError.NoRoute + throw TripPlanException(error) + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@onebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanRepository.kt` around lines 107 - 109, Update the empty-itinerary handling in the TripPlanRepository flow to use TripPlanError.NoRoute when response.error is absent, while preserving otp1ErrorFor mapping when an error object is provided. Ensure the defensive empty-results case reports “Cannot find route” instead of Unknown.onebusaway-android/src/test/java/org/onebusaway/android/ui/tripplan/TripPlanErrorMappingTest.kt (1)
26-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the OTP1 coverage exhaustive as documented.
Several
OtpErrorIdmembers—including combined geocode, ambiguity, and triangle variants—are not asserted. Add table-driven cases for every enum member, or narrow the KDoc claim.Also applies to: 38-82
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@onebusaway-android/src/test/java/org/onebusaway/android/ui/tripplan/TripPlanErrorMappingTest.kt` around lines 26 - 29, Add exhaustive table-driven OTP1 cases in TripPlanErrorMappingTest covering every OtpErrorId member, including combined geocode, ambiguity, and triangle variants, and assert each mapped category and detail string; otherwise narrow the KDoc claim to match the existing coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@onebusaway-android/src/main/java/org/onebusaway/android/ui/home/directions/DirectionsFeature.kt`:
- Around line 292-323: Add a polite accessibility live region to the
`DirectionsErrorSnackbar` `Surface` by applying `semantics { liveRegion =
LiveRegionMode.Polite }` to its modifier, ensuring newly displayed planning
errors are announced without changing the existing layout or dismissal behavior.
---
Nitpick comments:
In
`@onebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanRepository.kt`:
- Around line 107-109: Update the empty-itinerary handling in the
TripPlanRepository flow to use TripPlanError.NoRoute when response.error is
absent, while preserving otp1ErrorFor mapping when an error object is provided.
Ensure the defensive empty-results case reports “Cannot find route” instead of
Unknown.
In
`@onebusaway-android/src/test/java/org/onebusaway/android/ui/tripplan/TripPlanErrorMappingTest.kt`:
- Around line 26-29: Add exhaustive table-driven OTP1 cases in
TripPlanErrorMappingTest covering every OtpErrorId member, including combined
geocode, ambiguity, and triangle variants, and assert each mapped category and
detail string; otherwise narrow the KDoc claim to match the existing coverage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: aea1dfc0-d3ac-4307-88bc-adfc40d5f996
📒 Files selected for processing (13)
onebusaway-android/src/main/java/org/onebusaway/android/api/contract/OtpPlanModels.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/home/HomeScreen.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/home/directions/DirectionsFeature.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/Otp2Planner.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanError.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanFormState.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanRepository.ktonebusaway-android/src/main/java/org/onebusaway/android/ui/tripplan/TripPlanViewModel.ktonebusaway-android/src/main/res/values-night/colors.xmlonebusaway-android/src/main/res/values/colors.xmlonebusaway-android/src/main/res/values/strings.xmlonebusaway-android/src/test/java/org/onebusaway/android/ui/tripplan/TripPlanErrorMappingTest.ktonebusaway-android/src/test/java/org/onebusaway/android/ui/tripplan/TripPlanViewModelTest.kt
…ve test
- DirectionsErrorSnackbar: mark the surface as a polite live region so a screen
reader announces a newly surfaced planning failure.
- OTP1 empty-result with no error object now classifies as NoRoute ("Cannot find
route") instead of Unknown, mirroring the OTP2 path.
- TripPlanErrorMappingTest: make the OTP1 coverage exhaustive (table-driven over
every OtpErrorId, with an assertion that fails if a new code lacks a mapping),
matching the test's documented claim.
* Surface trip-plan errors as a classified, dismissible snackbar Replace the flat String trip-plan error with a structured TripPlanError (category + severity + specific detail) and render it as a persistent, dismissible Material 3 snackbar over the directions map, in place of the old DirectionsMessageCard. - TripPlanError: a coarse Category (each with a stable header string and a Severity) plus the exact OTP1/OTP2 detail message, so no wire information is flattened away. Both planner paths classify into it via testable top-level functions (otp1ErrorFor for OTP1 REST codes, otp2ErrorFor for OTP2 GraphQL RoutingErrorCode) and throw a TripPlanException (an IOException, so the existing runCatching/monitor flows are unaffected); the ViewModel maps failures via Throwable.toTripPlanError(). - Snackbar: a consistent severity-coloured header (every no-route failure reads "Cannot find route") over a single reason line, with an explicit dismiss. Persists until dismissed or the plan state changes, so a deterministic failure isn't lost to an auto-timeout. - Severity colours use the theme error role plus new semantic warning/info text tokens (Material ships only error); values are tuned per light/dark so the header stays legible on the inverting inverseSurface bar. - Tests: TripPlanErrorMappingTest covers both classifiers exhaustively; TripPlanViewModelTest covers classified vs. Unknown-fallback failures. * Address PR #1942 review: a11y live region, NoRoute fallback, exhaustive test - DirectionsErrorSnackbar: mark the surface as a polite live region so a screen reader announces a newly surfaced planning failure. - OTP1 empty-result with no error object now classifies as NoRoute ("Cannot find route") instead of Unknown, mirroring the OTP2 path. - TripPlanErrorMappingTest: make the OTP1 coverage exhaustive (table-driven over every OtpErrorId, with an assertion that fails if a new code lacks a mapping), matching the test's documented claim. * Pin resolved trip-plan endpoints on the map as they're set Drop a green (From) / red (To) pin on the home map as soon as each directions endpoint resolves to coordinates — a map-tap pick, a long-press "directions from/to here", or a selected placename/address — before a full itinerary is planned. So a single-endpoint state already shows the point. - DirectionsMapController.setEndpoints(from, to): draws/updates the two endpoint pins via the same MapHost.addMarker green/red hues the itinerary's own start/end pins already use, diffing against the current pins so an unchanged endpoint keeps its marker (no flicker) and a null endpoint drops its pin. - The pins are driven from HomeScreen (a LaunchedEffect on the resolved From/To coords) through the existing MapDirective channel (SetDirectionsEndpoints -> MapViewModel.setDirectionsEndpoints), matching the interleaved ShowItinerary/ClearItinerary lifecycle. The itinerary's own pins supersede these once one draws; leaving directions clears them. Note: the MapLibre renderer drops the generic-marker hue (a pre-existing gap the itinerary start/end pins already share), so the pins are green/red on obaGoogle but color-indistinguishable on obaMaplibre.
What
Replaces the flat
Stringtrip-plan error with a structured error ontology and surfaces it as a persistent, dismissible Material 3 snackbar over the directions map (in place of the oldDirectionsMessageCard).Model —
TripPlanErrorA coarse
Category(each with a stable header string + aSeverity) plus the exact OTP1/OTP2 detail message, so none of the fine-grained wire information is flattened away.otp1ErrorFor(OTP1 REST integer codes) andotp2ErrorFor(OTP2 GraphQLRoutingErrorCode+InputField) — and throw aTripPlanException. It extendsIOException, so the existingrunCatching/runCatchingCancellablewrapping and the trip-plan monitor's empty-list-on-failure path are unaffected.TripPlanViewModelunwraps viaThrowable.toTripPlanError(), falling back toUnknown.Category.headerRes+detailRes.UI — the snackbar
INFO), amber = non-error (WARNING), red = failure (ERROR). Red reuses the themeerrorrole;warning/infoare new semantic text tokens (Material ships onlyerror), tuned per light/dark so the header stays legible on the invertinginverseSurfacebar.Notes
error.messagesites (route info, trip details) are unrelated and untouched.Testing
TripPlanErrorMappingTest— exhaustively covers both classifiers (every OTP1 code and OTP2RoutingErrorCode→ expected category + detail).TripPlanViewModelTest— a classified failure surfaces itsTripPlanError; an unclassified throwable falls back toUnknown.-PwarningsAsErrorsgate for bothobaGoogleandobaMaplibre; installed and exercised on a Pixel 7 Pro.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests