loading...
This was my first project as a builder rather than a writer of prompts alone. I had a nagging groin/glute injury that needed specific rehab, a general fitness goal on top of it, and a habit of getting stuck doing the same exercises and stretches on repeat. I looked for an existing app that would track my progress, steer me toward exercises that respected the injury, and keep enough variety that I’d actually stick with it. Nothing quite fit, so I built it.
Before opening Claude, I mapped the app out, on paper, starting with the daily flow, what a "session" actually consists of, what needed to sync across devices, and potentail rules and option trees. That brainstorm became a written project brief, which became the first prompts, setting up a back-and-forth planning dialogue with Claude.
The first real version was the daily loop: Morning Stretch, Cardio, Gym, Evening Stretch, plus a Progress view and an exercise library. The interesting decisions weren't the screens themselves, they were the rules underneath: what counts as a "complete" day, how a streak breaks, and how gym exercises get picked so the injury never gets aggravated and each session targets the correct muscles, in the correct order, and with variety.
The initial build existed only as an ephemeral browser session: no persistent storage, no user accounts, and no state once the tab closed. The next milestone was making the app survive beyond a single session, persisting across devices so it would follow me between phone and laptop. That required introducing a genuine backend, including authentication and cloud-based data storage.
Since I had limited prior experience with backend architecture, authentication systems, or cloud databases, I approached this phase as a guided learning process, working alongside Claude as a tutor who explained not just the implementation steps but the reasoning behind each decision. Concretely, this meant hosting the app as a page within my personal website and integrating Firebase to handle authentication and cloud data storage.
This stage was where the real engineering work happened. Some of the bugs encountered were genuinely serious, including several that cost significant data.
As the app grew in complexity, it became essential to diagnose issues methodically, working through hypotheses and root causes collaboratively with Claude rather than applying surface-level fixes. This process included prompting Claude to hardcode starter data and build functionality for editing, adding, downloading, and uploading data.
Notable issues included:
The lasting solution wasn't patching each bug individually. It was introducing a rolling automatic backup as a genuine safety net, ensuring that a future bug could never again cause unrecoverable damage.
Once the app was functionally complete, the interface remained purely utilitarian: dark cards with no visual hierarchy. Design isn't a strength of mine, so this stage was deliberately structured as a collaborative process rather than either dictating a rigid specification or ceding creative control entirely to Claude. That balance is the core discipline of effective vibe coding: providing enough direction and taste to keep the output aligned with intent, while leaving enough room for genuine collaborative iteration.
I began by gathering reference material: screenshots of apps, individual UI components, and colour palettes that reflected the direction I wanted. Rather than moving straight into code, the redesign process started with mockups, allowing a visual direction to be evaluated and refined before any implementation commitment was made.
I also set myself one governing constraint early on and held to it throughout the redesign: green is reserved exclusively for signaling completion, never used as a general category colour. It's a small rule, but it's the detail that separates a coherent, meaningful colour system from a palette that simply looks pleasant.
By this stage the app was fully functional and stable, with several days of consistent, bug-free use behind it. Through actual daily use, a gap became apparent: steps, a core fitness metric I was tracking against a 10,000-plus daily goal, was missing entirely. I had been relying on a separate app to measure it and wanted that data pulled directly into my own tool instead of tracked in parallel.
I raised this with Claude as a goal rather than a specification. It confirmed the idea was feasible but noted that direct integration with Apple's Health app wasn't possible. When I mentioned that I personally used Pacer to track steps, a deeper search turned up something useful: Pacer exposes a developer API. That opened a real path forward, but it also introduced a genuine technical obstacle: the browser couldn't call Pacer's API directly due to CORS restrictions.
The goal at that point wasn't to route around the restriction with a partial workaround, but to solve it properly. That meant building a small server-side proxy to make the request on the app's behalf, sitting between the browser and Pacer's API. Working through this with Claude, testing possible solutions live as we went, we arrived at a working integration that pulls steps data directly from Pacer into the app, completing the final feature and, with it, the build.
The biggest shift wasn't learning to prompt. It was learning to direct.
Specificity was the recurring lesson across every stage of this build. A vague bug report got a vague guess back. An exact console output, an exact reproduction sequence, and a clear description of expected versus actual behavior got a real diagnosis and a real fix, the difference visible directly in how the timezone bug and the Firestore write failure were actually resolved rather than papered over.
The same principle held for testing. Deliberately testing across devices, at odd hours, and against intentionally rough edge cases surfaced the bugs that actually mattered, including the race conditions that put real data at risk, rather than only the bugs that were convenient to stumble across in normal use. Passive testing finds what's in front of you. Deliberate testing finds what's actually broken.
Design worked the same way. Establishing a visual direction through mockups and reference material before any code was written prevented exactly the kind of costly rework that comes from building against a specification that was never actually agreed on. Locking a rule like green meaning "complete," and nothing else, before implementation began is the same discipline as a tight creative brief: it's cheaper to fix a direction on paper than to unwind it in a finished product.
This is the throughline back to the writing and creative-management side of my background. The skill underneath this entire project was never really about knowing syntax. It was about asking good questions, being precise about what "done" actually means before starting, and knowing when a first answer, even a working one, wasn't the right one, and needed to be pushed on rather than accepted.
Building this app didn't require becoming an engineer. It required treating an AI collaborator the way I'd treat any capable creative partner: with clear intent, honest feedback, and the judgment to know the difference between a solution and a shortcut.