Change list and test notes
Back to the app launcher
Add only, remove nothing. No existing function, screen, field or Firebase configuration was deleted.
The three files you supplied are kept byte for byte as the backup masters under /original/. The working
copies are the ones the launcher opens, and every change in them is either a new file, a new function, a wrapper around
an existing function (the original stays reachable on window.originalRiderFirebase and
window.originalDriverFirebase), or an added column, field or section.
Firebase layout, as found
| Project | Used by | Holds |
driver-pro-card-login | Rider app, Admin app, and now the Driver app for ride data |
trips, active_trip_request, pricing, reservations, riders,
lost_and_found, support_chats, plus the mirrored drivers and new driver_locations |
rider-s-drivers-info | Driver app login and approval |
drivers/{uid} with the full application, documents and bank details |
The Driver app also loads the Firebase compat SDK for its wallet screen, pointed at
driver-pro-card-login. That was left exactly as it was.
Root cause
The Rider app writes a request to active_trip_request and trips/{id} in
driver-pro-card-login. The Driver app was listening to active_trip_request in
rider-s-drivers-info. Two different projects, so the request never arrived, no driver was ever assigned,
and Admin only ever saw an unassigned request. Everything else downstream (chat, GPS, payment, ratings) failed for the
same reason.
Rider app
- Added
shared/rider-bridge.v1.js. The original app code and every original Firebase function are untouched.
tripId is now written next to id, and both hold the same value for the life of the trip.
- A stable
riderId is generated once per device and stored, and a riders/{riderId} profile is written,
so the Admin rider list is no longer empty.
- Requesting a ride while one is already running is blocked, so a double tap cannot create a second trip.
- Added a payment method selector. The choice travels on the trip record and is visible to Driver and Admin.
- Chat messages, tips, the star rating and the payment status are now written to
trips/{tripId} as well as to the
live pointer, using transactions so the Driver app cannot overwrite them.
- The driver is now drawn on the rider map from
trip.driverLocation, and the status card shows the driver name,
vehicle and trip id.
- Clearing a ride before it completes now marks the stored trip
CANCELLED instead of leaving it stuck as requested.
Driver app
- Added
shared/driver-bridge.v1.js. Login, approval, the wallet, the maps, the ringtone and the whole UI are untouched.
- The driver screens are now wired to the canonical database, which is where ride requests actually appear.
- Accepting a ride runs an atomic transaction: the first driver wins, a second driver is told the ride is gone, and no
second trip record is created.
- Accepting writes
driverId, driverName, contact and vehicle onto the existing trip.
- Status changes are guarded so a trip can only move forward, and only the driver who owns the trip can move it.
- Live GPS is published to
driver_locations/{uid} and attached to the running trip (throttled to one write every
three seconds). The legacy drivers/driver_location node is still written, so nothing that read it breaks.
- A safe profile mirror (name, email, phone, vehicle, plate, approval state, presence) is written to the canonical project so
Admin can see and manage drivers. SSN, bank details and document photos are deliberately NOT copied: they stay in the
driver login project.
- Admin approve and suspend decisions are applied live, without a reload, and mirrored back into the driver login database.
- Completing a cash ride asks the driver whether the money was collected and records the answer as the payment status.
Admin app
- Drivers are read from both projects and merged, so a driver shows up whether they registered in the driver login project or
have already used the Driver app.
- The legacy
drivers/driver_location GPS node is no longer mistaken for a driver account.
- Approve and suspend now write to both projects.
- Live trips show the trip id, the real driver name and plate, the payment method and status, the tip and a live GPS link.
- Trip history gained trip id, tip, payment and rating columns.
- Revenue is counted once per trip id across completed trips. The old total double counted the active trip and ignored history.
Company earnings, driver payouts and completed trip counters were added next to it.
- Added the support chat panel, the missing other half of the support window that already existed in the Rider app.
Shared layer and tests
shared/rideshare-sync.v1.js holds the rules all three apps agree on: one trip id, forward only status, first
driver wins, message merging, fare and payout maths, payment states and revenue totals. It is pure JavaScript with no
Firebase import.
- Those rules are covered by unit tests in the repository (
tests/sync.test.mjs, run with node --test),
including: accepting never mints a new trip id, a second driver cannot claim a taken ride, status cannot go backwards,
simultaneous chat messages are merged rather than overwritten, tips go entirely to the driver, and revenue is never
double counted.
Hardening after the code review
A second pass over the bridges found races that only show up when two people act at the same moment. All of these
are fixed and covered by tests:
- Every write to
active_trip_request now runs inside a transaction that refuses to recreate a ride that
has just been cleared. A blind write there used to leave a half empty record that all three apps read as a brand
new request, and it locked the rider out of booking again.
- The rider apps never write to a trip they do not own, and never write at all without a known trip id.
- Cancelling checks the stored trip inside the transaction, so a ride the driver completed a second earlier cannot
be rewritten as cancelled (which would also have removed it from the revenue figures).
- Closing the tip window no longer marks an uncollected cash fare as paid, and no longer stamps a five star rating
on a driver the rider never rated.
- COMPLETED and CANCELLED are terminal: nothing moves a finished ride again. Re-accepting a ride you already have
cannot rewind it from "driver arrived" back to "on the way".
- The rider profile is merged rather than overwritten, so the phone number and joined date survive.
- Suspending a driver now publishes the offline state before the approval is withdrawn, so the driver does not stay
shown as online. Only one admin decision watcher runs at a time, bound to the driver who is actually signed in.
- The Driver app's original listener on the other database can no longer tear down a live trip card.
- Admin can open the details and delete drivers that exist only in the driver login project.
Second review pass: five defects fixed
- Driver write loop. Applying an admin approve or suspend decision wrote a fresh timestamp to the same
record the decision listener watches, so the listener fired again and wrote again. A pending or suspended driver's tab
would write to Firebase without stopping. The decision is now applied once per actual change, and that branch no longer
writes to the watched record at all. Measured in the browser test: the same scenario used to produce a thousand writes
in two and a half seconds, it now produces at most two.
- One rider could delete another rider's live request. Clearing a finished ride used to delete the shared
live pointer without looking at what was in it. Rider A closing a stale window could wipe rider B's brand new request.
Clearing is now a transaction that only removes the pointer when it really is that rider's ride.
- Stale mirrors could revert newer information. Copying a live snapshot onto the stored trip could turn a
completed ride back into an in progress one, undo a cancellation, or move a payment backwards. All mirrors now go through
a protected merge: completed and cancelled are terminal, status and payment never move backwards, and ratings, tips and
chat are never unwritten.
- Refused writes. A permission or network failure in the rider app used to become an unhandled promise
rejection and silently skipped the stored trip update. Both stages are now handled, the app carries on, and the failure
is logged as a warning.
- Misleading accept message. A driver tapping Accept again on a ride they already have was told another
driver had taken it. That is now treated as what it is: a no-op on their own ride.
Real time driver location on the rider map
The Driver app's device GPS was already working and already publishing. The Rider app was reading the wrong copy of it.
What the Driver app does (unchanged): navigator.geolocation.watchPosition with high accuracy,
started when the driver goes online and when a ride is accepted, stopped when the driver goes offline with no ride, logs out
or is locked. Every fix writes to four places:
drivers/driver_location in the driver login project, the original legacy node, still written;
drivers/{uid}/location in the driver login project;
driver_locations/{driverId} in the canonical project, every fix, unthrottled;
active_trip_request.driverLocation and trips/{tripId}.driverLocation, at most once every three
seconds to keep the write rate sane.
The mismatch: the Rider app only ever read the last of those, the throttled copy carried on the trip record.
So the car could move at best once every three seconds, only when the whole trip record changed, and it froze completely if
that one write was skipped. The live per driver feed that the Driver app was already publishing was not being read by anyone
except the Admin dashboard.
The fix: the Rider app now subscribes to driver_locations/{driverId} for the driver assigned to
its own trip, so the car moves as the driver moves. Details:
- The driver id comes from
trip.driverId on the rider's own trips/{tripId}. A fix is only used when
it belongs to that driver, and when the trip stamped on it matches. A rider can never follow another driver, another
rider's ride or an old trip.
- The live feed is now stamped with
tripId, tripStatus, vehicle and plate, so the rider can verify
the car it is watching, and the feed is marked offline when the driver goes offline or the tab closes.
- Following starts when a driver is assigned and stops on COMPLETED and CANCELLED, using the existing status values.
- The copy on the trip record is kept as a fallback, so a rider still sees the car if the per driver node cannot be read.
- The rider map now shows π pickup, π the driver and π the destination. The map moves to the car ONCE, when it first
appears; after that the marker moves and the map does not, so panning and zooming stay under the rider's control.
- A line under the trip status says how fresh the position is ("Driver location: live", "8 seconds ago"), and an old fix
turns the car grey rather than pretending it is current.
- The Rider app never asks for the rider's own location, so there is no second permission prompt, and the Driver app still
runs exactly one
watchPosition watcher no matter how often it goes online or accepts.
Rider payment information
The payment data was always being written. Nothing in the Rider app ever displayed it: the rider only saw the fare
estimate before the ride and the tip window at the end. Traced end to end, the money for a ride lives in one place,
trips/{tripId} in the canonical project, mirrored on active_trip_request while the ride runs:
| Field | Written by | When |
fare, estimatedFare | Rider | when the ride is requested |
paymentMethod | Rider | request, and whenever the selector changes |
paymentStatus | Rider, Driver | PENDING, AUTHORIZED on accept, CHARGED or CASH_DUE on completion, PAID at the end |
tipAmount, driverPayout | Rider | when a tip is added |
finalFare | Driver | on completion, fare plus tip |
paymentUpdatedAt | Rider, Driver | every payment change |
paymentReference (new) | system | issued once per trip on completion, derived from the tripId |
paymentAttempts, paymentFailureReason, lastPaymentAttemptAt (new) | system | only when a payment fails and is retried |
What was added, all reading that same record:
- A Payment Information section in the Rider app showing trip fare, tip, final fare, method, status,
reference, the time of the last payment update and the attempt count. It appears as soon as a ride is requested and
updates itself whenever the Driver, Admin or the payment system changes the trip. No refresh.
- The same summary inside the existing Trip Complete popup, added above the tip buttons. The stars, the
tip grid and the Submit button are untouched.
- Card details are never shown. Anything that looks like a card number is reduced to its last four
digits and any security code is stripped, so a stored value like "Visa 4111111111111111" displays as "Visa β’β’β’β’ 1111".
- FAILED joins the existing status list (PENDING, AUTHORIZED, CHARGED, CASH_DUE, PAID). No competing
status system was introduced.
- A Retry Payment button that appears only when the payment actually failed. It runs against the same
trip and the same payment reference inside a transaction, so it cannot create a second trip, a second payment record
or a second charge, and a second tap after a successful retry does nothing.
- The payment section stays visible after the ride is cleared, reading the stored trip, so the rider can still see what
they paid for their last ride.
Road distance and road time, from the routers already in the project
Two road routing services were already configured here, and both are now used for what they are good at. Nothing new was
added, no API key, nothing paid.
- Google Directions, already loaded by the Rider app, prices the ride. It resolves the typed addresses
itself, so a quote can be produced before the map has drawn anything.
- OSRM, already used by the Driver app through Leaflet Routing Machine, is the fallback for the quote and
is what works out the driver's road distance and ETA to the pickup. Nominatim, also already used by the
Driver app, turns an address into coordinates when only OSRM is available.
The rider quote is now priced only from a road route. Straight line distance and average speed guesses are
not used for it at all. If neither router answers, the ride is not created and the rider is told to check the addresses:
better than the old behaviour, which quietly used 5 miles, 10 minutes and $18.50.
The driver's pickup distance and ETA are now real. The request card used to read "~5 mins (1.8 miles)"
because nothing ever wrote pickupDistance or pickupEta. The road route is worked out from the
driver's current GPS to the rider's pickup, shown on the request card, refreshed as the driver gets closer, and saved to the
trip once the driver owns it so the rider and Admin see the same numbers.
Estimated and actual stay apart on the one trip record:
estimatedRoadMiles and estimatedRoadMinutes from the road route before the ride,
actualTripMiles and actualTripMinutes from the driver's GPS and the clock afterwards, alongside the
existing fields. The fare formula was not touched.
Upfront pricing, quoted from the road route
What was wrong with the fare. The request carried
miles: document.getElementById('miles').value || 5,
minutes: ... || 10 and a fare of "$18.50" whenever the fare box still showed $0.00. So any ride
where the route lookup did not answer, which is every ride if the map key is restricted or billing is off, was quoted at
$18.50 for 5 miles and 10 minutes, whatever the real trip was.
What it does now. The rider is quoted before the ride from the expected ROAD ROUTE, using the pricing
table Admin already controls. No second pricing system: the arithmetic is the one the Rider app has always used, base plus
distance plus time, floored at the minimum, plus the booking fee, times the period multiplier, with an airport flat rate
replacing all of it.
- The route comes from the map's road route when it answers. When it does not, the road distance is estimated from the
pickup and dropoff coordinates with a road factor and a speed band, and the trip records
routeSource: "estimated" so nobody mistakes it for a real route. The silent 5 miles, 10 minutes and
$18.50 are gone.
- The rider sees the price itemised before requesting: base fare, distance, time, minimum if it applies, booking fee,
multiplier, and the upfront price, with a line saying the price is fixed and the driver's GPS records the actual
distance and time separately.
- The whole quote is saved on
trips/{tripId} as fareQuote, with
quotedFare, quotedMiles, quotedMinutes and routeSource.
- When the ride finishes, the driver's GPS distance and clock time are stored separately, as before, and the fare the
meter WOULD have produced is recorded as
meteredFare with the difference in
fareVarianceAmount and fareVariancePercent. The rider is charged the price they were
quoted. Admin shows the meter underneath the estimate so you can see how the two compare before deciding
whether to change anything.
- If no pricing table has been saved yet, the same starting rates the apps already carried are used, so a quote is never
missing. Whatever Admin saves under
pricing always wins.
For the reported trip, Riviera Beach to Fort Lauderdale airport with no route service answering, the quote is
$74.26 for an estimated 60.97 miles and 73 minutes, against the old $18.50.
Why a 57 mile drive still read 4.2 miles
The first version of the odometer only ran between Start Trip and Complete Trip. Two
things followed from that, and both produced the old placeholder numbers:
- If the driver never pressed Start Trip, and the app allows going straight from arrived to completed, nothing was ever
measured and no
startedAt was recorded. The trip card then fell back to its built in
"4.2 mi" and "12 mins", which is exactly the pair being reported.
- Between accepting and starting, nothing was tracked at all, so the first part of the drive existed nowhere.
Tracking now runs for the whole time the driver owns the ride, from the moment they accept. The single
track is marked at arrival and at Start Trip, so it splits into the drive to the rider and the ride itself without a second
GPS system. The ride is measured from Start Trip when it is pressed, and from the arrival when it is not, so a forgetful
driver still produces a real distance. If startedAt is missing at completion it is filled in from the arrival
time and the record says startedAtEstimated, so the duration is real rather than zero.
The trip card is also refreshed every second from the live measurement, in every stage of the ride, so the app's own
placeholders can never be what the driver reads.
Measured, not displayed. The engine is checked against the reported trip: Riviera Beach to Fort
Lauderdale airport, a road route of 57.45 miles where the straight line between the two addresses is 48.78. Fed a realistic
GPS trace of that drive, the odometer reports 57.45 miles, an error of 0.00%. A ten mile leg reports ten
miles. Nothing was hard coded to display 57: the same code measures whatever is driven.
Real mileage and real trip time
What was wrong. Nothing in any of the apps ever measured how far a car drove. The driver screen read
activeTrip.tripMiles || "4.2 mi" and activeTrip.tripDuration || "12 mins", and nothing anywhere
wrote tripMiles or tripDuration. So every trip displayed those two constants. The only distance in
the system was the route estimate the rider's map produced before the ride, which is not the distance driven, and no duration
was calculated at all.
Mileage. While a ride is in progress the driver app now accumulates real distance between consecutive GPS
fixes using the haversine formula, on the same fixes the live map already publishes. No second GPS system was created. Bad
data is filtered: movements under 6 m are jitter, fixes accurate to worse than 100 m are not measured with, and anything
implying more than about 139 mph is rejected. A long gap counts the straight line rather than losing the distance. Two
impossible steps in a row re-anchor the odometer, so one bad fix, or a stale position left over from before the ride, cannot
wedge it at zero for the whole trip.
Time. startedAt is stamped when the driver presses Start Trip and completedAt
when they end it, and the duration is completedAt - startedAt. GPS frequency cannot shorten it: a twenty minute
ride reads twenty minutes even if only three fixes arrived.
Both are saved to the canonical trips/{tripId} record as distanceMeters, tripMiles,
tripMilesValue, tripDurationSeconds, tripDurationMinutes and tripDuration,
with the GPS fix counts kept for auditing. The driver sees them live on the trip card and again on the completion summary, the
rider sees them on the payment card, and Admin shows them on the live ride and in each rider's trip table.
Double clicking the wrong file
The package contains two files that both look like the Admin app. Only one of them can be opened by double clicking, and
the other one failed silently, which is indistinguishable from a broken login.
admin.html inside admin-package/ now shows a red banner when it is opened as a local file:
it explains that a browser will not load the app's shared code that way, names
admin-standalone.html as the file that does work, and gives the local server as the alternative. The
banner is a plain script, not a module, so it still appears when the modules are blocked, which is exactly when it is
needed.
admin-standalone.html now reports its own state as soon as it opens, with no button press: build, whether
it was opened as a local file, whether the shared code loaded, whether the email is on the admin list, and whether each
Firebase project can be reached.
- A page opened as a local file often has no usable browser storage, and the Firebase SDK keeps the session there. The
session is now held in memory on a file:// page, so storage restrictions cannot block a sign in.
Telling you why a login failed
The Admin login card has a Run connection test button. It reports the build that is loaded, whether the
page was opened as a local file or over http, whether the shared code loaded, whether the email is on the admin list, whether
each Firebase project can actually be reached, what the last sign in attempt returned from each project, and whether the
browser thinks it is online. One press answers "why can I not log in" without another round trip.
Running Admin on your own computer
Copying admin.html to a computer and opening it did not work, for two reasons.
- admin.html is not the whole app. It imports
shared/rideshare-sync.v1.js (the trip, fare,
payment and merge rules) and shared/firebase-canonical.v1.js (which Firebase project is which). One file
leaves those behind.
- Browsers refuse to load ES modules from a file:// page. Even with the shared folder beside it, a double
click produces "blocked by CORS policy: Cross origin requests are only supported for protocol schemes". That is a
browser security rule, confirmed by loading the real file in a real browser.
So there is now a download page with two forms of the same working app:
admin-standalone.html, one file that opens by double click because the shared code is inlined and no local
import remains, and riders-admin-package.zip, the normal layout with the shared folder and a one command local
server. The Firebase configuration, both projects, the login and every feature are the working code, copied as is.
No cash on the rider screen, even on an old trip
Trips taken before the card only change still carry paymentMethod: "Cash" in the database, and the Rider app
was faithfully printing it. A rider should not be shown a payment type they can no longer use, so:
- The Rider app never renders Cash. Where a stored trip says cash or nothing, it shows the card on the account, or
"Credit or debit card" when there is none. That is a display decision: the stored record is not touched.
- Admin has a Convert Cash Trips to Card button next to the trip history controls, for when you want the
data itself cleaned up. It rewrites the method to the rider's card, keeps the old value in
paymentMethodBefore, moves a CASH_DUE status to CHARGED with paymentStatusBefore recorded, and
leaves fares, references and everything else alone. Nothing is deleted, and running it twice changes nothing the second
time.
Card only, and the driver never handles money
Riders pay by credit or debit card. Cash is gone from the Rider app, and the driver is never asked to collect anything.
Rider app
- The payment list no longer offers cash. It shows the card on the account, and nothing else.
- A ride cannot be requested without a card. Asking for one without a card says: "Add a credit or debit card before
requesting a ride. Cash is not accepted", opens the card box and scrolls to it.
- Choosing a category is not the same as having a card: "Credit Card" as a preference does not open the gate, an actual
card reference does.
- A new Card on file box adds or updates the card. Only the brand and the last four digits are stored.
The number and the security code never reach the database, and the number is wiped from the form as soon as it is read.
With a Stripe publishable key configured, Stripe's own field is mounted and the card never touches this site at all.
- No trip can be created as cash: even if an old value were still lying in the browser, it is replaced by the card.
- The rider still sees the total fare and the payment status throughout, and everything still lands on
trips/{tripId}.
- Nothing is faked. A card saved without a payment provider reads pending, not paid. The states stay
PENDING, AUTHORIZED, CHARGED, PAID, and only a real processor can honestly move the last step.
Driver app
- The cash collection prompt is gone. No "did you collect $XX from the rider".
- Completing a trip now shows a driver summary: Trip Completed, rider, trip id, pickup, destination, distance and time
when known, payment status, and the driver's own earnings as the headline figure.
- The rider's fare is never presented as the driver's money. A $55.00 fare shows the driver
$22.00 as earnings with a separate line reading "Rider fare $55.00. Your share is 40% of the fare."
The calculation is the one the app already used, untouched.
Trip record
driverEarnings now sits next to driverPayout, carrying the same number under the name the
dashboards ask for. Payment state still only moves forward, and a card trip that has been charged or paid can never fall back
to a cash state, even from a stale mirror. Older cash trips still read and settle exactly as they always did: history is not
rewritten.
The stuck ride, the wrong payment status and the zero total
Three symptoms, three separate causes, none of them a Firebase project mismatch. Rider, Driver and Admin all read the ride
data from driver-pro-card-login, and Admin additionally reads registrations from
rider-s-drivers-info. That part was correct.
"Another ride is active right now"
active_trip_request is a single shared node. If a tab closes mid ride, or a ride is finished only in the trip
record, the pointer is left aiming at a ride that is over, and then nobody can book. Forever. The stored trip is the authority
now: a pointer to a finished ride is released, and a request with no driver that has been sitting for more than half an hour
is treated as abandoned, marked cancelled in history and released. A genuine ride in progress still blocks, and the rider is
told which ride, what state it is in, which driver has it and how long it has been running.
Admin said PENDING while the Rider app said AUTHORIZED
Both were reading the correct field on the correct record. They were reading different rides: Admin quoted the
newest trip by timestamp, the Rider app showed the ride that device was on. Admin now quotes the ride that is happening now
when there is one, names the trip id beside the status, and marks it as a live ride. With nothing running it falls back to the
newest finished ride, as before.
Trips: 2 / $0.00
The riders table showed only money that had settled, and neither of that rider's rides had completed. It now reads
"2 rides, $50.90 in fares" with the settled amount beneath, so a fare is never invisible just because the ride has not closed
out yet.
Payment Method: Not specified
That rider record carries no card because it is the record the Rider app creates on the device, not the registration record
that holds the card. Where the account has no card but the rider's rides do, Admin now shows the method used on the ride and
says plainly that it came from a ride rather than from the account.
Fares in the Rider Information view
The payment summary only added up rides that had finished, so a rider with two rides still running read $0.00 for
everything and looked as though the fare was missing. It now separates what is settled from what is not:
- Total Fares (all trips), every ride whatever its state;
- Total Paid (completed) and tips, as before;
- Not settled yet (in progress), the fares of rides still running;
- Last Trip Fare with the status of that ride;
- and, once rides have completed, what those rides produced: 40% plus tips to drivers, 60% to the company.
In the trip table inside the view, a ride that has not finished now shows its fare marked "not settled" instead of a bare
$0.00 in the Final Fare column.
Who and how much, in both trip tables
Live Active Rides & Trip Requests and Ride Request & Trip History now carry, for
every ride: the rider name with the rider id underneath, the driver name with the driver id (and plate) underneath, and the
fare split into three columns.
- Fare, the amount for the ride, with any tip shown beneath it;
- Driver 40%, and when a tip exists, the driver's total with the tip added;
- Company 60% for Rider's Transporthers.
A $32.40 fare reads $12.96 to the driver and $19.44 to the company, and the two always add back to the fare. Tips sit on
top and go entirely to the driver, so they never reduce the company share. These are the same numbers the Driver app pays out
and the dashboard totals already used, now from one shared calculation rather than three copies of the arithmetic.
Payment method on the rider record, ready for Stripe
The registration page always wrote safe card metadata, and it never wrote a card number, a security code or an expiry
date. Those stay in the form and are thrown away. What was missing was one agreed shape, so the rider record now carries:
riders/{uid}
savedCard "Visa \u2022\u2022\u2022\u2022 4242"
paymentMethodType "card"
paymentMethodLabel "Credit/Debit Card"
paymentMethodStatus "pending" (no provider yet) or "ready" (Stripe holds it)
paymentMethodId "" or "pm_..."
paymentMethodAddedAt timestamp
cardBrand, cardLast4 kept, nothing was removed
Admin reads that shape whatever wrote the record, shows the Stripe id and marks a Stripe backed method as ready. When a
rider row has no card at all it now says which fields it looked for and which source the row came from, instead of leaving
"None saved at registration" hanging.
How Stripe drops in
shared/stripe-config.v1.js holds one value, the publishable key. Paste it there and the
registration page loads Stripe.js, mounts Stripe's own card field, hides its own card inputs (they stay in the page), and
turns the card into a PaymentMethod. The card is typed into an iframe served by Stripe and never touches this site. Firebase
then stores only the brand, the last four, the pm_ id and a status.
Leave the key empty and nothing changes: the current form keeps working and keeps storing only brand and last four.
The secret key never goes in this repository. Creating a PaymentMethod needs only the publishable key.
Charging it, refunding and paying drivers need the secret key and must run on a server. The trip record is already shaped for
that: PENDING, AUTHORIZED, CHARGED, PAID, one payment reference per trip.
Locked out of a login
Both login screens can now get you back in without anyone deleting or recreating a Firebase account:
- Send password reset email on the Admin card and the Driver card. It asks Firebase for a reset in both
projects and reports which ones accepted it.
- The failure message now shows the most useful error rather than simply the last one. "Wrong password" from one project
no longer hides "email and password sign in is switched off" or "too many attempts" from the other.
- The Admin login card shows which build is loaded, so a stale cached page can be spotted at a glance instead of being
mistaken for a broken login.
The floating trip messenger
A π¬ button now sits in the top right corner of the Rider app and the Driver app. It stays hidden until a driver accepts
the ride, then it appears with an unread badge, and it opens a popup messenger for that trip.
- Same conversation on both sides. The messages live where they already lived, on the canonical trip record at
trips/{tripId}/messages, mirrored on the live trip pointer. Nothing was moved and no second chat system
was created.
- Real time in both directions through Firebase listeners: the popup reads
trips/{tripId}/messages directly as well as the copy on the live pointer, and merges them. No refresh, no
polling, no copying HTML between boxes, no localStorage as the message store.
- Every message carries a stable
mid, plus sender, senderId,
senderName, text and a timestamp. The same message arriving from two listeners is shown once,
and two identical lines sent in the same millisecond stay two separate messages. De-duplication never clears the chat.
- The header shows who you are talking to and the trip reference. The X closes the popup only: the trip, the listeners
and the conversation carry on, and anything that arrives while it is closed raises the unread badge.
- The chat card that was already on both pages is untouched and shows the same conversation.
Every place a rider can exist
The rider panel now collects riders from four sources and merges them, richest record first:
riders/{uid} in the registration project, written by the registration page;
riders/{riderId} in the canonical project, written by the Rider app;
users/{uid} with role: "Rider", in either project, which the registration page also writes;
- riders rebuilt from the trips they took.
The note under the table now names each source with its count, or NOT READABLE and the Firebase error code when the rules
refuse a read. adminRiderSources() in the browser console returns the same thing as data. An empty panel can now
be explained in one glance instead of guessed at.
The registration app writes to the other project
The registration page (registrationlogin.html) creates the Firebase Authentication account and then writes
riders/{uid}, drivers/{uid} and users/{uid} into rider-s-drivers-info.
The Admin dashboard was reading riders from driver-pro-card-login only. Drivers appeared because
the driver list was already merged across both projects; riders were not. That is the whole reason the Registered Passengers
table stayed empty while people were registering.
Admin now listens to riders in the registration project as well and merges three sources: registered riders,
the records the Rider app saves, and riders rebuilt from the trips they took. The note under the table counts each source.
The registration record also carries safe card metadata, which is now shown: cardBrand,
cardLast4, paymentMethodType, paymentMethodStatus, paymentMethodId and
paymentMethodAddedAt. The registration page deliberately does not store the card number or the security code, and
Admin displays only the brand and the last four digits.
One thing to fix in the registration page: it also writes the rider's and driver's
password into the database in plain text. Admin never displays it and warns when it finds one, but it should not
be there at all: the Firebase Authentication account is what signs people in, so that field is redundant.
Why the rider panel looked empty
The panel was correct, the data was not there. Two causes, and the old code could not tell them apart:
- The rider record was written with a transaction. A Firebase transaction has to READ the node before it
writes it. If the rules let the Rider app write
riders/{riderId} but not read it, the write failed and no
rider ever appeared. The save now falls back to a plain update, which needs no read, and the Rider app says on the page
when even that is refused.
- Admin had no error handler on the riders listener. A read refused by the rules left the list empty and
printed "No riders registered yet", which reads exactly like "nobody has signed up". The listener now reports the real
Firebase error under the table.
On top of that, the passenger list no longer depends on the riders node existing at all. Every trip carries
riderId and riderName, so Admin rebuilds the list from ride history and merges the saved profiles on
top when they are readable. A rider who has taken a trip is always listed, with their trips, fares and payments, and the note
under the table says which source the rows came from.
Rider information and payments in Admin
What the rider record already holds. One writer, one path: the Rider app writes
riders/{riderId} in the canonical project with riderId, fullName,
email, phone, createdAt, lastSeen and source. The rider id
is generated on the device and kept, so it survives page loads. Admin was already listening to that exact path, so there was
no database mismatch to fix: the data was simply thin, and the dashboard only showed four columns of it.
What the payment system holds. There is no payment provider connected, so there is no card brand field,
no last4 field, no payment method id and no processor transaction id. What exists per trip is paymentMethod
(the label the rider chose), paymentStatus, paymentReference (derived from the tripId),
paymentUpdatedAt, estimatedFare, finalFare, tipAmount and, after a
failure, paymentAttempts. Reporting that is the honest answer: no card storage was invented to fill the gap.
Added to Admin, reading those same records:
- The riders table gained Rider ID, Trips and total spent, the payment state of the last ride, and Account Status,
alongside the name, email, phone and joined date that were already there.
- A Rider Information view: profile, registration date, last seen, account status, a payment summary
(trips, completed, cancelled, total paid, tips, last payment), and a table of that rider's trips with trip id, date,
pickup, destination, driver, status, estimated fare, final fare including tip, payment method, payment status and the
payment reference with its timestamp.
- Trips are matched by an exact
riderId comparison and nothing else, so one rider's payment information can
never appear under another rider. A trip with no rider id belongs to nobody.
- Cards are shown as brand and last four only, read out of the stored label for display. Full numbers and security codes
are never stored and cannot be rendered: the masking runs before anything reaches the screen.
- The open rider view follows the existing realtime listeners, so a payment moving to PAID updates it with no refresh,
using the existing status values.
The Rider app also gained a small My Details box (name, email, phone) that writes into the same
riders/{riderId} record, because nothing in the apps ever collected an email or a phone number. It merges, so a
blank field never wipes something already stored, and no second rider record is created.
The missing driver profile
A Firebase Authentication account and a database record are two different things. Creating the user, in the Firebase
console or anywhere else, does not create drivers/{uid}. Nothing in these three apps ever created it: every
drivers/{uid} write in the whole project is an update() for approval, presence or GPS that assumes
the record already exists. There has never been a registration flow in the Rider, Driver or Admin file, only error messages
that mention one. That is why a driver can sign in successfully and still be told there is no profile.
Added, without deleting or resetting anything:
- When a sign in succeeds and no profile exists, the Driver app now shows a Complete your driver profile
panel on the login screen. It writes exactly one record,
drivers/{auth.currentUser.uid}, in the project the
driver signed in to, with name, email from the account, phone, vehicle and plate, marked
approved: false and Pending Approval. The administrator approves it as usual.
- The write is a transaction that MERGES: if anything already exists under that key, including an approval decision or
presence data, it is kept and only the empty fields are filled.
- If a profile already exists somewhere else for the same email, under a push key for example, it is copied
onto the Firebase UID with its approval intact, and the original record is left in place and marked
supersededBy, so the Admin list shows the driver once rather than twice. Nothing is deleted.
- The panel reports exactly what was checked in both projects, so "the record is not there" is never confused with
"the database rules would not let me read it".
- Sensitive fields stay where they are. Only name, email, phone, vehicle, plate, approval and presence are mirrored into
the canonical project for the Admin dashboard.
Run driverProfileDiagnostics() in the browser console on the Driver app, after signing in, for a straight
answer about the live database: the account, the UID, which project holds the session, whether the profile exists, every path
that was checked with found, absent or denied, and any other record carrying the same email.
Driver sign in
Same shape as the admin problem. The Driver app signs in against rider-s-drivers-info and nowhere else, so a
driver whose Firebase account was created in driver-pro-card-login could never get past the login screen, whatever
they typed. What changed:
- Sign in is tried in both projects, the driver login project first, then the canonical project. The account only has to
exist in one of them, and the login card says which one let the driver in.
- The driver profile is looked for in both projects too. When both hold a record, the real registration record wins over
the stub that an approve or suspend click leaves behind, so names, vehicles and documents are never lost.
- Approval is read from both records: suspended anywhere means suspended, approved anywhere means approved.
- Everything the driver does afterwards, going online, GPS, accepting, chat, status changes, now accepts a session from
either project. The original checks are still there and still run.
- The login card now says exactly what went wrong: which projects were tried and the Firebase error code, or that the
credentials were fine but no
drivers/{uid} profile exists, naming the exact record to create, or that the
account is waiting for approval, or suspended.
- Logging out clears both projects.
Run driverSyncStatus() in the browser console on the Driver app to see the signed in account, which project
holds the session, where the profile was found, the approval state, the current trip and the last GPS fix.
Admin sign in
The Admin dashboard used to sign in against one project only: driver-pro-card-login. If the admin user was
created in rider-s-drivers-info instead, the login could never succeed, and the only feedback was a bare
"Login failed" box. Two changes:
- The login is now tried in BOTH projects, canonical first, then the driver login project. The admin account only has to
exist in one of them. The dashboard still reads and writes ride data in the canonical project, and it says on screen
which project signed you in.
- Failures now say what actually happened: which projects were tried and the Firebase error code, including the common
one where email and password sign in is switched off in that project.
The authorized address is still driverandridersapp@gmail.com. Typing anything else says so instead of
failing silently. If a second admin address is needed, it goes in the ADMIN_EMAILS list in the Admin app.
When a driver has to call off a ride
A driver who has accepted a ride now has a red Cancel Trip button with the other trip buttons. It opens a
panel of eleven reasons, one tap each: rider didn't show up, rider requested cancellation, safety concerns, no car seat, too
many riders or luggage, inconvenient trip or personal reason, vehicle problem, wrong pickup location, rider not responding,
emergency, and other. Only "Other" asks for a sentence, and Confirm Cancellation stays shut until one is written.
- The trip is recorded as
DRIVER_CANCELLED, which is its own status rather than the ordinary
CANCELLED, so the office can always tell a driver cancellation from a rider one.
- Saved with it: the driver id, the driver name, the reason in words, the reason code, the explanation if there is one,
and the time it happened.
- No fee, no penalty, no rating and no cancellation limit is applied. Those were left for a later version, as asked.
- The rider is told once, by name, with the reason, and that nothing was charged. Admin shows the same reason in the trip
history and under the rider's own trips.
- A cancelled ride is final: no later write can move it back into a running state, and only the driver who owns the ride
can cancel it.
Driver registration, approval and access control
Registering as a driver no longer opens the Driver App. The application is saved to the Realtime Database with the status
PENDING_APPROVAL, and the driver is told it is waiting for Rider’s Administration.
- A pending or rejected driver cannot open the Driver Portal, go online, accept a ride or appear on the live map. The
check is on the status held in Firebase, not on a hidden button: forcing the portal open in a browser puts the approval
screen straight back.
- The Corporate Command Operations Panel has a Pending Driver Registrations table, oldest application
first, with Review Application, Approve and Reject. Every driver row also has Approve, Reject, Suspend, Delete and a
Change status control.
- Approving writes
APPROVED; rejecting writes REJECTED with an optional reason kept for the
office. Decisions are written to both Firebase projects and reach the Driver App live, with no reload.
- One status is the source of truth,
approvalStatus on drivers/{uid}. The older
approved and accountStatus fields are still written and still read, so accounts created
before this change carry on working, and an out of date approved: true can never beat a decision.
Country of Application, and telling a driver what was decided
Driver and passenger registration now both ask, and require, a Country of Application. The country and its
two letter code are saved with the application, along with what that country was set to at the moment they applied.
- Which countries Rider’s accepts applications from is set in Admin, under Countries & Applications,
and kept in Firebase. United States, Brazil and Mexico start as Accepting Applications and everywhere else as Coming
Soon. Opening a new country is a decision made in the panel, not a change to the code.
- A country never approves and never rejects anybody. An application from a country that is not open yet is still
accepted, still saved and still waits for a human decision, with the country’s status shown beside it.
- Admin shows the country in the Pending Driver Registrations table, in the driver table, and in Review Application.
- Reviewing an application offers Approve, Reject, a reason and Send Email. Rejecting opens a panel with ten standard
reasons plus your own words, shows the letter before anything is sent, and keeps the reason on the record.
- Two letters, in the owner’s words: the application was approved, or it was not approved with the reason. What was
sent, to whom and when is written on the driver record.
- Email is optional. A mailbox can be installed from Admin and is held on the server only, never in a page and never in
Firebase. With no mailbox, Send Email opens your own mail program with the letter already written.
The driver approval statuses are unchanged: PENDING_APPROVAL, APPROVED, REJECTED,
SUSPENDED. Passwords are no longer written to the Realtime Database at all: Firebase Authentication is the only
thing that checks one.
Deleting a passenger actually deletes them
Delete in Registered Passengers / Riders used to look as if it did nothing. A passenger can exist in five places, and it
only cleared two of them, both in the canonical project. Anyone who signed up on the registration page lives in the other
Firebase project, so their row came straight back on the next snapshot.
- Delete now clears the profile from both Firebase projects, waits for the writes, and says what was
removed and what the database refused.
- Every row has a tick box, with Select all, a live count of what is selected, and
Delete selected for removing several at once. The ticks survive the list redrawing.
- A passenger who has taken rides still appears, marked "from trip history only", and the message says why: their rides
are the record of work done and money taken, and are never deleted to tidy a list. Trip History has its own delete for
those.
- Deleting a driver had the same fault and now clears both projects, the users directory and the live GPS entry.
Removing a card is one wallet, and now says so
The card on a Rider’s account pays for rides and for package deliveries. Removing it from the delivery screen used to
be one tap on a button marked "Remove card", which could quietly take away the card a rider pays for rides with.
- The button now reads Remove from Account.
- Pressing it opens a warning that says the card pays for both rides and deliveries, and that removing it here removes it
from the whole account.
- The Remove button in that warning stays disabled until the rider ticks "I understand this removes the card from my
whole account, rides included". Nothing is written before that.
- If a ride or delivery is running, the warning says that job already carries the card and will still be charged.
There is still one Stripe wallet: no second customer and no second card store.
π Support, π¬ chat and π Call Rider
- Support is now in the menu of both apps, and it opens inside the app. Riders could only reach support
during a ride; now they can reach it any time, and drivers can reach the office the same way. It is the
same conversation the office already answers, not a new one.
- π¬ Chat between rider and driver is on every active ride, both ways, live, on the ride itself.
- π Call Rider sits on the ride request and on the trip in hand. One tap opens the phone's dialler with
the rider's number already in it, taken from their account. Nothing to type, and the number is never shown on the
screen. If there is no number on file it says Rider phone number is not available.
π± The Admin app, on your phone
A private Admin app for company use, for phones and tablets. It is the same office system, laid out for a screen you hold
in one hand. The desktop panel is untouched.
- Scan and go. There is a QR code in the Admin panel, under π± Admin App. Scan it with your phone or
tablet and the app opens and offers to add itself to your home screen, with its own icon.
- The same live system: the same Firebase data, the same Stripe data, the same riders, drivers, trips,
payments, security alerts and approvals. Nothing is copied and nothing is stored on the phone.
- Four screens: π Live (rides happening now, drivers online, today’s takings), π₯ People (drivers
and passengers waiting for approval, and a search), π³ Money (this month, unpaid rides, held payments), π‘οΈ Alerts
(anything that needs a person).
- Admin login required. Nothing at all is shown until you sign in with your usual Admin account, and an
account that is not an administrator is signed straight back out.
- The code is a doorway, never a key. It carries one web address and no login of any kind. Anything that
moves money still asks the server for your admin password, every time.
- It is not a public app. It is not on the app launcher, not on the downloads page, and search engines are
told to ignore it. No Stripe key or other secret is anywhere in it.
β
Approving a new passenger
π€ Rider Registration is now a waiting list: new passengers who have signed up and are waiting for you to say yes.
- Approve moves that same account out of Rider Registration and into π€ Registered Passengers / Riders.
The same Rider ID, the same sign in, the same rides, the same payments, the same Stripe customer and the whole history.
Nothing is created, copied, merged or deleted.
- You are told exactly what will happen before you confirm, and afterwards the approval is written on the
account with your name and the time, and kept in that rider’s history where you can read it back.
- An approved passenger never returns to the waiting list, even if the account is suspended or their
payments are stopped later. Those are account statuses, not approval.
- Everybody who was already riding is unaffected. An account from before this existed counts as approved,
so nobody who has been using Rider’s for months is suddenly put in a queue.
- The Registered Passengers list tells you plainly when somebody is waiting above.
π€ Rider Registration, in the office
A new section under Driver Approvals, showing everybody who has registered as a passenger. Nothing else on the page moved.
- Every registered rider, with first name, last name, full name, email, phone, Rider ID, the date and time
they registered, where they registered from, their state, town and country, their account status, whether their email is
verified, how many rides they have taken and when the last one was.
- Open one and you get their whole story: registration details, every ride, what was paid and what is
still owed, cancellations, payment holds, disputes, security events and every change the office has made to the
account.
- Search by name, email, phone or Rider ID, and filter by Active, Suspended or Blocked,
by state, and by when they registered.
- Suspend or reactivate an account. The reason and your name are written on the account and kept as a
trail you can read later.
- It fills itself in. When somebody registers they appear here, because it reads the same rider records
the apps already use. There is no second list and nobody is duplicated.
- No payment details, ever. No card number, no security code, no token, no key, no password. The one
payment thing it can show is the Stripe customer reference, and only after you have signed in for it.
- Anything a record does not hold reads Not recorded, and the security column reads Not loaded until you
ask for it, so a gap can never be mistaken for a clean bill of health.
π‘οΈ Payment security
Money is now decided in one place: the server, with Stripe. The apps ask; they never confirm. Nothing about how a ride looks
or works has changed.
- A ride only goes out after the card is really approved. The approval now comes back with a signed note
from Rider’s own server, and the ride carries it, so a ride nobody checked can be told apart from one that was.
Nothing in a phone can make one of those notes.
- Stripe Radar scores every payment, and Rider’s obeys it: the highest risk is refused outright, and
anything Radar wants a second look at is held.
- 3D Secure is asked for when something looks off, which puts the question to the cardholder’s own
bank and moves the liability for a stolen card away from Rider’s.
- A held payment is never quietly taken. The ride still happens, nobody is paid out on it, and it waits in
the new π‘οΈ Payment security panel for you to release it. Releasing charges it the ordinary way.
- Repeated refused cards stop an account trying more, which is how card testing is done. A card that works
clears the run, so a typo costs nobody anything.
- Fake accounts, referral abuse and excessive cancellations are all watched: a brand new account spending a
lot on day one, somebody inviting themselves or paying with the inviter’s card, and an account that cancels nearly
every ride.
- No money leaves for a driver until Stripe itself says the rider paid. The amount must match that ride’s
own split, the account must be the one the ride paid, a driver can never be paid for their own ride, and if Stripe cannot
be reached, nothing is sent at all.
- Disputes, fraud warnings and Stripe reviews come back through the webhook and hold the ride at once.
- The passenger is never accused. A held payment says it is being checked; a refused one asks for a
different card. Nothing more.
πΊοΈ Every rider and driver, and where they are
One list in the office panel that answers the question "where is the business actually happening", so growth can be decided
on figures rather than a feeling.
- Everybody in one table: every rider and every driver, with their name, contact details, the state they
registered from, their town, the date they registered and whether they are still active.
- Totals by state, biggest first: how many riders, how many drivers, how many of them are active and how
many rides have come out of that state.
- Search and filters: type a name, an email or a town, or narrow the list to one state, to riders only, to
drivers only, or to the people who have gone quiet.
- Download the list as a spreadsheet, exactly as filtered. It carries no card, no payment id and no social
security number.
- Active means used Rider’s in the last 90 days, or only just joined. A suspended driver is never
counted as active.
- Nothing is guessed. Where a state was never recorded the row says Not recorded and is counted
that way, and every state says where it came from: the registration form, an address on file, or the state that
person’s rides actually start in.
- Riders were never asked for a state before, so the registration page and the Rider app now have an optional Town
and State. The picture fills itself in from here on.
π± Canada, in Canadian dollars
Rider's prices a ride once. A passenger in the United States is quoted and charged in US dollars; a passenger in Canada is
quoted and charged the same ride in Canadian dollars, worked out from that one US fare.
- US$20.00 at 1.39 is C$27.80. The same ride, the same value, in the money the passenger actually
spends.
- A Canadian rider sees and pays Canadian dollars, everywhere: the quote, the breakdown, the tip buttons
and the receipt. Their fare is never turned back into US dollars on their screen.
- The rate is saved on to the ride the moment it is requested. If the exchange rate moves while somebody
is in the car, or a week later, that ride does not move with it.
- Every record says which money it is: the ride, the payment in Stripe, the driver's payout, the receipt,
the trip history and the office's own tables. Nothing has to be guessed from a dollar sign.
- The driver is paid in the same money the rider was charged in, with the same 40% share and the whole
tip, exactly as before.
- The office sets the rate in π± Exchange rate and can see what has been taken in each currency, kept
apart rather than added together.
- The United States is unchanged. A US ride is US dollars at a rate of 1, which is what every ride has
always been.
π³ The ride pays for itself
A finished ride used to sit there waiting for the passenger to notice a Pay button. Now the card on the account is charged
the moment the driver ends the trip.
- The driver ends the trip and the card is charged. The passenger presses nothing. The amount is the fare
on the ride record, and the driver's 40% plus their whole tip still travels inside the same payment exactly as before.
- Stripe confirming it is what marks the ride paid, as it always was. Nothing is paid because a screen
said so.
- If the card is refused the ride is marked Payment failed, unpaid with the bank's own words, and
the passenger is told straight away with a Pay now button in the message. Nobody is credited for a ride that was not paid
for.
- The account stays active. Nobody is suspended, closed or locked out for owing money.
- What is owed is collected on the next booking, ride by ride. $25 owed and a $30 ride to come are a $25
payment and a $30 payment, and each ride keeps its own status: never one lump nobody can explain.
- It cannot pile up for ever. Past 2 unpaid rides, or $75, the next ride waits until the balance clears,
and the app says exactly what is owed. The office can change both numbers without touching any code.
- The office has a new π³ Unpaid rides panel, and a failed payment no longer looks the same as one that is
simply waiting.
π³ Saved cards work now
A card saved on an account could not always be charged, and this is why: a saved card belongs to a Stripe customer, which is
remembered against the id the sign in gives, while a ride carries the id the app made on the phone. Two different names for the
same person.
- Nothing was renamed and nothing was lost. The account id is now written down beside the phone's own id,
on the passenger's record and on every new ride, and the payment looks for the card under both.
- A card typed in fresh and a card already saved both work, on the automatic charge and on the Pay
button.
π€ Talking to each other, without swapping numbers
A driver and a passenger who have never met have to find each other at a kerb. That is the one moment they need to talk, so
that is the moment this opens, and it closes again by itself.
- The rider sees the driver, always: photo, name, car model, car colour and licence plate, from the second
the ride is accepted. All five are written on to the ride itself, so the history still says which car it was. Anything
still blank is named to the driver on their own screen, so they can fill it in.
- The driver sees the rider: name, pickup, drop-off, how long the ride is and how far. That is the whole
list. No phone number, no email address. The rider's number has also been taken off the Lost & Found screen.
- Call and chat, both ways, from accepting until starting. The rider gets a Call driver button and a
Message driver button beside the car details; the driver keeps theirs on the trip in hand.
- Neither of you sees the other's number. You both dial one number: Rider's own. Rider's connects the two
ends, so nothing personal reaches either phone, and nothing personal is left in a call log afterwards.
- When the trip starts, the pre-pickup line closes on both sides: the two are in the same car. What was
already said stays on the screen to read. Nothing is deleted, and 911 is on every screen at every moment as before.
- Until the office sets the company number up, calling is not offered and the apps say so plainly and point at the
messenger. It never gives somebody's own number away instead. The office turns it on in Admin under
π Private calling.
π€ No name, no ride
A driver pulling up needs to know who they are collecting, and the office needs to know whose ride it was. So a ride now
carries a real name, or it is not requested at all.
- A first name AND a last name are required before a trip request is written. One word is not enough,
and the app's old placeholder does not count. The rider is told which part is missing and the box to type it in opens
for them.
- The name is taken from their registration or profile when it is there, so most people are never asked:
it fills itself in and the ride goes through.
- The trip record carries the full name and the first and last name separately, so the Driver app and the
office can show either without splitting a string.
- The Driver sees the passenger's name on the request, on the trip, on the completion summary and on a
tip, and the office reads the same name in the live trips table and the trip history: the same way it
reads a registered passenger's name.
- A ride recorded under the old placeholder says Your passenger or Name not given rather than calling
everybody by one name. Nothing in the history is rewritten.
π€ Two passenger lists, told apart
The office had one table, and it mixed two different things: people who registered, and people who only exist in the rides
they took. Four rides looked like four registered customers.
- π€ Registered Passengers / Riders now shows only passengers with a real registration record: someone
who saved their details in the Rider app or registered an account.
- π Riders Found From Trip History, in its own section below, shows the ones with rides and no
registration record, with a count of them and of the rides between them.
- Every row is exactly as it was: the same columns, the same trip and fare totals, the same payment
state, View Details, Delete and the tick box.
- Each list has its own Select all, so ticking one never quietly ticks the other.
- The second section says what deleting can actually do there: a passenger with no profile has nothing to delete, so
they are cleared with π§Ή Clear Test Data / Prepare for LIVE, which takes their rides with them.
The π Language section is untouched.
πΊπΈ π¨π¦ United States first, Canada second
- The country list on the Rider and Driver sign up now starts with πΊπΈ United States and
π¨π¦ Canada, with their flags, and every other country follows in exactly the order it was in. Nothing
was removed or renamed.
- Canada is open for applications, beside the United States, Brazil and Mexico. A Canadian applying is
told Accepting Applications rather than "coming soon", and you can still open or close any country from the
Admin panel without a code change.
- The office's own country picker leads with the same two.
- A Canadian driver gets a Canadian Stripe account, taken from the country on their own record, so they
can be paid into a Canadian bank. A driver with nothing on their record is American, exactly as every driver was
before today.
- The language button is unchanged and already carries both: English first, and French, which Canada also needs, in the
short list at the top.
π₯ Clearing out your own test passengers
The passengers left over from your own testing look like real customers, so the π§Ή Prepare for LIVE scan
will never propose them: that caution is deliberate, because it must never delete a genuine passenger. So the panel now lets
you tick them by hand.
- Under π₯ Also clear out these people the panel lists every passenger and driver on file, with their
email, their id and how many rides would go with them. Nothing is ticked for you.
- Tick somebody and they join the plan: their profile, their rides, their store orders, support
conversations, notifications and GPS records. Untick them and they come straight back out.
- Every candidate still says why it is there, and it is still all behind the same guard: a full backup first,
the warning, and the words PREPARE FOR LIVE typed in.
- A passenger who exists only in their rides (no profile at all) can be cleared this way too: the rides are the only
record of them, so removing the rides removes the row.
One thing to expect: removing rides removes their money from the earnings totals. That is the point of clearing test data
before launch, and the backup taken a moment earlier holds all of it.
π€ The passenger list in the office
The Registered Passengers / Riders table showed Beatris in the Rider Name column for everybody,
because that is the name the old rides were recorded under.
- The office no longer prints the placeholder as a name. A passenger whose records genuinely hold no
name reads No name on file, with their email and rider id beside it, instead of borrowing somebody
else's.
- You can type the name in. Open View Details on any passenger and there is a name box
with a Save name button. It writes the same field the Rider app writes, so from then on the name is on
their account, in this table, and on every new ride, message and receipt.
- Passengers using the app now fill this in themselves: the Rider app asks at the top of the screen and saves it to
their account.
- Rides already taken are not rewritten. The name on an old ride stays exactly as it was recorded.
π€ Passengers are called by their own name
Every ride in the office said Beatris, whoever took it. That name was written into the Rider app as a
placeholder in the original build and used whenever a device had nothing else stored, so four different people with four
different email addresses all arrived under one name.
- The name now comes from the passenger's own records, in this order: what they typed into Your details,
then their account in the office database, then the account they registered with,
then the name on their sign in.
- Nothing is invented. An email address is not a name. If none of those knows, the app says
Rider and asks them to add their first and last name, rather than putting somebody else's name on their
ride.
- Their name is at the top of the app, in the place that used to say "Beatris" for everybody. When the
app does not know it yet, that same spot says π€ Add your name and tapping it opens the box that takes it. It
never says "Rider" at somebody twice over.
- It asks at the front. While the app does not know who somebody is, the very first thing on the
booking screen is π€ What is your name? with a First name and a Last name box and one button, and it says why
it is asking: the driver sees it when they come to collect you, and it goes on your receipts. It disappears the moment
it is answered, and it never blocks anything: a ride can still be booked with it on screen.
- Once it is known it goes on their account, so it is on every future ride, message, receipt and support conversation,
and the office sees it in the live trips table and the passenger list.
- Rides already taken keep the name they were recorded under: history is history, and it is not rewritten.
ποΈ Deleting a support conversation
Every support conversation in the office now has a Delete button: on its card in
π§ Rider Support Chats and in π¦ Package Delivery Support, and inside the conversation
when it is open.
- It asks first, naming who the conversation is with and how many messages are about to go, and says
plainly that it cannot be undone.
- It removes that one conversation and nothing else. Every other conversation is untouched.
- The person can write to support again straight afterwards: they get a fresh, empty conversation, and the assistant and
the office answer it exactly as before.
- Pressing Delete on a card does not open the card, and if the conversation being deleted is the one on screen, the
panel closes itself rather than sitting on something that is no longer there.
βͺ Notification text in white, at 16px
Notification and alert text is now white and 16px, on the same black background it was
always on. (It was red, then briefly the company orange, and is now white.) 16px is the size a phone reads without
squinting, and the size that stops iPhones zooming the page.
- The messages on the card box, the sign in screens, the payment note, the GPS line, the store messages in both apps and
the registration status lines.
- Nothing else changed: the same place, the same box, the same timing, the same wording, the same
animation and the same behaviour. Only the colour and the size of the words.
- Other reds are untouched, as asked: the 911 buttons, Decline and End Trip, the office's delete
buttons, the red car on the live driver map and the payment badges are all exactly as they were.
ποΈ Rider's Store, in the Rider app
The store is now in the Rider app as well, on a ποΈ Rider's Store row in the menu that opens it inside
the app.
- It is the same store, not a copy: the same products you write in the Admin panel, the same prices and
pictures, the same stock, and the same order book you already read. Add a product once and both apps have it.
- A rider browses, adds to a cart, checks out and sees their own orders and their status, exactly as a driver does.
- Payment goes through the same Stripe path as everything else, charged on the server, with the amount read from the
order.
- The order says who it is from: your orders list marks each one π€ Rider or π Driver, and the shipping
section still comes from that person's own account.
- The Driver store and the Admin store are untouched. A driver's order is still written exactly as it
was, so nothing that reads them changes meaning.
π§ Support, on every page
The Rider app and the Driver app each have a support screen already. Every other page had nothing: someone reading the
terms, on the install page, or halfway through registering had to find their way back into an app before they could ask
anything. There is now a floating π§ Support button on all of them.
- It is the same support system, not a second one. A message sent from any page goes into the same
conversation the Rider app shows, the office answers it in the same panel, and the same assistant answers it
straight away.
- The popup opens over the page: nobody has to leave what they were reading.
- The button floats clear of everything else, is never in the flow of the page, and is hidden when a page is
printed.
- The Rider and Driver apps are untouched: they already have Support in their menus, so they are not
given a second button on top of it. The Admin pages have none, because the office answers support rather than asking
it.
π§Ύ Balance due: order now, pay as you drive
A driver who cannot pay today still needs the sticker, and still needs to work. So an order can be placed with no money at
all.
- Whatever the driver has earned is taken now; anything short becomes a Balance Due and the order goes
to the office as normal.
- It pays itself off as they drive. Every time a trip is paid for, what they have earned and not been
paid out goes against what they owe, oldest order first, a part at a time if that is all there is.
- Worked example, exactly as the owner put it: a $35 order with $0 earned is allowed
and shows $35 balance due. Earn $50 and $35 comes off, the driver keeps
$15, and the order is Paid. Earn only $20 and $20 comes off, leaving
$15 still due.
- Paying by card is unchanged, and a driver can still do that at any time from My orders. An order they meant to pay by
card is never quietly taken out of their earnings while they are still trying: there is a
Take it from my earnings button beside it, and it is their tap.
- A cancelled order stops owing anything, and a deduction can never be taken twice or take more than is owed.
Most important: owing the store money never stops, suspends or restricts a driver. Nothing about the
store is consulted by the rules that decide who may work. A driver with a balance goes online, is offered rides, accepts,
drives and is paid exactly as before, and the app says so in as many words: "It comes off your earnings as you drive.
You can keep working as normal." There is a test that drives a whole ride with a balance outstanding to prove it.
The office sees the balance on each order and the total owed at the top of the driver orders list.
π¦ Shipping & Driver Information on every store order
Every order in the Admin store now carries a π¦ Shipping & Driver Information section: the driver's
full name, email, phone number, street address, city, state and ZIP code.
- It is taken from the driver's own authenticated account, the one they registered and you approved,
not from anything typed at checkout. Nobody can redirect a parcel by retyping an address at the
till.
- It is copied on to the order when the order is placed, so you are always looking at where that order
was going, even if the driver moves house afterwards.
- Anything the account does not hold is shown as not on file in amber, with a line naming exactly what is
missing, so you can ask the driver to fill it in instead of posting a parcel into the dark.
- The driver registration form now also has City, State and ZIP boxes. Filled in, they are used exactly
as typed; left empty, the single address line is read instead, so nothing that already works stops working.
π³ Paying for a store order: card, or your earnings
At checkout in the Driver app there are now two ways to pay, and every order records which was used.
π³ Pay with card
- Charged through the same Stripe integration the rides use: the same customer, the same saved cards,
the same keys on the server and nowhere else. No key of any kind is in the Driver app, and the card
itself is typed into Stripe's own box, never into our page.
- The amount is never sent by the app: the server reads it from the order record.
- Paid β the order becomes Paid. Refused β it is not marked paid, the driver is told
what the bank said, and there is a Pay by card button on the order to try again.
- An order can never be charged twice.
π° Pay from Rider's earnings
- What can be spent is what the driver has earned and not yet been paid out, less anything already
spent in the store. Money already sent to their bank is not offered twice.
- Enough β the total comes off and the order is marked Paid, recorded as
DRIVER_EARNINGS, with what was taken and what is left written on the order.
- Not enough β nothing is taken and no order is placed. The driver is told how short they are and to
use a card instead.
- The balance can never go negative: it is checked before the order and again at the moment of
payment, and the payment refuses itself if the money is not there.
The office sees which way each order was paid on its card in the Admin store. The price on an order is still the price it
was placed at: a later price change never alters an order that has already been made.
πΌοΈ The product picture upload, fixed
Uploading a product picture in the Admin store failed with
Cannot read properties of undefined (reading 'getProvider'). The cause was in our own code, not in Firebase:
the upload passed driverAccountsApp, the function that returns the app, where Firebase Storage
expected the app itself. It looked for something on a function, found nothing, and threw. It is called properly now, and
the app the admin is signed in to is tried first, so the upload carries a token the storage rules
recognise.
- Pick a picture from the computer and it uploads, then the product saves in the same press of the
button, with the picture on the record.
- A phone photograph is scaled down and saved as a JPEG in the browser first, so uploads are quick and
the store stays light.
- If Firebase Storage refuses, the second project is tried, and if that refuses too the scaled picture is kept on the
product record itself, so the picture is never simply lost. Either way the office is told exactly what
happened, in Firebase's own words.
- Pasting a link still works, unchanged.
- The store holds as many products as you add: save one, the form clears, and the next goes in the same way.
πͺ The windshield sticker rule, in the Driver app
The company's own words about the sticker are now at the top of the store in the Driver app, where a driver cannot miss
them:
Rider's Transporthers Front Windshield Sticker
MANDATORY DRIVER EQUIPMENT
Every active Rider's Transporthers Driver is required to display an official Rider's Transporthers sticker on the front
windshield of the vehicle while providing transportation services.
The sticker helps riders easily identify an authorized Rider's Transporthers vehicle and provides a professional,
consistent company appearance.
Important: This sticker must be clearly visible from outside the vehicle and properly placed on the front
windshield.
One sticker is required for each active Driver vehicle.
- Under it, one tap: Order my windshield sticker, which puts one in the order. Once it is ordered the
same button shows that driver's own order status instead.
- The product in the shop carries the same words, so the rule and the thing being sold never disagree.
- If the office edits the description itself, that edit is kept: the wording is only brought up to date where it is still
the old default.
ποΈ Rider's Store
A shop the office runs itself, for its own drivers. Everything in it lives in the database, so new
products, prices and pictures are added from the Admin panel without anybody touching the site.
In the Admin panel
- ποΈ Rider's Store: add a product, upload a picture (or paste a link), set the price, set the stock
(leave it empty for unlimited), write the description, and mark it Required Driver Equipment if every
driver must have it.
- Edit anything later, take a product off sale and put it back, or delete it. An order that already has
it keeps its own copy of the name and price, so the order history never changes under you.
- π§Ύ Driver orders: every order, who placed it, what is in it, what it comes to, and a status to move it
along: placed, paid, packed, on its way, delivered, cancelled. The driver sees each change straight away.
In the Driver app
- ποΈ Rider's Store in the menu: the products with their pictures, descriptions and prices, a cart, and
a checkout that places the order.
- My orders shows each order, what it cost and how far along it is.
- Required equipment is called out at the top: not ordered yet, ordered, or you have your required
equipment, with the order's own status.
The first product is the Rider's Transporthers Front Windshield Sticker, created for you and marked as
required. It is an ordinary product record: change its price, picture or wording whenever you like.
π³ The card is authorised before the ride is created
A ride used to be created, sent to a driver and sometimes driven before anybody discovered the card would not pay for it.
The bank is asked first now:
- Request Ride β the card is authorised β the ride is created. An authorisation for the quoted fare is
put to the card issuer, exactly as a payment would be, and released again at once. Nothing is
captured, so nobody is out of pocket for a ride that has not happened, and the ride is still paid for at the end by
the payment flow that was already there.
- π’ Approved: the trip is created and sent to drivers as normal, and it carries the proof that the card
was authorised first.
- π΄ Refused: no trip is created and no driver is sent anything. The rider is told
"Your card could not be authorized. Please enter a different credit or debit card.", the card box opens, and
they can enter another card and press Request Ride again.
- What the bank actually said is kept for the office, not put in front of the passenger.
- The amount is worked out from the fare on screen and is clamped on the server ($1 to $500), so no page
can ask for a hold of any size.
- It uses the Stripe setup that is already there: the same customer, the same saved cards, no second
webhook and no new keys.
- If the payment server is unreachable, or a site has no Stripe configured, rides are booked exactly as they were before.
A network problem never refuses a passenger.
π³ The Stripe key in the browser now comes from the server
Stripe reported: "The publishable key in the browser belongs to a DIFFERENT Stripe account from this secret key."
The publishable key was written into the app's code, so it could not follow the LIVE secret key installed on the server.
- The publishable key is now installed in the Admin panel, beside the secret key, and the server hands it
to the Rider and Driver apps. The browser uses that one, so it can never drift out of step with the secret key
again.
- The server refuses a publishable key from another account, or a test key while the secret is live, and
says which. It proves the pair with Stripe itself: it creates a setup intent with the secret key and reads it back with
the publishable key, which is exactly what a rider's card box does.
- A new button, Check both keys are the same account, reports the account, the mode, whether they match,
and whether card payments initialise in the browser.
- The Stripe heading now says browser key does not match as soon as it does, instead of leaving it to be found by
a rider whose card will not save.
- Nothing else changed: the secret key, the webhook secret, Firebase, authentication, trips, ride requests, fares and the
payment logic are all exactly as they were. The key in the app's code is still there and is used only when the server
has none installed.
The secret key is never sent to the browser, never stored in Firebase and never written into any page or
log. Only the publishable key, which is public by design, is.
π§Ή Clear Test Data / Prepare for LIVE
An admin-only panel that removes the accounts, rides, deliveries, conversations and GPS trails made while testing, and
nothing else. It never runs by itself: there is no timer and no schedule, only four deliberate steps.
- 1. Scan. Every candidate is listed with the reason it is believed to be test data (a test email
address, a name like Test Rider, a phone number in the reserved 555-01xx range, a made up social security
number, an id that says test, or a ride belonging to one of those accounts). Each has a tick box.
What you untick is not touched.
- 2. Download full backup. The whole database, both Firebase projects, saved as a JSON file with a
fingerprint. A receipt is written into the database and read back again, so the backup is proved and not just
promised. Nothing can be removed until that backup exists.
- 3. Remove. Requires the warning to be accepted and the words PREPARE FOR LIVE typed
in. Then it removes one exact record at a time, never a whole Firebase node.
- 4. Check the system. Admin sign in, a real write to the database and back, app configuration,
pricing for rides and deliveries, Stripe, the Support AI and both support desks, and both Firebase projects.
Never touched, whatever is ticked: the app itself, prices, app configuration, Stripe keys, Firebase and
its authentication, the security rules, the database structure, the support system and its quick answers, and the admin
account. Those names are on a protected list, and a second check refuses them again at the moment of deletion.
Afterwards the office is shown exactly what went: test riders, drivers, trips, deliveries, support conversations,
notifications, chat records, GPS records and payment records, along with the backup it can be restored from. Anything the
database refuses is reported, never swallowed.
π§ Two support desks: rides and package deliveries
Support in the Admin panel is now read at two desks instead of one long list:
- π§ Rider Support Chats: rides. Trip questions, driver issues, fares and payments, lost items,
cancellations.
- π¦ Package Delivery Support: deliveries only. Pickup and delivery problems, a recipient who is not
there, a package missing or damaged, delivery payments, delivery drivers.
- Nobody has to choose a category. Each conversation is sorted automatically: from what the app recorded
when the message was sent, and failing that from the job it belongs to (
trips/{id}.serviceType). Anything
that cannot be told apart stays on the ride desk, where every conversation went before, so nothing can fall out of both
lists.
- Each card carries the person's name, whether they are a rider or a driver, the case number, whether it still
needs a person, and which service it is.
- Both desks have their own conversation panel, so reading a delivery does not close the ride somebody was answering.
- It is one support system underneath, not two: the same
support_chats, the same Support AI,
the same case numbers, the same live replies in the Rider and Driver apps. Only the view is split.
- The Support AI now answers delivery questions as deliveries: where the package is and what stage it is at, what to do
when the recipient is not there (the driver never leaves a package unattended), and a package missing or damaged always
goes to a person with a case number.
π Who is picking me up
The Rider app now shows, from the moment a driver accepts until the ride ends:
- the driver's profile picture, the one from their application (their initials in a circle if there is
no photograph on file);
- the driver's name;
- the car: colour, year and model, with a small dot of the colour beside it;
- the license plate, on a plate-shaped badge, so it can be checked from the kerb.
It is also on the popup that appears the moment the ride is accepted, which now names the car and the plate as well as the
driver.
- All of it is the information the driver already gave on their application. Nothing new is asked of anybody.
- The car is written on to the ride when it is accepted, so the ride's own record says which car took it, even if that
driver changes vehicle later.
- For a ride accepted before this existed, the card reads the driver's record instead, so it is never blank for no
reason.
- Nothing is invented. A detail that is not on file says Car details on the way or
Plate on the way, and the card asks the passenger to check the plate before getting in.
π Support answers you now
Support was reaching the office, and the office could answer, but nothing came back until somebody was at a desk. A message
sent at two in the morning sat in silence. It is answered straight away now, in the same conversation.
- Riders and drivers get an answer as soon as they write, and it is written from their own records: where
the driver is and how long, what the last ride cost and its card reference, the card on the account, a lost item, a
cancellation, and for drivers when Friday's payout lands, whether the account is approved, and why rides are not
arriving.
- Anything it cannot answer honestly, it hands to the office with a case number, says so plainly, and
never guesses. Those conversations are marked NEEDS A PERSON in the office panel.
- Anything that sounds like an emergency is answered with 911 first, before anything else, and still
goes to the office.
- It is the same support conversation as before: one place, which the office reads and answers in. The
office can always answer over the top, and its answer arrives live in the app, as it did before.
- The office panel now says who is writing (name and whether they are a rider or a driver) and the case
number, instead of only a key, and the assistant's replies are a different colour from the office's own.
The big roads are the company orange now
- The motorways on the driver's map used to be drawn pink. That colour comes from the map itself, not
from us. They are now #ff6600, the company orange.
- Only that colour changes. The water, the parks, the streets, the street names and the road shields are exactly as
they were.
- If anything gets in the way of the repaint, the map simply shows the plain tile. It is never left blank to make a
road orange.
π
Company earnings, by the month
- The Company Earnings (60%) card now counts this month, and says which month it is
counting.
- A π
Monthly Earnings button under it opens a page with every month that has closed:
what the company earned, what went to drivers, the tips, the gross and how many trips.
- On the first of each month the month that just ended is saved to that page by itself, and the card
starts again from zero.
- Nothing is deleted to do it. Every trip stays exactly where it is: the saved months are a reading of
them, so the figures can always be checked.
- The page also shows the month currently running at the top, and adds up all the saved months at the bottom.
The copyright line, on every page
© 2026 RIDER’S LLC. All Rights Reserved. now sits at the foot of every page,
in the same words everywhere.
- It is a quiet line at the very bottom, in the normal flow of the page. It is not pinned over the screen, it takes no
usable space away, and it cannot take a tap from a button.
- Two pages had grown their own wording over time, one of them naming a different company. Both say the agreed line
now.
- The year moves on by itself, so nobody has to edit twenty pages next January.
π€ Share this app
- A Share this app row in the menu of both apps, under Terms & Conditions, built like the rows
beside it.
- It opens the phone's own share sheet, so the link goes wherever you already talk to people. On a computer it copies
the link instead, and if even that is blocked it shows the link to copy by hand.
- The Rider app shares the Rider app; the Driver app shares the driver application.
- This is the plain app link. The π₯ Refer friends screen is still the one that carries your invite
code and your reward.
The ride request arrives in the middle of the driver's screen
- A new request now appears as a centred card over everything else, with the screen dimmed behind it.
- It does not matter where the driver has scrolled to, or whether they are looking at the map: the request is in the
middle of the screen, every time.
- Same request, same information, same Accept and Decline. Only where it sits has changed.
- It fits narrow phones, stays centred when the phone is turned, and scrolls inside itself if the screen is very
short.
The rider's card stays on file
- The card is now remembered on the phone as well as on the account, so a server that will not hand the
account back does not make the card disappear. Only the brand, the last four digits and the Stripe reference: never
the number, never the security code.
- The card box now says where the card is: held by Stripe, saved on the account, or saved on this phone
because the server refused to read the account, with the reason.
- Rides still go out with that card on them.
- Removing a card removes it from both places.
πΊοΈ A live driver map for the office
A Live Driver Map button beside the live rides panel opens the whole fleet on one map, in its own
window.
- π΅ Blue car: online and available. π΄ Red car: on a job. βͺ Grey car:
offline, or the GPS has gone quiet.
- The colour changes by itself as the job moves, and goes back to blue the moment a ride is finished or
called off. No refreshing.
- Every car sits at the driver's own position and moves as they move.
- Tap a car for the driver's name, ID, vehicle and plate, and the job they are on: trip, rider, pickup,
destination and fare.
- A list beside the map shows everybody, busiest first, and Fit all frames the whole fleet.
- Admin only. The map is not even built until an admin account signs in, and the page only ever reads:
it cannot change a trip, a driver or anything else.
Every Stripe payment now names the ride it paid for
A payment of $27.97 in Stripe used to say only "trip REQ-...". Opening one now answers everything on its own.
- The description reads Ride Payment - $27.97 - Trip REQ-1787...
- The payment carries the trip, rider, rider name, driver, driver name, pickup, destination, fare, currency,
ride or delivery, and the ride's status, all read off the trip itself.
- The payment reference is written back on to the trip and shown to the office, to the rider on their
payment card, and to the driver on the finished job, so a payment and a ride can be matched from either end.
- Same payment system as before, same single charge per ride: nothing was rebuilt and nothing can be charged twice.
π£ The office can send a message to one rider or one driver
Admin has a Send a Notification panel: pick a passenger or a driver, pick the person, write the
message, send.
- It appears on their phone, in the same card the apps already use for everything else, as soon as
their app is open.
- The office sees what it sent and whether it arrived: Sent, waiting for their app becomes
Delivered, read 4 min ago.
- It goes to that one person and nobody else.
- The support chat, the trip chat with the driver, trips, GPS and sign in are all untouched.
The Driver Console header, rearranged
- The logo sits on its own at the top, centred, and is bigger than it was.
- 911, π Driver Console and π³ Pro Card are together on the row underneath.
- Nothing changed about what they do: 911 still dials 911, Pro Card still opens the Pro Card.
π¦ Signals, stop signs and live traffic on the map
- Street names and road numbers were already on the driver's map: they come with the map itself and
show from about the zoom navigation uses.
- Traffic signals and stop signs are now drawn on the driver's map around the car: a lit three lamp
dot for a signal, a red diamond for a stop sign. They come from OpenStreetMap, which has them as data but does not
draw them, and they cost nothing.
- A π¦ button on the navigation screen turns them off and on, and the app remembers.
- Live traffic on the rider's map, in Google's own colours: green moving, orange slow, red stopped,
with a button in the corner to turn it off.
- Live traffic on the driver's map needs a traffic service, because the free map does not have one.
The office adds the tile address and key in Admin under π¦ Map & Traffic and it appears on the driver's map. Until
then the driver is told plainly that it is not set up.
Address entry: three more ways the screen could change, all closed
The 16 pixel fields were right, but they were not the whole story. Measured on a phone-sized screen with the real map
loaded, the page itself never rescales. Three other things could still make it look as though it had:
- The browser inflating the text by itself. Mobile browsers resize text when a block changes width.
They are now told to leave our sizes alone. This is not the same as stopping you zooming, and your phone's own text
size setting is still in charge.
- The keyboard squashing the page. The browser is now told the keyboard should cover the app rather
than resize it, so nothing reflows while you type.
- The address suggestion list. It comes from Google with no width limit of its own; if it reached
past the edge of the screen the whole page would look shrunken. It is held inside the screen now.
Pinch to zoom still works everywhere, nothing forces a zoom level, and the map keeps every bit of its own behaviour.
Typing an address no longer zooms the screen
Tapping the pickup or destination box on an iPhone used to zoom the whole page in, and it did not zoom back out, so the
map and everything around it looked wrong afterwards.
- That zoom comes from the phone, not from the app: iPhone Safari zooms into any field whose text is smaller than 16
pixels. Every field in the Rider app, the Driver app and the driver application form is now 16 pixels on a touch
screen, so the phone has no reason to zoom at all.
- Nothing forces a zoom level, nothing scales the app, and the map keeps its own behaviour exactly as it was.
- Pinch to zoom still works, and your phone's own text size settings are still in charge.
π₯ Refer friends, in both apps
Riders and drivers now have an invite screen in their menu: Refer friends.
- Invite friends who are new to Rider’s, or who have not taken a trip with us yet.
- Earn up to $273 for every friend who completes 160 trips in 90 days. Your friend has 90 days to
complete their trips after they accept your invite in West Palm Beach, Florida. The reward may differ if they are
based in a different city.
- Everybody gets their own invite code and link. Share it with the phone’s own share button, or copy it.
- A rider’s link opens the Rider app for their friend; a driver’s link opens the driver application.
- The screen lists every friend and how far along they are: 12 of 160 trips, 80 days left, what has been
earned and what is still working.
- The terms are set in the office, so a different city can pay a different reward. Changing them never changes an
invite somebody has already accepted.
π Drivers can navigate to a destination of their own
A round 🔍 button now sits in the top right corner of the driver's map, and of the navigation
screen. It is 52 pixels across, dark with a white ring, so it can be found and hit at a glance.
- Tap it, type an address or a place, and pick one from the list.
- Navigation starts on the same map with the same turns, miles, time and arrival clock as a job.
- ⭐ Save Destination keeps the places used often, and they are one tap away next time. They are
kept with the driver's account, so they are there on any phone they sign in on.
- The button at the bottom says Stop navigation, because this is an errand, not a trip.
- The driver stays ONLINE the whole time. A ride request still arrives, now as a bar across the top of
the navigation screen with a button that brings the offer card up to accept it.
- Accepting a ride takes the screen back for the job, and says so.
The logo is bigger
The mark in the app headers went from 36 to 50 pixels, the logo over the sign in went to 200, and the size it shrinks to
on a short screen went from 96 to 140. It was getting too small to read.
The passenger sees the same drive, live
The Rider app now has the driver's navigation screen, written for the person in the back seat. Nothing was rebuilt:
the booking, the fare, the payment, the chat and the map are all the ones that were there.
- The road the driver is actually driving is drawn on the rider's map, the same line, on the same
roads, because it is the driver's own route, sent once and drawn again. The passenger's phone asks no routing
service for anything.
- The car is an arrow that points where it is going, moving with every GPS fix.
- The map follows the car. Move it yourself and it stops, with a Follow my driver button to
pick the car back up.
- Live minutes and miles, worked out along the road rather than across the rooftops, and the arrival
time beside them.
- The driver's next move, drawn as the same big arrow the driver sees, with the road they are on.
- When the driver sets off with you aboard, the line, the arrow and the numbers move to your destination
on their own. If the driver leaves the route, the new one appears.
Turn by turn, the way the reference picture looks
The instruction at the top of the driver's map was rebuilt to match the navigation screen the owner sent.
- A big drawn turn arrow with the distance to that turn underneath it, counting down as
the car gets closer.
- The road name in the largest type on the screen, with the small words above it: "Turn left onto",
"Merge toward", "Continue on", "Make a U-turn onto", "Arrive at".
- A strip underneath for the turn straight after this one, which is exactly when a driver needs the
warning.
- The road under the car named on a white pill at the foot of the map.
- The card is repainted every second, so it moves on to the next turn by itself the moment one is taken. It never asks
the router for anything extra to do it.
- Inside the last few yards the card turns green.
- The map turns with the car, so the road ahead points up the screen. The compass button at the top
right switches to north up, and it remembers which one you prefer.
The driver map is a navigation screen now
The same map, the same route, the same trip. What changed is how it behaves while the driver is driving.
- The driver is a vehicle arrow, drawn rather than dropped as a pin, and it turns to face the way the
car is actually travelling.
- The map follows the car, close in at street level, so the road ahead is what fills the screen. Drag it
to look further along and a Re-center button appears to put it back.
- The route is drawn like a route: a bright line on a dark casing, along the actual roads.
- The next turn is drawn, not typed. A right turn is a line that bends right, a U turn is a line that
doubles back, beside the distance to it ("150 ft") and the road it is on.
- Pins on the pickup and the destination, from the real coordinates on the ride.
- A route arriving mid drive no longer zooms the map out to show the whole thing.
- After the passenger is aboard, the map, the route and the pins all move to the destination by themselves, and a
driver who leaves the route still gets a new one.
Drivers stay signed in, and prove who they are
Two changes to the Driver app, both additions.
- The session stays. Closing the app and opening it again lands on the console, with GO ONLINE the only
thing left to tap. Only pressing LOG OUT ends the session and asks for a password next time. The approval check still
runs on the restored session, so a suspended or rejected driver is stopped exactly as before.
- Identity check. A live selfie from the phone's camera, at least twice a week and now and then
unannounced. The driver agrees first, in plain words, and takes it parked. The app asks for a movement, blink or turn
your head, and compares two frames, so a photograph held to the camera does not pass.
- A driver with a check outstanding cannot go ONLINE, and is told exactly why.
- The office compares the live selfie with the one from registration and passes or fails it. The photo is deleted
with that decision, and no face measurements are made or kept at any point.
π Rider’s Lost & Found Return Policy
Something left in a car now has a route home, and a policy of our own behind it.
- The passenger reports it in the 24/7 Support window and picks the ride from the rides they have taken, so the trip and
the driver are identified by the system.
- The driver is told straight away, with the item, where it was left and a number to call. They answer whether they have
it.
- If they agree to bring it back, Rider’s pays the driver a flat $20 Lost & Found Return Fee.
It is $20 whatever the distance, and the passenger is charged nothing at all.
- The driver's screen says exactly that: the fee, that Rider’s pays it, and that the rider is charged $0.00.
- The office sees the report, the driver's answer, the return and the fee, and pays it to the driver's connected Stripe
account with one button.
Drivers are paid by Stripe, every Friday
The 40/60 split is now made by Stripe at the moment the rider pays: the driver's 40% and their whole tip go straight into
their own connected Stripe account, and Rider’s 60% is taken as the platform fee. Nobody has to move money afterwards,
and the company never holds the driver's share.
- Every connected account is set to pay out weekly, on a Friday. Stripe sends it to the driver's bank on
that day; the bank decides when it lands.
- A driver who has not connected an account yet is charged the ordinary way and their share waits for them. Once they
connect, it is sent, and a ride whose share already went with the payment can never be paid twice.
- The Pro Card shows Today, This week, This month, Total, Pending payout, the next payout and the Stripe Connect status,
all counted from the ride records.
- Firebase keeps the record of each ride and payment only: the amounts, the Stripe ids and the statuses. No bank details,
no card numbers, no keys, and no driver wallet.
Saved cards: add, choose, remove
A rider can now keep more than one card, choose which one pays, and remove an old one. It is one wallet for both services:
the card marked as the default is what every ride and every package delivery is charged to.
- A card is added through Stripe's own field and a SetupIntent, and is attached to the rider's Stripe
customer. That is what lets a saved card be charged at the end of a ride, when the rider has already got out.
- Only the brand, the last four digits and the expiry are ever shown or stored. The number and the security code never
reach this site, its server or Firebase.
- Every card request carries the rider's own sign in, and the server takes the rider's identity from that, so nobody can
see or change anybody else's cards.
- A declined card is explained in the bank's own words, insufficient funds and expired cards included, and the form stays
open so another card can be tried straight away.
Paying for a package delivery
The delivery form now has its own payment block. Ride payments are untouched.
- The card on the account is shown safely, brand and last four digits only, with a tick to use it for this delivery.
- Add card or Change card opens the same card form and the same Stripe integration the
account already used. Remove card takes it off the account.
- Only the Stripe payment method reference and the safe display line are stored. No card number, no security code and no
expiry ever reaches the database.
- A delivery cannot be confirmed without a usable card, and the card that was chosen is attached to the delivery when it
is confirmed.
π¦ Rider’s Package Delivery
A second service beside the ride, built on the same machinery: a delivery is a trip, with the same driver claim, the same
live GPS, the same chat, the same payments and the same cancellation. Nothing about the ride service changed, and every job
recorded before today is still a ride.
- Rider app: a Ride or Package Delivery choice at the top. A delivery asks for the size, what is inside,
how many, the weight, both names and phone numbers, and a confirmation that the packages are sealed, legal and allowed.
The price and its breakdown are shown before anything is confirmed.
- Prices: Small $6 + $1.25/mile + $0.30/minute + $3 service fee, Medium $8 + $1.50 + $0.35 + $3.50,
Large $10 + $1.75 + $0.40 + $4. Every number, the minimum, the charge per extra package, the maximum weight and the
driver's share are set in the Admin panel, never in code.
- Driver app: π¦ PACKAGE DELIVERY on the offer and on the job, with what is being carried and who it is
going to. The buttons become Arrived at Collection, Package Picked Up and Delivered, and navigation runs from the
driver to the collection and then to the delivery exactly as a ride does.
- Statuses: PENDING_DELIVERY, DRIVER_ASSIGNED, DRIVER_EN_ROUTE_TO_PICKUP, PACKAGE_PICKED_UP,
DRIVER_EN_ROUTE_TO_DELIVERY, DELIVERED, DELIVERY_CANCELLED.
- Proof of delivery: a four digit code from the recipient, a photo, or a signature, whichever the office
chooses. A delivery cannot be completed without it.
- Admin: the delivery prices, and a Package Deliveries table with the status, the contents, the route,
the recipient, the price, the driver and company split, and the proof.
Turn by turn navigation
Accepting a job now opens a full screen navigation on the Driver app, and the passenger watches the same ride from their
own screen. Both are built on the map, the router, the GPS and the trip record that were already there: there is one of each,
not a second navigation system beside the first.
- Driver: the next turn, its arrow, the road and the distance to it across the top; what is left of the
drive, the time to go and the arrival time across the bottom; and one button that moves the job on: Driver Arrived,
Start Trip, Trip Complete.
- The route runs to the pickup until the ride starts, then to the destination, using the coordinates on the ride record.
If the driver leaves the route, a new one is worked out.
- Passenger: the driver on the way with the time, then "driver arrived", then the trip view with the
miles and minutes left, then the completed screen. It follows the ride by itself, with no refresh.
- Both screens read the same rules, so the distance, the time and the arrival time can never disagree.
The maps no longer open zoomed out
Both apps opened on a fixed point with most of Palm Beach County on screen, and only came in close a few seconds later.
That opening view is what looked like the app zooming out.
- The Driver app opens on the last place its GPS reported, close in on the street, and only falls back to a wider view
when there is nothing remembered.
- The Rider app opens closer, and on the last address the rider asked to be picked up from. Choosing a pickup address now
brings the map to it.
- The Rider app still never asks the phone for its location. That was a deliberate decision and it has not changed:
the map follows what the rider types, not where the phone is.
The phone, not the website, is handing links to the Driver app
An app installed on Android from a website registers itself with the phone for every address its scope covered at the time
it was installed. The Driver app was installed when it claimed the whole site, so the phone hands it every link to
riderstransporthersllc.com, the Admin page included. Nothing on the server can override a claim that lives inside an app
already on the phone.
- The Admin page now notices when it is running inside another app's window and says so, with a button that opens it in
the browser. On Android that button uses an address that names the browser, so the phone cannot hand it straight back.
- The permanent cure is one setting: Settings, Apps, Rider’s Driver, "Open by default", turn off "Open
supported links". Removing and reinstalling the Driver app works too: the new copy only claims its own page.
If it still opens in the Driver app
A phone keeps an installed app's settings until that app updates itself. A Driver app installed before this change still
claims every page on the website, so the Admin page keeps opening inside it even though the settings on the server are now
correct.
- Open the Driver app from its icon, wait a moment, close it. Do that twice: that is what makes the phone fetch the app's
new settings.
- If it still happens, press and hold the Driver icon, remove it, then open
riderstransporthersllc.com/driver.html in the browser and install it again.
- Then open
riderstransporthersllc.com/admin.html in the browser and install the Admin app.
The Admin page now tells you when it is running inside another app, with a button to open itself in the browser, and it has
a Check this phone button that reports what the browser thinks is going on.
Why Install opened the Driver app
All three apps described themselves with a scope of /, which means "every page on this site belongs to me".
On a phone that already had the Driver app installed, opening the Admin page opened it inside the Driver app, and
Install offered to open it there rather than install a separate app.
- Each app now claims only its own page:
/rider.html, /driver.html, /admin.html.
Installing one can no longer be captured by another.
- The names, ids and icons are unchanged, so an app that is already installed updates itself instead of appearing twice.
- One consequence: pages outside an app's own page, such as the menu pages or registration, now open in a browser window
from inside an installed app rather than in the app window.
- If an icon on a phone still behaves like the old one, remove it from the home screen and install again.
What to check on the live databases
- The canonical realtime database rules must allow the Rider app to read and write
trips,
active_trip_request, riders, support_chats, drivers and
driver_locations. That is how the apps already worked.
- A driver appears in Admin as soon as they sign in to the Driver app once. If you also want drivers who have never opened
the app, the driver login project has to allow the Admin dashboard to read
drivers. If it does not, the
dashboard says so in a note under the driver table and carries on.
Back to the app launcher