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.
[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.