Gaines Concierge is my client booking service, and its operator console is a native iPhone app I built from start to finish. A client requests a time slot, my phone rings, and I approve it from the lock screen. The client's confirmation email is on its way before the phone is back in my pocket. It's the system my business runs on every day, and the project that took me from no iOS background to a production SwiftUI app.
Tuesday, October 7
2:04
New booking request — Thu 2:00 PM MT. Hold to respond.
The Challenge
In a one-person service business, how fast you respond is the product. A booking request that waits three hours for an answer is a client who has already booked somewhere else. But the person who has to answer is usually busy doing the work, not sitting at a dashboard.
The Real Constraint
Every decision had to be possible in seconds from wherever I was, couldn't be lost to a dropped signal, and couldn't be applied twice. A double-approved booking or a lost "yes" is a broken promise to a real person.
My Approach
The system is built on three machines that never talk to each other directly. The server runs on a VPS. The app is built in Xcode on a Mac. A home server is being brought in for private storage and inference. The server side is built by a Claude Code session on the VPS, and the app is built from the Mac. Neither can see the other's code.
So the contract comes first. A written CONTRACT.md in shared git defines every route,
payload, error code, and retry rule, with an OpenAPI mirror and a folder of example payloads beside it.
Git is the only channel between the two sides. If the contract doesn't say it, neither side builds it.
- Contract before code: both halves implement against the same committed spec
- Shared fixtures: the Swift tests and the server tests read the same JSON files, not copies
- Idempotency everywhere it matters: every decision carries a key the server dedupes on
- One decision path: browser and phone approvals go through the same server function
The Solution
The front door
Clients reach me through QR introduction codes, a contact form with email verification, and a booking
page backed by a scheduler that handles services, availability, and blackout dates. It's all one Flask
app on the VPS, behind a versioned /v1 API.
The iPhone app
The app is a thin SwiftUI operator console. It shows what needs my decision and sends the answer back. The interesting parts are under the surface:
- ConciergeKit, a Swift package that holds every contract type, the JSON coding, and the API client, and is tested without a simulator against the shared fixtures
- Actionable APNs notifications, so Approve and Decline work from the lock screen, with urgency mapped to interruption level
- A pending-answer queue that saves answers given without signal and retries them with their original idempotency key
- Open enums, so a newer server value turns one prompt into a read-only notice instead of breaking the whole list
- Keychain-backed auth, a schedule view grouped by day, and QR card scanning for client intake
Why the Contract Is a File
When two builders can't see each other's code, the only thing that keeps them in agreement is a document both of them are tested against. Writing it down first was slower for a day and faster every day after that.
Results & Impact
Lock-Screen Decisions
Booking requests are approved or declined in seconds, without opening the app.
Verified End to End
On 2026-09-21 the full loop ran for real: request, email, push, approve, confirmation.
Nothing Lost, Nothing Twice
Offline answers are retried with their original key, so they're never dropped or applied twice.
A Real Business
In daily use for my own clients. It isn't a demo.
What Doesn't Work Yet
Being honest about this is part of the design. Only bookings trigger a push today. QR scans and form submissions are recorded but silent, because how urgent each one is remains a design question. The web dashboard also doesn't update itself: an approval made on the phone shows up there only after a refresh. It's correct, just not live yet.
Lessons Learned
- Every piece working alone isn't the same as the system working. Each part had passed its tests for days. The first full end-to-end run was still the day that mattered.
- Assume push will fail. APNs drops, delays, and coalesces notifications routinely. Design so a missed push costs seconds, not a decision.
- Strict decoders break quietly. One unexpected value shouldn't blank the whole screen.
- The fastest way to learn a platform is to ship on it. Real stakes taught me more Swift than any tutorial would have.
See It Live
The client-facing side runs at concierge.lexgaines.com. There's more on how the app fits my work at lexgaines.com.
Need a Person in the Loop?
If your service needs fast human decisions, whether approvals, bookings, or escalations, I can build the API contract, the iPhone app, and the backend that ties them together.
Discuss Your Project