Inspiration

I commute on Sydney transport every day, and I got tired of apps that tell me when something is due but not what I actually need to know. Is the prediction genuinely live or just a timetable dressed up as certainty? Which platform do I need for the connection I am about to sprint for? Is there a quieter carriage or a better service one stop away? What happens when GPS disappears in a tunnel?

Transport for NSW publishes rich open data: vehicle positions, trip updates, service alerts, route geometry and, on some rolling stock, per-carriage occupancy. Yet very little of that reaches a rider in a form they can understand in the ten seconds before the doors close.

The project’s working name for a long time was literally “unshitty transport.” That is still the inspiration: take data that already exists, preserve the uncertainty inside it, and stop wasting it.

What I built with Codex

Platform is a native SwiftUI transit companion for iPhone, iPad and Apple Watch, with widgets and Live Activities, backed by a production proxy for Transport for NSW Open Data. It is designed as a network control room for ordinary riders rather than another timetable list.

  • A fullscreen live map of roughly 3,700+ vehicles at peak across Sydney trains, Metro, buses, ferries and four light-rail networks. It uses Transport for NSW mode and line colours, real static-GTFS route geometry, lane offsets for overlapping services, viewport-loaded bus stops and shapes, line-focus mode and universal search.

  • A real multimodal journey planner with Leave now, Leave at and Arrive by, exact journey corridors, saved trips and folders, stop selection from the map, local-only 90-day history and opt-in, deterministic usual-trip suggestions. Future journeys can be saved with a reminder, but tracking never starts without an explicit rider action.

  • GPS-driven journey tracking for multi-leg trips. The estimator uses live location, Core Motion hints, live vehicle evidence and an aware timetable fallback. It can advance stages automatically, reject implausible teleports, stop the timetable from racing ahead of the rider’s last real fix and, after repeated matching evidence, re-anchor an off-plan leg to the actual same-route service carrying the rider.

  • Station, platform and wharf departure boards for all five modes, including platform filters, capacity where the source provides it, Park&Ride and payment information, exact stopping patterns and a jump from a board to the corresponding live vehicle. On iPad, the same information becomes floating dual panels or a kiosk-style board inspired by current Sydney station indicators, with keyboard and pointer support.

  • Lock Screen and Dynamic Island Live Activities that say things like GO TO P26 for the next connection, rather than showing only a countdown. Saved-route widgets open the exact trip they display. The Apple Watch companion mirrors the same journey state, provides phone-computed haptics, shows the stopping pattern with the Digital Crown and supplies departures and complications.

  • Privacy-bounded trip sharing: an active journey can produce an expiring, revocable code or universal link so somebody can follow its derived progress along the planned corridor. The payload contains no rider GPS trail or device identifier. The feature is built, but its final Associated Domains, privacy sign-off and physical-device release gates remain deliberately open.

  • Relevance-ranked service alerts with a short on-device Apple Intelligence brief on supported systems and a clearly labelled deterministic fallback. Alert text is treated as untrusted source material, and the model is constrained to summarise only supplied facts.

  • A self-collected carriage-load dataset. Because the official occupancy feed is sparse, the backend records occupancy-bearing stop visits and builds run-, destination- and network-level profiles. The serving switch remains off until a held-out evaluation, after enough real collection time, proves that the model beats a naive baseline and I explicitly approve it. Until then, Platform withholds the estimate instead of pretending the data is stronger than it is.

The current release candidate also includes a first-run introduction, a Planner-first simple home screen, minimisable tracking, large-text and VoiceOver hardening, honest partial-feed states and personalisation for regular rail, Metro, light-rail and validated bus services. The protected internal TestFlight lane has produced successful Xcode Cloud archives across iOS and watchOS; the newest follow-up fixes still have to pass the same protected merge, cloud-build and physical-device acceptance path before external release.

I got Codex to architect a chain of prompts to fix the first TestFlight bugs/feedback and it managed to complete a 13 hour turn before hitting my usage limits.

How I built it

I am building Platform solo, pre-funding and cost-conscious, so operating cost and verification are product constraints rather than afterthoughts.

The app repository now contains only the Swift/Xcode product: the iPhone and iPad app, iOS widgets, Apple Watch companion, watch widgets and a shared PlatformKit Swift package. The app is built with SwiftUI, MapKit, Core Location, Core Motion, ActivityKit, WidgetKit and WatchConnectivity. A pure, unit-tested journey estimator sits at the centre. Given timestamped GPS fixes, movement hints, live service evidence and a schedule, it produces one bounded view of journey progress with explicit rules for which signal is allowed to win.

The production backend lives in a separate private repository. It is a Node/Express proxy in front of Transport for NSW’s GTFS-Realtime and Trip Planner services, with Redis caching and one dedicated poller so many app clients collapse into one upstream request per feed window. The proxy is versioned under /v1, credentials never enter the app, and production traffic goes through api.platformtransit.com hosted on AWS and proxied(again) via Cloudflare.

