posts

Idea to TestFlight in Three Weeks

Three weeks ago Tobi was an idea in my head. Yesterday it hit feature complete on TestFlight. Here's the AI workflow that got it there.

Three weeks ago, Tobi was an idea in my head. Yesterday, it became feature complete on TestFlight. Tobi is a shared household maintenance app: everyone in a home shares one view of what needs upkeep and a log of what was done, so the knowledge of how to care for the house doesn’t live in just one person’s head.

This post is about how it got built that fast: a workflow that leans on Claude, ChatGPT, and Greptile, each doing the thing it’s best at. I’m not claiming this is the way to build with AI. It’s the way that’s working for me, and pieces of it might be useful in yours.

Start with a PRD, not with code

Before writing any Swift, I sat down with Claude Opus on high reasoning and worked out a PRD. I knew what I wanted in general, and I used the conversation to force out the details. Here’s the prompt that started it:

Can you help me create a PRD for a house maintenance app in swiftui with a
backend that allows for people to share the household with others?

Here's the features I want:

* Panel layout like the image attached
* A view to list projects, who they were performed by and any notes
* Maintenance log
   * Item
   * task
   * How often
   * Last performed
   * Next due date
   * Manual links
   * Notes
   * Replacement links if needed like links to air filters when they
     need replacement

I would like to have notifications when maintenance is due like the day
it's due and then the day after and an option to choose when to remind
me again

I had to stop Claude pretty quickly. It started making product decisions for me, filling gaps with assumptions instead of flagging them. I told it to ask me before assuming anything, because I wanted to be the one designing the product. That one correction changed the whole conversation. Instead of getting a plausible-looking document full of choices I never made, I got questions, and answering them is where the actual design work happened.

Even then, the first PRD draft had open questions in it, so we worked through those until nothing was left unresolved. Once the PRD said what I meant, I asked Opus to break it into phases that made sense to build in order.

From phases to a Linear-driven loop

I took that PRD into Claude Code and worked through it one phase at a time. That was enough to get a working app, but over time the process tightened into a loop that runs through Linear:

  1. Capture. Every idea goes into Linear the moment it happens, straight from my phone, no AI involved.
  2. Define. Back at the computer, a Claude Code skill turns that rough capture into a fully specified ticket.
  3. Build. I tell Claude Code to work the ticket.
  4. Ship. The finished work goes up as a PR.

The skill in step two is where most of the leverage lives, so here are the exact rules it enforces:

That structure is what pushed Tobi from “usable by me” toward “usable by anyone.” The skill conversation is where I make decisions. The implementation run is where Claude executes them. When those two got blurred early on, I ended up reviewing code and product choices at the same time, and both suffered.

Never trust a single model’s spec

Between defining a ticket and building it, there’s one more step I’ve come to rely on: I don’t treat one model’s output as settled.

When I built Tobi’s feature flag service, the Linear skill produced a ticket that looked complete. I pasted it into ChatGPT anyway and asked it to evaluate the ticket and question any gaps.

It caught that the ticket quietly assumed a flag would already exist in PostHog before the service ever asked for it. In reality the service needed to accept any key or variant from day one, configured or not. It also pushed on a person property race the ticket had listed as a known issue, and working through it ended with the ticket redesigned so the race couldn’t happen at all.

The two models don’t share assumptions, so the second one catches what the first one silently baked in.

Review every PR, even as a team of one

ChatGPT checks the spec on the way in. On the way out, once the ticket becomes a PR, the review is Greptile’s job. Mid-project, Greptile was releasing a new model, so I gave it a try, since I wanted the whole workflow to be AI driven, including the part where someone checks my work.

Every review comes with confidence scores and a sequence diagram of what the change actually does. For the Firestore functions especially, that high-level view is worth a lot. Here’s the diagram it generated for my notification scheduler PR:

sequenceDiagram participant Scheduler as Scheduled Function participant FS as Firestore participant FCM as Cloud Messaging Scheduler->>FS: collectionGroup("tasks") where nextDue in window FS-->>Scheduler: task docs loop For each task Scheduler->>FS: "households/{id}/members (householdMemberIds)" FS-->>Scheduler: current member ids (Set) alt assigneeUserId set AND is current member Scheduler->>FCM: send to [assigneeUserId] else assigneeUserId set BUT not a member Scheduler-->>Scheduler: return [] — no notification sent else no assignee Scheduler->>FCM: send to all current members end end Scheduler->>FS: "collectionGroup("reminders") where type=snooze, fireDate today" FS-->>Scheduler: snooze docs loop For each snooze Scheduler->>FS: "households/{id}/members (householdMemberIds)" FS-->>Scheduler: current member ids (Set) Scheduler->>Scheduler: filter recipientUserIds ∩ currentMembers alt filtered recipients non-empty Scheduler->>FCM: send to filtered recipients end Scheduler->>FS: delete snooze doc (always consumed) end

