vineroute// field manual
Rider
Driver
Dispatch
Systems
The Rider/Today
03 / 08

Today

The route, the ETA, the driver — in that order

CoreRealtimeMap

Overview

The home tab loads the rider's next confirmed reservation, the route it's on, and a live SSE feed of the vehicle's position. The RouteHero sits at the top. Below it, a LiveMap shows the route polyline, stop markers, and an animated vehicle marker that updates as new positions arrive. An EtaBanner hovers above. The DriverCard below shows the driver's portrait, name, and a stats row (PRO · YRS · TOURS · star rating).

How it works

1

Three React Query queries fire in parallel on mount: `/api/passenger/today/route`, `/api/passenger/today/driver`, and `/api/passenger/announcements`. Each owns its own loading state so the page paints progressively.

2

Realtime: an EventSource subscription opens to `GET /sse/vehicles` with the JWT in the URL. The SSE stream emits `vehicle` events with assignmentId, lat, lng, speedMps, headingDeg. We match the rider's assignment and update the marker.

3

ETA computation: the haversine distance from the vehicle to the next un-passed stop, divided by speedMps (clamped to a minimum so a stopped bus doesn't show infinity), gives the minute estimate. Recomputed on every SSE event.

4

Threshold notifications: a `useEffect` watches the ETA and tracks the last threshold bucket (10/5/1 minutes). When the bucket changes, we fire the EtaToast with the appropriate copy.

5

Tapping a stop in the list opens the StopDetailSheet. Tapping the driver card opens the DriverBioSheet.

Key decisions

Apple Maps on mobile, Mapbox on web

The mobile app uses react-native-maps with the Apple Maps backend, which is free and ships with iOS. The admin console uses Mapbox light-v11 because it gives us a much more refined visual aesthetic on a large screen. The trade-off is consistency vs. cost — we picked cost because passengers tap their phones; stakeholders look at the dispatch dashboard.

SSE over WebSockets

Vehicle positions are one-way pushes. SSE gives us that with a simple HTTP connection, automatic reconnects, no framing protocol, and no extra dependency. We don't need bidirectional messaging on this stream. The web admin uses the same endpoint identically.

Threshold-based toasts, not continuous

Showing the ETA tick every second would be noise. Showing a toast 'arriving in 10 minutes', then '5 minutes', then '1 minute' gives the rider three useful moments instead of 600. We track which bucket we have fired in a ref so the same threshold does not fire twice on a single approach.

Today

Quick Reference

Routeapps/mobile/app/(passenger)/(tabs)/today.tsx
StoreReact Query + AuthContext + local refs
APIsGET /api/passenger/today/route, /today/driver, /announcements, SSE /sse/vehicles
Components
RouteHeroLiveMapEtaBannerDriverCardStopRowEtaToastAnnouncementRow
PreviousOTP entryNextDriver bio sheet