That ownership model matters because the Transport for NSW account is capped at 60,000 calls per day. Seventeen poll targets at a 30-second cadence consume about 48,960 calls per day, roughly 82% of the allowance before Trip Planner searches and other work. Running a second independent poller would almost double that and exhaust the shared account quota, so Platform enforces a single upstream owner, shared caching, coordinated backoff and an account-wide circuit breaker instead of trying to hop between tokens after a rate limit. However, TfNSW gave approval to increase that limit for this app.

The same discipline applies to delivery. staging is a protected internal-TestFlight lane and main is the protected external-release lane. Changes reach staging through pull requests, resolved review conversations and required release-policy checks; successful internal acceptance comes before promotion to main. The latest large release passes hundreds of serial Swift tests, warning-as-error Debug and optimised Release builds across all five targets, static analysis, privacy-manifest checks and iPhone, iPad and Watch Simulator smoke tests. Hardware-only behaviour, tunnel recovery, haptics, locked-screen lifecycle, accessibility and tracking-camera performance stays on a human acceptance checklist rather than being inferred from a green Simulator run.

Codex became part of the engineering system, not just a code generator. I use agents for implementation, adversarial review, regression tests, release-policy checks and documentation, while routing routine work to cheaper models and reserving deeper reasoning for architecture and milestone reviews. Agent output only counts when the relevant build, test, live-feed or device evidence supports it.

What I learned

The deepest lesson is that real-world transit data is adversarial in ways a specification does not prepare you for. Transport for NSW’s coord field is [latitude, longitude], despite the surrounding convention making [longitude, latitude] look plausible. A T8 service to Leppington can reuse a Macarthur static shape ID, so blindly trusting shape_id draws the wrong tail unless the geometry is clipped against the trip’s real stop pattern. A repeated trip number is not enough to identify a physical service. A future timestamp, stale vehicle sample or partly failed feed can make a polished interface tell a very convincing lie.

Those failures became the product philosophy: never fabricate confidence. Live, predicted, inferred, scheduled, stale, partial and unavailable are different states. Amber is reserved for genuinely fresh live evidence. If the evidence does not support “on time,” “this is your train” or “this carriage is usually quiet,” the app either labels the weaker source or says nothing.

I also learned that cross-device products are distributed systems in miniature. The phone, Live Activity, widget, Watch and share page can all be individually correct and still disagree because one cache outlives its owner, a timestamp is refreshed for the wrong reason or a late async task resurrects state after a journey ends. The safest design was to share one wire model through PlatformKit, preserve measurement timestamps and exact journey identity, and make every stale or destructive transition explicit.

Finally, AI coding agents are a genuine force multiplier for a solo founder, but only with strong boundaries. Broad independent reviews found concurrency, privacy, stale-data, accessibility and lifecycle defects that feature-by-feature testing missed. The value came from combining agent speed with source-of-truth documentation, protected release lanes and evidence that can falsify the implementation.

Challenges I faced

  • Quota scarcity as an architectural constraint. Every feature has to justify its upstream cost. That pushed the system toward one poller, shared Redis caching, viewport-demand data and coordinated failure handling rather than naive per-client fetching.

  • GPS underground and in dense corridors. Sydney Metro and City Circle tunnels can remove location evidence for minutes. The tracker needs a physically plausible timetable fallback without silently claiming it is live, plus a boarding gate that refuses to assume a missed connection was caught.

  • Exact service identity. Reused trip numbers, changing consist identifiers, early or displaced services and out-of-order GPS samples can attach a rider to the wrong train. Fixing that required service-instance identity, timestamp ordering, destination validation and repeated evidence before rebinding.

  • Keeping five surfaces honest at once. The app, widgets, Live Activity, Watch and trip-sharing page must agree about progress, freshness and provenance even across relaunches, locks, revocations and partial network failure.

  • Data quality problems that appear only against the real network. Coordinate order, reused shapes, set-down-only stops and partial feeds can all survive sample fixtures. Live-contract probes, captured regressions and production-shaped tests became essential, while secrets and direct Transport for NSW access remain confined to the backend.

  • Shipping without lowering the bar. Xcode Cloud exposed a compiler-budget failure that local correctness tests did not. Independent release review then found more cross-target races. The response was to split compiler-heavy views, expand strict CI and route every candidate through the protected internal TestFlight lane, not to treat a successful archive as proof that physical-device behaviour is accepted.

  • Privacy and accessibility as architecture. Location is When In Use, background tracking exists only during a rider-started journey, local history stores no GPS trail and shared journeys expose only derived corridor progress. Large text, VoiceOver, Reduce Motion, minimum touch targets and small-watch layouts are release criteria, not post-launch polish.

Platform uses Transport for NSW Open Data under CC BY 4.0. It is an independent project and is not an official Transport for NSW app, partner or endorsed service.

Built With

Share this project:

Updates