I shipped a native app on both stores, in five languages, on my own, with a general-purpose AI as my developer. Between my first prompt and the first screen that did what I wanted, there were six versions of a "master prompt". This episode is about those versions, and about why the fix I needed wasn't in any of them.
In the first episode I told how the AI took my first line literally: I wrote "share your position", and it gave me a map of friends. This is what happened next.
The master prompt
Every chat with an AI starts from zero. So from the first week I kept one document that I pasted at the start of every new session: what the app is, who it's for, how it works, which decisions are already made. I called it the master prompt.
It changed nature faster than I expected:
- January 26: vision, user flow, colors. Basically my first prompt plus the AI's answer, cleaned up. One line in capitals that I still like: "remove the word 'invite' completely".
- February 13: a technical spec. Tables, pages, triggers.
- February 18: the same spec rewritten in FlutterFlow's own vocabulary: page state, actions, conditional visibility.
- The same day, seven minutes apart: a new version "synced with what I actually built in the editor".
That last one was the real change. The master prompt stopped being a description of what I wanted and became a photo of what existed. An AI that reads a wish will build the wish. An AI that reads a photo will start from where you are.
By spring I had two branches of the document, numbered in a way that makes no sense today (a v4.0 in March, then a v3.2 and v3.3 in April and June). More on that in episode 5.
The spec that ran every ten seconds
In late January the AI generated two files for me and called them FlutterFlow exports. The simple one had exactly the five screens it had recommended. The "advanced" one had this:
"actions": {
"update_live_location": "runs every 10 seconds",
"share_location_event": "writes to locationShares",
"join_event": "adds user to events.participants"
}
Plus friends' pins on the map, a live location field on every user, and a collection to share your position with an expiry.
Three problems, in increasing order of importance:
- Battery. A position update every ten seconds is the fastest way to get uninstalled.
- Stores. Both stores are strict about location in the background as a main feature. The app would have had a hard time getting approved.
- It was the app I didn't want. People on the map, not events.
And a fourth one, smaller and funnier: the file couldn't be imported in my version of FlutterFlow, and in the same configuration it mixed a Mapbox style address into a MapLibre map. A spec that looked perfectly plausible and couldn't run anywhere.
I also tried FlutterFlow's own AI
In February I tried FlutterFlow's built-in page generator for the first screens. One of my prompts started with:
Create a map UI like Zenly.
Zenly was the best-known app for seeing your friends on a map. So I was asking a second AI to build, again, the version of my idea I didn't want. The generator was quick for empty layouts. I didn't like what it gave me, so I built the pages myself in the editor. The logic code came from the general-purpose AI, and FlutterFlow compiled it.
The fix was one line about data
Rewriting the pitch didn't change the direction. Every new answer still leaned on "position". What changed it was a rule in the data model.
On February 13 a line appeared in the spec: no position saved outside an active event. On February 18 the users table no longer had any location field at all. A position existed only as the place of an event, and the event had an expiry date. The cloud database became, in the words of that spec, a bulletin board for pins on a map.
After that, the AI could not drift back even if it wanted to. There was simply nowhere to store where a person is. You can argue with an AI about intent for weeks. A missing field ends the argument.
The revision loop that came later
The real cycles of revision started in April, with a working app and a second AI reviewing the code. In one week:
- April 22: first audit;
- April 23: nineteen fixes;
- April 24: second audit, 23 problems;
- April 26-27: final audit before release, 31 bugs;
- April 30: verification. Eleven of the thirteen "pending" fixes were already in the code.
The documentation was behind the code, not the other way around. From then on the rule was: when the code and the document disagree, the code wins. And one more, learned the hard way: a fix declared "applied" must be checked against the previous export, because one of them had introduced a new bug.
To keep the document up to date I ended up with a very short prompt, which I still use (translated from Italian):
Scan <chat> and <audit>, extract every fix and decision.
Output: ONLY a diff 🟢/🟡/🔴 per section. No preamble. Nothing unchanged.
Priority: audit > chat > document.
What I took from it
- Revisions improve the form of a spec. They rarely fix its intent.
- If your idea goes against the common version, put it in the data model, not only in the description. A rule on data is the one thing the AI can't reinterpret.
- Keep your main prompt as a photo of what exists, not a list of what you want.
- When the code and the document disagree, believe the code. Then fix the document.
- A generated spec has to be read line by line. "Every 10 seconds" was right there.
Next episode: the first sketch. Colors, a font chosen on day one, a logo I threw away, and five languages that turned out to be the hardest part.
I'm building orbiTome solo, with FlutterFlow and Firebase. More at orbitome.com.
Top comments (0)