It catches real bugs, too. On one PR it flagged a P1 in my subscription reconciliation:

API failure incorrectly deactivates active subscriptions

revenueCatEntitlementActive returns false for both “subscriber has no entitlement” and “network/API error”. reconcileUserSubscription writes that false unconditionally to users/{uid}.subscriptionActive and then fans out to every owned household. A transient RevenueCat outage silently deactivates a subscriber’s household access until the next successful reconciliation.

That’s not linting noise. That’s a paying subscriber losing access to their household because RevenueCat had a slow morning, and I would not have caught it reading the diff myself. Greptile is a paid service, so the deal is that Tobi’s revenue has to cover it. If it can, Greptile stays in my toolbox, especially once Tobi starts expanding to other platforms.

Docs as part of the work, not after it

Everything above follows one ticket from capture to merged PR. Two habits run alongside that loop, and the first is documentation. Claude Code writes docs as part of the work itself, never as a cleanup task afterward.

The doc that matters most describes the Firestore data models. Those same models have to exist in the Cloud Functions and in iOS, and eventually on other platforms too, so there has to be one written description that every implementation answers to.

The rest cover the day-to-day. The analytics doc means I always know exactly what’s being measured without spelunking through code. An App Store Optimization (ASO) doc keeps store copy easy to produce whenever it’s needed. And when I settle on a pattern I want followed, that becomes a doc as well, so future Claude Code sessions can reference the convention instead of reinventing it.

None of these docs are for some hypothetical future team. They pay off now, because every new Claude Code session starts from written-down truth instead of re-deriving it.

Use the better model for the creative work

The second habit is handing creative output to ChatGPT, which I think is simply better at it. The app icon on TestFlight right now came from one sentence about what I wanted plus the app’s color scheme, first try. It wrote the App Store copy too, which the ASO doc makes quick to put together.

What didn’t go smoothly

Three weeks also included the failures, and the AI workflow caused some of them.

The biggest one got its own post: the emails my app swore it sent. An AI assistant confidently told me that verifying a root domain in Resend covers its subdomains. It doesn’t, and because my email logging recorded attempts instead of outcomes, that wrong answer hid for weeks inside a log that said everything was fine.

The other time sink was the paywall. My monetization model is a little unusual: only the owner of a household needs a subscription, so I can’t gate access on RevenueCat entitlements alone. Whether you get in depends on RevenueCat and on Firestore entries about your household.

Those two load at different speeds, so I put up about four different PRs circling around how and when the paywall shows. Fix the entitlement path, and the Firestore path lags and flashes the paywall at people who should never see it. Fix that, and something else regresses.

The eventual answer was boring: one loading flag, and nothing renders until both RevenueCat and Firestore have loaded.

No model was going to hand me that on the first try, because the problem wasn’t in any single PR. It was in my design splitting the truth across two systems. AI makes the write-code part fast, and that speed made it very tempting to keep patching instead of stopping to notice the architecture underneath.

The workflow from a high level

Here’s the whole thing in one view:

You don’t have to adopt all of this at once. If I were adding one piece to an existing project, I’d start with ticket definition and continuous documentation: one settles the decisions before any code exists, and the other keeps those decisions from getting lost between sessions.

And if a shared view of your home’s maintenance sounds like something your household needs, Tobi is feature complete and on TestFlight. Sign up at gettobi.com.

Apr 15, 2026 aiworkflowjaba
How I Automated JABA's Developer Update Posts
I built a GitHub Actions pipeline that collects merged PRs across three repos, generates a developer update post with Claude, validates the output, and opens a PR for my review every two weeks.
Oct 16, 2025 ship-a-tonswiftcamp notes
Camp Notes's First Paywall Experiment
Starting my first paywalll experiment for Camp Notes.
Oct 13, 2025 ship-a-tonswiftcamp notes
Camp Notes Won a OneSignal Boost Award at Shipaton
Camp Notes placed 5th in the OneSignal Boost Award category at RevenueCat's Shipaton hackathon. This post covers what Shipaton is, why I entered, how I implemented OneSignal for notifications and emails, and what I learned from competing against over 800 entries.