Transmission 016 · 2026-06-21

Restructured the codebase, built three new verticals, and closed every security vulnerability.

A Python script migrated 25 files into a feature-based layout. Travel Tickets, Courier & Moving, and Kampala Real Estate Broker were built from scratch. The monorepo came out of a security audit clean.

The codebase was flat. Everything lived in pages/, components/, and store/. Before building three new verticals, I wrote a Python script to reorganise it. It either worked or broke everything.


The constraint

A flat structure meant every new vertical scattered files across unrelated folders. Adding Courier & Moving would mean a page in pages/, a component in components/, a store in store/ — no grouping, no ownership. The fix was a feature-based layout under src/features/. The risk was rewriting every import path across the entire codebase in one move.


The proof

The migration script

restructure.py moved 25 files and rewrote every import reference across every .ts and .tsx file it touched:

import os, shutil, re

base_dir = r"d:\Destination\bexze-monorepo\artifacts\fikalo-web"
src_dir = os.path.join(base_dir, "src")

moves = [
    ("pages/LoginPage.tsx",               "features/auth/LoginPage.tsx"),
    ("store/useAuth.ts",                  "features/auth/useAuth.ts"),
    ("pages/TravelTicketsPage.tsx",       "features/tickets/TravelTicketsPage.tsx"),
    ("components/TicketBookingSheet.tsx", "features/tickets/TicketBookingSheet.tsx"),
    ("pages/MyOrdersPage.tsx",            "features/orders/MyOrdersPage.tsx"),
    ("store/useCart.ts",                  "features/orders/useCart.ts"),
    ("pages/LandingPage.tsx",             "features/core/LandingPage.tsx"),
    ("pages/HubPage.tsx",                 "features/core/HubPage.tsx"),
    # ...25 files total
]

replacements = {
    "@/store/useAuth":           "@/features/auth/useAuth",
    "@/store/useCart":           "@/features/orders/useCart",
    "@/pages/LoginPage":         "@/features/auth/LoginPage",
    "@/pages/TravelTicketsPage": "@/features/tickets/TravelTicketsPage",
    "@/pages/MyOrdersPage":      "@/features/orders/MyOrdersPage",
    "@/pages/LandingPage":       "@/features/core/LandingPage",
    # ...full replacement map
}

for src_rel, dest_rel in moves:
    src_path = os.path.join(src_dir, src_rel)
    dest_path = os.path.join(src_dir, dest_rel)
    if os.path.exists(src_path):
        os.makedirs(os.path.dirname(dest_path), exist_ok=True)
        shutil.move(src_path, dest_path)

for root, dirs, files in os.walk(src_dir):
    for file in files:
        if file.endswith((".ts", ".tsx")):
            file_path = os.path.join(root, file)
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()
            for old_import, new_import in replacements.items():
                pattern = re.compile(rf'([\'"]){re.escape(old_import)}([\'"])')
                content = pattern.sub(rf'\1{new_import}\2', content)
            with open(file_path, "w", encoding="utf-8") as f:
                f.write(content)

The new structure:

src/features/
  auth/        — LoginPage, ProfilePage, ProfileDropdown, useAuth
  cars/        — RentalCarsPage, CarBookingSheet, rental-cars
  core/        — LandingPage, HubPage, AdminPage
  courier/     — CourierPage
  orders/      — MyOrdersPage, OrderDetailsPage, CheckoutPage, useCart
  properties/  — PropertyPage, KampalaRealEstatePage
  tickets/     — TravelTicketsPage, TicketBookingSheet

The build passed clean after the script ran.


Travel Tickets — built from scratch

Travel Tickets existed as a card on the landing page with nowhere to go. It became a working vertical.

mockSupabase.ts got a travel_tickets table with a simulator that auto-confirms bookings after 15 seconds:

setTimeout(() => {
  ticket.status = "confirmed";
  dispatchMockEvent("ticket_updated", ticket);
}, 15000);

TravelTicketsPage.tsx — a search dashboard with bus and flight filters for Kampala and Mogadishu routes.

TicketBookingSheet.tsx — a multi-step slide-out drawer: traveler details, seat selection, payment, confirmation.

MyOrdersPage.tsx — a Travel Tickets tab that renders confirmed bookings as boarding passes with QR codes.


Courier & Moving — built from scratch

Four vehicle classes, dynamic UGX pricing, a dispatch simulator with four state transitions:

// mockSupabase.ts — courier dispatch loop
const states = ["pending", "assigned", "picked_up", "delivered"];
let step = 0;
const interval = setInterval(() => {
  if (step >= states.length) {
    clearInterval(interval);
    return;
  }
  order.status = states[step];
  dispatchMockEvent("courier_updated", order);
  step++;
}, 8000);
VehicleUse caseBase price
BodaSmall packages5,000 UGX
CabFragile or medium loads12,000 UGX
PickupHeavy items, appliances35,000 UGX
Box TruckFull house moves80,000 UGX

CourierPage.tsx — district selector for pickup and delivery, vehicle class picker, contact entry, mobile money payment sheet. MyOrdersPage.tsx — status polling wired to dispatch events, showing live state transitions.


Kampala Real Estate Broker — built from scratch

