Back to work

Tripshepherd - Native Guide App

One tour-day flow that holds four kinds of tour, across 22 cities

Tripshepherd runs tours in 22 cities with a roster of about 240 guides. I wrote the requirements, flows and code prototypes behind the native app they work from, and built the app's design system in Kotlin.

Year
2026
Role
Product Designer
Team
Engineering, Operations
The Tripshepherd wordmark

Overview

Tripshepherd is a tour operator: 149,045 bookings across 22 operating cities, a roster of about 240 guides, and a fleet of 74 vehicles. Guides work from a native app on their phone, on the road, between pickups, often with one hand.

I was the product designer on that app. I wrote the behavioural requirements, drew the flows, built the code prototypes engineering implemented from, and wrote the app's design-system module in Kotlin.

What I made

Behavioural specs, 10,899 lines, with acceptance criteria per screen
36
Deployable React prototypes, 14,793 lines, every state a screen can be in
9
Kotlin files: the app's Compose design system and Host Mode screens
84

Why a guide app is harder than it looks

A tour is not a calendar event. A guide can be rostered for a shift with no tour on it, so scheduled and assigned are different states and both have to be designed. A guide asking for three days off can strand tours already assigned to them, and every one of those has to be handed to someone else before the request can even submit. A guide picking up an open shift has to be certified to run that specific tour.

Most of the work was turning rules like these into states a guide could read while driving.

The tour day

The tentpole flow is the working day: five phases from opening a tour to closing it out. Pre-tour, pickups, tour in progress, drop-offs, complete. Each phase is its own route with its own guard, so a guide cannot reach drop-offs with unresolved pickups, and cannot end a tour with stops still open.

Tour group detail: date, pax, vehicle, and the pickup list
Phase 0. The day's assignment.
The pickup list, ordered by dispatch
Phase 1. Collect the party.
Pickups Review and Resolve, showing guest waivers signed
The gate that stops a deferred waiver slipping through.

One flow, four kinds of tour

Driving, walking, shuttle leg and juggle are four different shapes of day. Rather than four implementations, they share the same five phases through phase-aware props: the pickup carousel, the route card, the skip-reason modal and the list control all take the phase rather than being duplicated per type. A walking tour sets no drop-offs and the flow adapts around it.

A shuttle booking defaults to three legs but any of them can be removed, so the leg set is treated as a list of one to three rather than a fixed shape. A shuttle driver is a guide holding a shuttle certification, not a separate user type with a separate pool. Certification is a layer, not a role.

Three operational rules that changed the design

  • Coverage eligibility is a gate, not a list. Recipients are filtered by certification and conflict up front, so a guide is never offered a shift they cannot legally run. The gate is asymmetric: it applies to whoever claims, not to whoever gives away.
  • One waiver per booking, signed by the lead guest for the whole party. That dropped per-companion signer records from the backend and propagated to every surface that collects them.
  • The post-completion stats screen was removed. The stats still log to operations. The guide just doesn't need them at the moment they finish a tour.

Iterating in the open

Schedule v2 was worked out across a single day. A week grid was rejected in favour of a month calendar, then refined down to the final layout. The city filter went through three treatments before it landed on the canonical one.

First pass: a week grid
Started as a week grid.
Second pass: a month calendar
Rejected for a month calendar.
The final schedule layout with shifts listed by day
Where it landed.
City filter, first treatment
City filter v1.
City filter as a pill
As a pill.
The canonical city filter treatment
Canonical.
The Today card, first redesign
The app's primary card, redesigned.
The Today card with passenger counts moved into an inset
Passenger counts moved into an inset.
The final Today card with three tours listed by time of day
Final, with canonical tokens applied.

Parity with the native app

The web app was rebuilt with the native app as the source of truth for every API contract, payload and business rule. That made parity measurable rather than aspirational: 75% at the formal measurement, 49.5 of 66 wired screens scored full, partial or missing, and around 81% after the alignment sweep.

Profile screen before alignment, showing drift from the native layout
Before: drifted from native.
Profile screen after alignment with the native layout
After.

A reversal, from one guide

The app opened on an AI-first command centre. In the June first-run study, a working guide testing it on a live revenue tour read it as a threat.

I know neither myself nor other guides will want a company app doing anything with my personal accounts. That is a breach of privacy.
Guide, first-run usability study

The synthesis recorded it as disconfirming rather than softening it. It contradicted an assumption that AI-forward framing reads as modern and valuable. To the person who would actually use it, it read as surveillance.

How it was checked

Sixteen screens across four iPhone viewports, 64 assertions per run: no horizontal overflow, sticky header on screen, bottom action inside the viewport, zero console errors. The script exits non-zero on failure, so a layout regression stops the build rather than being spotted later.

Not every decision was to build. Three of the four rulings in the July readiness audit were decisions not to: ship the waiver copy knowingly as an operations dependency, ship without availability and delete the dead intake chain, keep dispatch chat hidden to match native. Each was recorded with what was accepted, not just what was fixed.