Transmission 019 · 2026-08-13

Replaced every mock in the passenger app and wrapped it for iOS and Android.

27 files swept to light mode. The ride request flow now inserts directly into Supabase and subscribes to postgres_changes for real-time driver updates. Eight files had mock arrays stripped and replaced with live queries. Capacitor wrapped the Vite bundle into native iOS and Android projects.

The passenger app had four layers of fake data. Every layer needed to come out before the app could connect to a real driver, a real vendor, or a real rental car.


The constraint

The codebase had three categories of problems.

First, 27 files contained hardcoded dark hex values (#0B0F1A, #141929, #1C2333) and the old teal accent (#00D4AA). The design system had moved to light mode with Lime Green (#10B981), but the components had not followed.

Second, RideRequestSheet.tsx was not talking to the database at all. handleConfirm called fetch("/api/rides/request") — a route that does not exist in production — and a set of useRef timers auto-advanced trip status from searching to assigned to en_route without any driver ever accepting. Five mock drivers lived in MOCK_DRIVERS as a fallback when no WebSocket data arrived.

Third, HubPage.tsx, rental-cars.ts, TravelTicketsPage.tsx, StorePage.tsx, PropertyPage.tsx, MyOrdersPage.tsx, and two others held hardcoded arrays — MOCK_VENDORS, MOCK_CARS, MOCK_SCHEDULES, MOCK_ORDERS. Each returned fake data if Supabase was unreachable, or fake data unconditionally.


The proof

Phase 1 — light mode sweep

The sweep ran in two rounds. Round 1 touched the four core components: RideRequestSheet.tsx, BottomNav.tsx, HubSwitcher.tsx, and BuyAgainCarousel.tsx. Round 2 ran 22 files across four parallel agents. After both rounds, a pattern scan returned:

PatternResult
Dark hex colors (#0B0F1A, #141929, #1C2333)Zero matches
Old teal accent (#00D4AA)Zero matches
bg-white/[, border-white/[, hover:bg-white/[Zero matches
dark-shimmer in componentsZero matches
rgba(255,255,255,...)8 intentional — frosted headers, image overlays

27 files changed. Every dark background replaced with #FFFFFF or #F9FAFB. Every TEAL constant replaced with LIME (#10B981). Every text-white on a light surface replaced with text-slate-900 or text-slate-500.


Phase 2 — live ride insertion

handleConfirm in RideRequestSheet.tsx previously called:

fetch(`${API_BASE}/api/rides/request`, {
  method: "POST",
  body: JSON.stringify({ pickup, destination, vehicle, payment_method }),
})

That route does not exist. I replaced it with a direct Supabase insert:

const { data, error } = await supabase
  .from("orders")
  .insert({
    status: "searching",
    payment_method: paymentMethod,
    fare: fareResult?.fare ?? 0,
    pickup: { address: pickup, lat: pickupCoords![0], lng: pickupCoords![1] },
    destination: { address: destination, lat: dropoffCoords![0], lng: dropoffCoords![1] },
    customer_id: userData?.user?.id,
  })
  .select("id")
  .single();

I removed fallbackTimerRef, fallbackChainRef, isDemoFallbackRef, fallbackClearedRef, and the entire useEffect that auto-advanced trip status on a clock. The MOCK_DRIVERS array — five hardcoded Ugandan names, plates, and ratings — came out entirely. liveDriver now resolves to wsDriver or null.

In place of the timers, a postgres_changes subscription activates the moment orderId is set:

supabase
  .channel(`order-${orderId}`)
  .on("postgres_changes", {
    event: "UPDATE",
    schema: "public",
    table: "orders",
    filter: `id=eq.${orderId}`,
  }, (payload) => {
    const status = payload.new.status;
    if (status === "assigned")  setTripStatus("assigned");
    if (status === "en_route")  setTripStatus("en_route");
    if (status === "completed") setTripStatus("arrived");
    if (status === "cancelled") setTripStatus(null);
  })
  .subscribe();

When the subscription unmounts or orderId changes, supabase.removeChannel(channel) cleans up.


Phase 3 — live hub queries

Eight files had mock arrays removed and replaced with live Supabase fetches.

HubPage.tsx previously fell back to MOCK_VENDORS when hasSupabase() returned false. The guard came out. The table name changed from vendors to partners — the correct table in the production schema:

const { data, error } = await supabase
  .from("partners")
  .select("*");

if (error || !data) {
  setVendors([]);
  return;
}

rental-cars.ts removed MOCK_CARS and the usedMock return flag. fetchRentalCars now returns { cars, error }. An empty response renders an empty state, not fake cars.

TravelTicketsPage.tsx removed MOCK_SCHEDULES — 10 hardcoded bus and flight rows — and added a useEffect fetching from supabase.from("travel_schedules").select("*").

StorePage.tsx removed MOCK_PRODUCTS and fetches from supabase.from("products").select("*").eq("vendor_id", id).

MyOrdersPage.tsx removed the entire localStorage mock branch — courier orders, broker requests, travel tickets, and ride orders all returned as fake data if Supabase was unreachable. All six data sources now fetch exclusively from live tables. The catch block resets every array to [].


Phase 4 — Capacitor native shell

I installed the four Capacitor packages:

pnpm install @capacitor/core @capacitor/ios @capacitor/android
pnpm install -D @capacitor/cli

capacitor.config.ts in the project root:

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.fikalo.passenger',
  appName: 'Fikalo',
  webDir: 'dist'
};

export default config;

pnpm run build compiled 2,278 modules in 20.12 seconds. Then npx cap sync returned this:

[fatal] The Capacitor CLI requires NodeJS >=22.0.0

The machine was on Node v20.19.6. I upgraded with nvm:

nvm install 22
nvm use 22.23.2

cap sync ran clean:

√ copy web in 324.83ms
√ update web in 211.52ms
[info] Sync finished in 0.975s

The ios/ and android/ native project folders now exist in the repo.


Where this sits

ItemStatus
27-file light mode sweep — zero dark hex matchesDone
handleConfirm — direct Supabase insert into ordersDone
Demo fallback timers — removedDone
MOCK_DRIVERS — removedDone
postgres_changes subscription for real-time driver updatesDone
HubPage.tsxMOCK_VENDORS removed, live partners fetchDone
rental-cars.tsMOCK_CARS removedDone
TravelTicketsPage.tsxMOCK_SCHEDULES removedDone
StorePage.tsxMOCK_PRODUCTS removedDone
MyOrdersPage.tsx — mock localStorage branch removedDone
Capacitor installed, cap sync completedDone
iOS and Android native projects generatedDone
Google Maps API key — not yet wiredOpen
PesaPal production credentials — not yet wiredOpen

The passenger app no longer simulates a ride. It books one.