Changelog
Everything that shipped — bugs included
Rendered at build time from docs/CHANGELOG.md in the product repo. The "Fixed" sections stay on the record — a changelog with only features is marketing.
2026-09-08 — mobile: store builds, background-location disclosure, demo mode
- Local signed store builds:
mobile/scripts/build-store-aab.shbuilds a signed AAB (+ universal APK) for any app from the committed native project. Release signing comes fromRIDEKIT_UPLOAD_*environment variables through a new Expo config plugin (plugins/withReleaseSigning.jsin each app) that survivesexpo prebuild --clean;ANDROID_VERSION_CODE/IOS_BUILD_NUMBERare read from the environment. - Driver app: prominent disclosure before background location. Google Play requires an in-app disclosure with explicit consent before the system prompt for background location; the driver app now shows it (14 languages) the first time a trip starts, and a decline keeps the trip on foreground pings.
- Demo mode (
EXPO_PUBLIC_DEMO_MODE=1, our store demo builds only): an amber DEMO strip on every screen, demo sign-in hints,mobile/scripts/demo-build.envprofile. Compiles out of buyer builds. - Play Store listing kit for the three demo apps:
docs/marketing/play-store/. - Sales site: privacy policy now covers the demo apps and no longer shows an owner TODO banner.
2026-09-08 — sales site: payments confirm without webhooks, real order emails
- Payment confirmation no longer depends on the gateway webhook. The success page
asks Razorpay/Stripe directly whether the checkout is paid and fulfils it on the spot;
the account page re-checks that account's open checkouts on every load (new ledger
data/checkouts.json, written when a checkout is minted);POST /api/checkout/reconcile(Bearer owner secret) sweeps everything pending. Fulfilment stays idempotent on provider + reference, so these paths can race the webhook. Lookups are throttled. - Order emails are sent through Resend when
RESEND_API_KEY+EMAIL_FROMare set (text + HTML, reply-to, idempotency key per order). Every mail — sent or not — is recorded indata/outbox/with its delivery result. "Email me the key" re-send on the account page (3/hour per order). Delivery failures never fail the order. - Success page shows the order and the license key when the arrival is trusted (Razorpay signed redirect, Stripe session id, or the signed-in owner), and polls itself while the gateway still reports the payment as pending.
[Unreleased] — Fleet partner console (2026-09-07)
Added
- Sales site: a buyer community —
/communityon ridekit.dev: issues, improvements, feature requests and team announcements, votes, statuses (planned / in progress / shipped) that build a public roadmap. Reading is public; posting signs in with the license key, the same proof the buyer portal takes. Self-hosted in the site's JSON data store, no third-party service.npm run issue-licenserecords a manual order and prints a key for marketplace or offline buyers so they get the same portal, community and downloads. - Sales site: the plan on the pricing page — 6 months of support and updates included, a yearly renewal after that, community access for life, and an optional done-for-you setup service; prices are environment values.
- Sales site redesign — light, product-led, every screenshot a real capture from the end-to-end run, two self-hosted recordings.
- Fleet partner console — the fleet owner's app and web panel now run a
business rather than describe one: live driver status (on a trip / online /
offline, last seen, the car they are on), per-driver detail with trips,
documents (status and expiry only) and per-driver terms, fleet-owned
vehicles (register, edit, assign, free, retire), a trips list and detail
with the money split, earnings by day with trip shares and lease charges
apart, a wallet with the fleet's ledger and withdrawals from the fleet's
own account, and an account screen. Backend:
GET /fleet(live stats),/fleet/drivers(live rows,lookup?phone=,:driverIddetail, termsPATCH),/fleet/vehicles(create / update / remove / types),/fleet/trips(+:rideId),/fleet/earnings(series + split),/fleet/wallet,/fleet/payouts(+limits),PATCH /fleet(profile). - Self-serve fleet application —
POST /fleet/apply: an account that owns nothing applies from the app, the fleet is created unverified, and thefleet_ownerrole is granted. Nothing had ever granted that role: an operator could create a fleet and name its owner, and the partner surfaces built for them answered 403. Now granted on application, on operator creation, and on an ownership change; existing fleet owners are backfilled by migration1700000072000-FleetConsole. - Operator side —
Add fleet partner(owner found or created by phone),Openon a fleet for everything the owner sees plus verify/active switches, applications flagged with the date they applied./admin/partners/*behindpartner.manage. - Fleet payouts —
payout_requests.organization_id; a fleet withdrawal reserves from and refunds to thefleet_ownerledger account, and the operator queue names the fleet. - 15-language admin catalogs and en/hi app catalogs for all of the above,
with parity tests; admin e2e specs for the partners page, the partner panel
and every operator screen; Maestro flows for a full ride on two emulators
and for the fleet app;
mobile/scripts/e2e-side-by-side.sh.
Fixed
- The operator's Partners list never returned
isVerified, the settlement terms, or the driver/vehicle counts — every fleet rendered as unverified with "0 drivers · 0 vehicles" whatever the row held. - A fleet owner's token opened the operator console by URL (an RBAC-empty shell where every call 403s); the console now routes them to their panel.
- The partner app told every fleet owner they were not one when the fleet check failed for any reason; a 403 now leads to the application form and keeps the session, and any other failure says what it is.
site-template's colour fallback was a different teal from every other shipped default.- On the drivers, riders, trips, parcels and vehicles lists, the filter-to-URL
sync (
router.replace) could cancel a row'sOpennavigation that had just started; the URL is now updated withhistory.replaceState, which does not touch the router. - A fleet could never register a plate that had belonged to a retired car:
the clash check read every vehicle ever while the unique index is partial on
deleted_at IS NULL. Live vehicles only, on create and update. - The fleet console's Add-vehicle modal could open before the vehicle-type catalogue arrived and then stay disabled with a type visibly selected; it now takes the first type when the catalogue lands.
[Unreleased] — Platform completion phases 1–4 (2026-08)
Added
- Bidding/negotiation end-to-end — inDrive-style offer/counter-offer UI on
both apps over the existing
PricingMode.BIDDINGbackend, with a one-shot 60-second bid grace on dispatch. - Refunds — operator refund console (
payment.refundpermission): wallet debit first, then gateway refund with compensation on failure; refuses insufficient balance stating the max refundable. Corporate rides get credit notes instead (no gateway leg to reverse). - Women-only rides — market-gated (
womenOnlyEnabled), fails closed at booking before pricing; driver side filtered in dispatch SQL. - Live trip sharing — tokenized public tracking page (identity, phone and money structurally excluded from the payload); unknown/revoked/expired tokens all 404 identically.
- Safety centre — trusted contacts (cap 5), SOS fan-out SMS carrying a
live-trip link, automatic ride check-in (long-trip and stationary signals,
escalation to a HIGH incident), emergency numbers for all 37 markets in
market config. Audio recording deliberately deferred — see
docs/safety-audio-recording.mdfor the reasoning and the full design. - Driver retention — destination mode (2/day, dropoff must close ≥25% of remaining distance), trip preferences (pickup distance / min fare / long-trip / tier opt-outs), auto-accept through the normal accept path, payout limits endpoint with honest settlement estimates.
- Corporate rides — book on a company account with credit-limit enforcement at booking, org settlement as a first-class ledger account (negative balance = the month-end invoice), org-owner portal in the rider app, admin billing console with credit notes and company payments.
- Parcel delivery — full vertical: OTP-at-both-ends + photo proof of delivery (codes redacted from the courier's app), priced failure paths (free retry → return-to-sender at 60%, courier paid both legs), chain of custody in admin, per-market size/weight tier pricing, market gating.
- Rentals — hourly packages (1h/10km … 12h/120km) seeded from each market's own economy fare, live overage meter mirrored in both apps, mid-trip destination edits, package-expiry sweeper, rider-ends-the-trip semantics.
- Intercity/outstation — one-way (1.3×) and round-trip (0.85× with a 250 km/day floor), driver daily/night allowances and toll/permit line items, driver opt-in as a dispatch predicate, 30-minute dispatch lead on scheduled departures.
- Local payment rails — M-Pesa STK push, Paystack, Flutterwave, OXXO
vouchers (top-up only) and GCash behind a new
AsyncTopUpProviderinterface (instruction + later-outcome semantics); Pix retrofitted onto the same path. Each rail declares an honest integration status the admin shows as "Keys set · unverified" until proven against the live provider. - Languages — Thai, Vietnamese and Indonesian across rider, driver and admin (catalogs now 14/14/15), parity-test enforced.
- Public banners endpoint —
GET /content/banners?audience=serving only live, windowed banners with an app-safe payload.
Fixed
- Ledger-corrupting minor-unit bugs (
formatMoney/toMinorSafehardcoded/100; JPY and KWD amounts rendered and stored wrong). - Tax invoices claimed
issuedwithout any authority response; local fallback is nowlocal_only. - Re-inviting a revoked org member silently did nothing (stale soft-delete timestamp written back over the restore).
- Unaccepted parcels were uncloseable (
no_driversterminal-state gap). - Payout reconciliation: confirmed failures now return driver funds; uncertainty never moves money.
- Rail errors no longer surface to riders as HTTP 500.
backend/storage/untracked from git — dev uploads (including driver KYC test documents) no longer ship in the product archive.
[Unreleased] — Gap Closure (P0 → P3)
Closing every item in Claude_Code_Gap_Closure_Spec.md, sequence admin → rider
→ driver. Each item verified end-to-end (UI → API → DB → response), all four
codebases (backend / admin / rider / driver) tsc-clean.
Added
- Multi-stop rides (up to 3) — multi-leg fare pricing (
distanceKmMultiPoint), rider "+ Add stop" flow + multi-leg polyline, driver per-stop arrival progression,POST /driver/rides/:id/stops/:index/arrive(idempotent, ownership-checked). Verified: fare rises monotonically with total leg distance; 4th stop rejected (400). - Driver incentives / quests — operator-defined target campaigns (rides or earnings goal → wallet reward), admin CRUD (
/admin/incentives), driver quests screen with live progress, idempotent ledger-backed award (posts asadjustment→ shows in earnings). Verified: reaching target pays once, never double-pays. - Country Preset Pack — canonical 37-country data (currency/locale/timezone/dial/distance-unit/gateways/tax-engine/top-up presets), seed migration, installer applies + activates a market on onboarding, both apps'
COUNTRY_DIALSregenerated from the same source. - Distance units (
km/mi) — per-marketdistanceUniton/branding,formatDistance()helper (km→mi at render), i18n unit labels. - Market-driven dates & timezone — locale-aware client date helpers; server-side
formatInMarketTz()(Intl, zero deps); daily report buckets + tax-invoice issue date rendered in the market IANA timezone (storage stays UTC). - Call masking — provider abstraction (Twilio Proxy-style) with direct-dial default;
GET /rides/:id/call-numberreturns a masked or real number, ownership-checked; both apps dial via the endpoint. - Government e-invoicing live wiring — all 4 tax engines (India GST / ZATCA / CFDI / NFS-e) credential-gated: real authority/GSP filing when configured, deterministic local invoice as dev/B2C fallback; never blocks a ride. See
docs/E-INVOICING.md. - Referral programme, broadcast (push/email/SMS), CMS FAQ + Blog, provider credential store (AES-256-GCM), SMS providers (Twilio/MSG91), S3/R2 storage, RazorpayX payouts, social login (Apple + Google) — full slices, all verified.
- Docs —
E-INVOICING.md(per-country credential requirements),BRANDING-ASSETS.md(white-label icon/splash spec).
Changed
- White-label theming — welcome-screen gradients, Razorpay checkout name/theme, push LED colour, and wallet top-up presets all now derive from the market/branding config instead of hardcoded values; app icon/splash paths are env-overridable.
- Status colours tokenized (rider + driver) — hardcoded success/danger/warn hex replaced with theme tokens that adapt to dark mode.
- Money formatting — one shared
formatMoney+useInstallationacross the admin app (removed 10 localmoney()helpers).
Fixed
- Promo money bug — a shown discount is now actually charged (promo persisted at booking, settled on the discounted fare).
- Saudi gateway routing — SA now resolves to Moyasar (was falling through to Stripe); removed gateway keys with no provider code.
- Socket event mismatch — offer-expiry now dismisses correctly (
ride:offer_expiredshared constant). - Silent error swallows — stale-tracking / fake-zero-balance / invisible-reject-failure surfaced to users; backend config-load failures now logged before defaulting (never silently price on defaults).
- GeoJSON coords — pickup/drop-off markers now render from the raw ride entity's PostGIS GeoJSON in both apps.
[Unreleased] — Phase 3: Multi-Entity Panels
Added
- Admin panel (Next.js,
/admin) — OTP login, live dashboard (stats + trips, auto-refresh, sparklines, status pills, skeletons), drivers, riders, reports, dispatch. Premium UI pass (dark sidebar + light content); a deeper redesign is still planned. - Dispatcher — manual booking on a rider's behalf, force-assign + change-driver (
ride.assigngated). Live-verified. - Geofencing / zones — PostGIS polygon zones (service / restricted / airport / surge), point-in-zone (
ST_Covers, GIST-indexed), serviceability check. Live-verified with real Bangalore polygons. - Fleet / Corporate / Franchise — unified
organizationsmodel + memberships; create, add/remove members, scoped reports (fleet earnings / corporate billing vs credit limit / franchise city), owner-only isolation (403 otherwise). Live-verified all three types. - Docs — Admin Guide (panels, dispatch, zones).
Fixed
- Added admin panel origin (
:3200) to backend CORS (was blocking the panel login).
[Unreleased] — Phase 2: Money & Operations
Added
- Wallet on the double-entry ledger — top-ups, balances, idempotent.
- Ride settlement + commission — completing a ride splits the fare (rider → driver + platform commission) in one balanced entry; commission per vehicle type, floored.
- Pricing engine extras — configurable surge/night multipliers (
pricing_rules, compounding, midnight-wrap windows), waiting charge, flat toll. - Cancellation policy engine — admin-configurable free window, late-cancel fee, no-show fee, driver/rider fault; rider may go into debt for mandatory fees.
- Promo codes (flat/percent + cap, limits, validity) and referrals (refer-a-rider + refer-a-driver, separate rewards) — all credited via the ledger.
- Payout engine — driver wallet → bank, reserve→process→paid/failed-refund, pluggable rails (manual + RazorpayX stub),
payout.approvegated. - Payment integration — gateway charge → wallet top-up bridge; real Stripe (PaymentIntents, India 3-D Secure handled via webhook capture), signed webhook verification; manual/cash provider; refunds.
- Reports — revenue, drivers, customers, trips, tax (taxable base) — RBAC
report.view, date-ranged, reconciled from the ledger. - Docs — Admin Guide (money & operations), User Manual (wallet & payments).
Fixed (found via live e2e testing)
- Cancellation fee on a rider with no balance was blocked — rider wallet now goes into debt for mandatory fees.
- Assigned driver was not released back to
onlineon cancel/no-show. - Discovered (via real Stripe keys) the account is India-registered → server-side auto-capture isn't possible; reworked to webhook-driven capture.
[Unreleased] — Phase 1: Core Ride Loop
Added
- Ride schema — vehicle types, vehicles, driver profiles (PostGIS location), rides, immutable ride-event audit, ride bids. GIST spatial indexes.
- Driver presence — online/offline, location updates (PostGIS geography).
- Dispatch engine — PostGIS nearest-driver query (ST_DWithin + ST_Distance), distance + rating weighted, GIST-indexed. Live-verified ranking.
- Fare estimate — base + per-km + per-minute, min-fare floor, per vehicle type; integer minor-unit money math.
- Ride loop — book → accept → arrive → start → complete, enforced by a state machine; every transition audited and pushed to rider + driver.
- Bidding — inDriver-style negotiation toggle (rider bids, driver counters, rider accepts at agreed fare).
- Realtime — Socket.IO
ride:status/driver:locationevents to per-user rooms (JWT-authed, Redis-adapter fan-out). Live-verified. - Basic admin — RBAC-gated live trips, dashboard stats, driver/rider lists.
- Docs — User Manual (booking), Driver Guide (going online + trip flow).
Fixed (found via live e2e testing)
rides.pricing_modecolumn mapping missing on the entity (every ride fetch 500'd).- Bidding accept DTO missing a validation decorator (rejected by global whitelist).
[Unreleased] — Phase 0: Foundation
Added
- Project scaffold — NestJS + TypeScript, ESLint/Prettier, Node 20 pinned (
.nvmrc,engines). - Config + env validation — typed config; boot fails fast on missing/weak vars (JWT secret ≥32 chars; prod rejects placeholder secrets and empty CORS).
- Database — PostgreSQL + PostGIS via TypeORM; UUID PKs, soft deletes, dormant
tenant_id, migration tooling. Extensions auto-enabled by first migration. - Wallet ledger — double-entry, immutable. DB-enforced: per-currency balance, ≥2 postings, positive amounts, immutability triggers, one system account per (owner_type, currency).
- RBAC — roles + permissions +
@RequirePermissionsguard; 8 system roles seeded. - Auth — phone OTP (Redis, hashed), JWT access + rotating refresh tokens (theft detection), password login, social-login structure, SMS provider plugin (dev log provider).
- Payments — gateway plugin layer (manager pattern); manual/cash provider + Stripe stub with HMAC webhook-signature verification.
- Storage — local + S3/R2 driver abstraction, config-selected; path-traversal guarded.
- Realtime + queues — Socket.IO with Redis adapter (multi-instance), BullMQ background jobs.
- Observability — pino structured logging, Sentry capture filter,
/api/healthreadiness (DB+Redis, returns 503 on failure with bounded Redis ping). - Ops — Docker multi-stage image (non-root), docker-compose (Postgres+PostGIS, Redis), GitHub Actions CI (lint→build→migrate→test→docker build).
- Docs — Installation + Deployment drafted; section tree scaffolded.
Fixed (found via live testing during Phase 0 audit)
- Ledger: cross-currency false-balance, duplicate system accounts, zero-posting entries, mixed-case currency split wallets, soft-delete vs immutability crash, idempotency race returning 409/500 instead of the existing entry.
- Runtime: health check hanging on dead Redis; 5xx errors silent without Sentry DSN.
- Auth: refresh
replaced_bytyped uuid vs token hash (every refresh 500'd). - Build: broken
package-lock.jsonfailingnpm ciin Docker/CI.