Mogadishu already had property listings. Kampala had nothing. KampalaRealEstatePage.tsx added Kampala-specific rentals with dual UGX and USD pricing, a broker match form with a budget slider from 500,000 to 10,000,000 UGX, and a live-matching simulator:

// mockSupabase.ts — broker matching stages
const stages = [
  {
    status: "SEARCHING_BROKER",
    delay: 0,
    message: "Finding a broker near you...",
  },
  {
    status: "BROKER_ASSIGNED",
    delay: 6000,
    message:
      "Broker Assigned! Andrew Mukasa (+256 782 111 222) is on your request.",
  },
  {
    status: "PROPERTIES_FOUND",
    delay: 18000,
    message: "Andrew Mukasa found 3 properties matching your criteria.",
  },
];
stages.forEach(({ status, delay, message }) => {
  setTimeout(() => {
    inquiry.status = status;
    dispatchMockEvent("fikalo:inquiry_update", { inquiry, message });
  }, delay);
});

When a broker is assigned, a WhatsApp button appears linking to https://wa.me/256782111222 with a pre-filled message. When PROPERTIES_FOUND fires, a banner renders linking to /courier — if you just found a place, you might need a truck.

PropertyPage.tsx required a type fix to support the new Kampala listings — price_ugx was missing from the mock property schema:

// PropertyPage.tsx — before
type MockProperty = {
  price: number;
  // ...
};

// After
type MockProperty = {
  price: number;
  price_ugx?: number;
  // ...
};

The back button now routes dynamically: /kampala-real-estate for IDs prefixed kp, /mogadishu otherwise.


Mombasa paused

Removed from geofencing.ts, useCity.ts, HubSwitcher.tsx, LandingPage.tsx, and BottomNav.tsx. The City type now accepts only kampala and mogadishu. Not deleted — paused.


Hub integration — 9 services, one grid

All three new verticals were registered in KAMPALA_SERVICES and the hub grid replaced its hardcoded row layout with a single wrapping grid:

// HubSwitcher.tsx — KAMPALA_SERVICES
const KAMPALA_SERVICES: ServiceConfig[] = [
  {
    key: "FOOD",
    label: "Order Food",
    subtitle: "Restaurants & cafés",
    icon: UtensilsCrossed,
    isTab: true,
    sectionLabel: "Food Stores",
  },
  {
    key: "PHARMACY",
    label: "Pharmacy",
    subtitle: "Medicines & wellness",
    icon: Pill,
    isTab: true,
    sectionLabel: "Pharmacies",
  },
  {
    key: "GROCERY",
    label: "Groceries",
    subtitle: "Fresh produce & more",
    icon: ShoppingBasket,
    isTab: true,
    sectionLabel: "Grocery Stores",
  },
  {
    key: "RIDER",
    label: "Order Rider",
    subtitle: "Quick errands",
    icon: Bike,
    isTab: true,
    sectionLabel: "Riders",
  },
  {
    key: "CAR",
    label: "Order Car",
    subtitle: "Rides across Kampala",
    icon: Car,
    isTab: true,
    sectionLabel: "Cars",
  },
  {
    key: "RENTALS",
    label: "Rent a Car",
    subtitle: "Self-drive & chauffeured",
    icon: Car,
    isTab: false,
    href: "/rental-cars",
  },
  {
    key: "TICKETS",
    label: "Travel Tickets",
    subtitle: "Bus & flights",
    icon: Ticket,
    isTab: false,
    href: "/travel-tickets",
  },
  {
    key: "COURIER",
    label: "Courier & Moving",
    subtitle: "Packages & relocation",
    icon: Truck,
    isTab: false,
    href: "/courier",
  },
  {
    key: "REAL_ESTATE",
    label: "Real Estate Broker",
    subtitle: "Find homes & offices",
    icon: Home,
    isTab: false,
    href: "/kampala-real-estate",
  },
];

Nine services. Three rows. Any new vertical added to the config renders automatically.


Security audit — clean

$ pnpm --filter @workspace/fikalo-web typecheck
> tsc -p tsconfig.json --noEmit
Exit Code: 0
$ pnpm audit
No known vulnerabilities found

Vulnerabilities were found in an earlier pass. Fixed with pnpm.overrides in the root package.json:

"pnpm": {
  "overrides": {
    "picomatch": "^4.0.4",
    "path-to-regexp": "^8.4.0",
    "fast-uri": "^3.1.2",
    "ws": "^8.21.0",
    "vite": "^7.3.5",
    "yaml": "^2.8.3",
    "postcss": "^8.5.10",
    "qs": "^6.15.2",
    "brace-expansion@^2": "2.0.3",
    "esbuild": "^0.28.1",
    "@babel/core": "^7.29.6"
  }
}

Where this sits

ItemStatus
Feature-based codebase restructureDone
Python migration script — 25 files movedDone
Travel Tickets verticalDone
Courier & Moving vertical — 4 vehicle classesDone
Kampala Real Estate Broker — broker simulator, WhatsApp, dual pricingDone
Mombasa pausedDone
Kampala hub — 9 services, unified gridDone
TypeScript typecheck — zero errorsClean
Security audit — zero vulnerabilitiesClean
Supabase production wiringNot started

Three verticals, one migration script, zero vulnerabilities. The workspace is clean.

← Back to Transmissions