Between late April and the end of June, I designed, built, and published 11 apps and games to the Apple App Store and Google Play. They are small, self-contained games and utilities, all cross-platform Flutter, all built with AI coding agents. My AI tooling cost about $40 a month: two $20 plans, Claude and Codex, on top of the one-time store fees every developer pays.
That is the headline, and headlines about AI and speed are cheap. So let me be precise about the part that actually matters. The speed did not come from typing prompts and hoping. It came from treating the agents like fast, tireless engineers who still need a spec, a plan, a review, and tests. “Vibe coding” has come to mean the opposite of that: describe a feeling, accept whatever comes back. I want to reclaim the term. In this post, vibe coding means spec-first, test-gated development where an AI agent does the typing and you own the engineering.
Here is exactly how I did it, including what went wrong and what I changed.
TL;DR – SmtC
Too Long; Didn’t Read – Show me the Code: github.com/iseif/flutter-app-planner
All 11 apps and games: iseif.dev/apps
The experiment: three $20 agents, one game
The first app, Math Duel, started as a controlled comparison. My kids kept asking me for mental-math questions on long drives, so I decided to build them a two-player math game, and at the same time to answer a question I actually cared about: on the entry-level $20 plans, how do the coding agents really compare?
I gave all three the same starting point:
- I wrote a detailed PRD with Claude Opus.
- I turned the PRD into a visual design using both Claude Design and Google Stitch, then picked the better of the two. (Stitch was slightly more polished here.)
- I asked Opus to break the design and PRD into detailed tasks and subtasks, where each task named the files to create and edit and shipped as a ready-to-paste prompt.
Then I ran the same task prompts through Claude Code (Sonnet 4.6), Codex (GPT-5.5, medium), and Gemini CLI (Gemini 3, auto), with the same MCP servers, the same skills, and a /clear between tasks so every task started from a clean context.
The code quality was comparable. All three completed the tasks and held to the design. Each made mistakes (bad assumptions, small bugs), and each fixed them when I fed back a stack trace or a screenshot. The one sharp difference was quota. After a handful of tasks, Claude Code had burned through its five-hour window while Codex and Gemini had barely moved. At the time, my honest conclusion was simple: if $20 of Codex or Gemini gets me the same result, why pay for the $100 to $200 tier?
Then the ground shifted. On May 6, 2026, Anthropic doubled Claude Code’s five-hour rate limits (tied to a new compute deal with SpaceX) and removed the peak-hours reduction on Pro and Max. Overnight, Claude Code went from the most quota-constrained option to one I could lean on all day. Around the same time, Gemini dropped out of my rotation. Its CLI quota had looked generous in the bake-off, but when I tried to lean on it for sustained, day-over-day work it kept failing in ways that stalled real progress: frequent 429 Too Many Requests errors and the model is overloaded responses, and on top of that its output lagged Claude and Codex. The friction was no longer worth it. From the second app onward, I settled on two agents: Claude and Codex.
The workflow that stuck
Every app after Math Duel followed the same pipeline. It is the whole method, so I will be concrete:
- Masterplan (Claude Opus, high effort). One document describing the product: screens, modes, monetization, tech choices, and a phased roadmap.
- Design prompt (Opus). Opus turns the masterplan into a single paste-ready prompt with design tokens and every screen. I run it through Claude Design and Google Stitch and keep the stronger result.
- Phases and subtasks (Opus). The masterplan becomes an ordered set of phases, each broken into tasks. Every task is a self-contained prompt block that names exact files, classes, and method signatures, and ends with
flutter analyze+dart format .. - Implementation (Sonnet, then Codex). I hand tasks to Claude Sonnet. When the five-hour limit hits, I move the same task to Codex and keep going. Core game logic, the parts where a subtle bug is expensive, I gave to Opus or GPT-5.5 on high effort.
One practical detail shaped the handoff. When Codex hits its limit mid-task, it tends to finish the task rather than stop on a half-written file. Claude Code hard-stops. So for a large task near a reset boundary, Codex was the safer place to start. Other times I simply waited four hours for the window to reset before starting the next phase.
To make this concrete, here is a lightly trimmed version of one real task: the deterministic simulation at the heart of Gem Digger, my Boulder-Dash-style digger. This is close to what the agent actually received:
## Task 2.2: Deterministic tick, gravity & roll (unit-tested)
Files: lib/game/cave/cave_simulation.dart, test/cave_simulation_test.dart
Dependencies: Task 2.1 (cave data model)
Implement the deterministic simulation. This is the single most important file
in the app. Pure Dart, no Flame/Flutter imports, fully unit-tested.
class CaveSimulation:
CaveSimulation(this.cave);
void tick(); // advance ONE logical step; deterministic; no randomness, no clock.
tick() algorithm (scan order matters):
Scan cells BOTTOM-to-TOP, left-to-right. For each fallable object (boulder/gem):
1) FALL if the cell below is empty; if it lands on the player while falling,
mark the player crushed; otherwise it comes to rest.
2) else ROLL off a rounded object underneath: try LEFT first, then RIGHT.
Deterministic tie-break: LEFT before RIGHT, always. No RNG anywhere.
test/cave_simulation_test.dart, cover at least:
- a boulder falls one cell per tick until it rests on a wall.
- a boulder resting on a boulder with both sides open rolls LEFT.
- determinism: two identical caves ticked N times produce equal grids.
Run `flutter analyze` + `dart format .`.
Done when:
- the documented fall / roll / determinism tests pass.
- files have zero Flame/Flutter imports; `flutter analyze` is clean.
Notice what the agent is not asked to do: invent the architecture. The files, the class, the method signature, the tie-break rule, and the tests it must pass are all decided before a line is written. The agent’s job is to type a correct implementation of a spec I can verify, and the Done when block is how I verify it.
The part vibe coding skips: quality
This is the part that separates a shipped app from a good demo, and where being an engineer pays off.
Every task ended in a gate. The task template forces an objective “Done when” checklist and a clean flutter analyze before the next phase can begin. Tests were not an afterthought bolted on at the end; they were part of the definition of done: targeted unit tests where they remove ambiguity, plus regression tests for known failure modes, rather than a vanity coverage number.
I used the two agents to check each other. I would have Claude review a Codex implementation, or GPT-5.5 test something Claude wrote. Two independent models reading the same diff catch different things. For performance work, I let both profile the problem, then handed each one the other’s findings and asked them to converge on the best fix. When both got stuck, I stopped delegating and fixed the code myself. Knowing when to step in is part of the job.
The clearest example came from Updraft, a one-touch cave-flyer. Codex implemented the core game loop and the sound effects. It worked, and then my phone started getting hot and the game slowly ground to a crawl. Claude found the cause: the audio code was allocating native audio players and never releasing them. On Flutter, nulling a Dart reference or calling stop() does not free a native player; a low-latency one-shot or a looped sound tied to a frequent event leaks a handle every time it fires, and the accumulation burns CPU until the device overheats and later screens hang on a black frame.
I fixed Updraft. But the more valuable output was that I turned the lesson into a rule, which is what the next section is really about.
From repetition to a reusable skill
By the eighth app I had run the same pipeline enough times to see its sharp edges: the audio-leak class of bug, ad and in-app-purchase wiring that broke in the same ways, canonical facts like the package id or product ids drifting between documents. So I extracted the whole process into a Claude Code and Codex plugin: flutter-app-planner.
You describe one idea, and the skill interviews you and then generates a full plan bundle, one approval-gated file at a time:
- a masterplan,
- a design-tool prompt,
- phase-by-phase task files, each task a copy-paste prompt with a “Done when” gate,
- per-phase kickoff prompts, and
- a start-here checklist: store setup, every in-app purchase defined field-by-field, and a generation prompt for every image and sound asset.
Two decisions make it hold up under real agents. It is approval-gated and resumable: each artifact is written to disk immediately, so when a session hits a token or five-hour limit mid-bundle, I re-invoke it and it resumes from the first missing file. And canonical facts are locked once and quoted verbatim across every file, so the monetization phase and the store checklist can’t disagree about a product id.
The Updraft leak lives in the skill now, as performance-patterns.md: pool sounds that fire often, hand-dispose one-shots, .dispose() every player from a single audio owner wired to the widget lifecycle, add a disposal regression test, and validate on a real device, because flutter test disables the native audio path the bug actually lives on. A mistake became a codified, tested contract that every future app inherits.
I built the last three titles (Cozy Sokoban, Gem Digger, and Cadence) with the skill, and they carry the most tests of anything I shipped: 45, 50, and 33 respectively, against a handful in the earliest apps. The raw number is not the point. I am not chasing a coverage percentage. What changed is the kind of test: regression tests that pin down a specific past failure, like the audio-disposal check that fails the instant someone reintroduces the Updraft leak, or the determinism test in the Gem Digger block above. The process did not just get faster; it got more disciplined, and those tests are where the discipline lives.
Shipping is its own project
Writing the app is half the work. Getting it live is the other half.
I chose Flutter specifically so one codebase compiles to both iOS and Android. Only two apps needed platform-specific code, Til. and Cadence, for home-screen widgets and cloud sync.
Across all 11 submissions I had zero rejections on either store. That was helped, honestly, by the fact that these are small, offline apps with a limited review surface. Apple’s review was consistently fast. The friction was on Google’s side: Play now requires 12 testers across 14 days of closed testing before you can promote a build to production. Because that 14-day clock runs per app, I kept several apps in closed testing at once rather than shipping them strictly one after another. That overlap is how 11 titles fit into two months. For the first two apps I hired testers on Fiverr; for the rest I used TestersCommunity, which was cheaper and smoother.
For assets I stayed on free tiers: ElevenLabs for sound effects, and ChatGPT’s image generator plus Google’s Nano Banana for characters, backgrounds, and even store screenshots.
What $40 a month actually bought
Here is the full bill:
| Item | Cost |
|---|---|
| Claude (Pro) | $20 / month |
| Codex (ChatGPT Plus) | $20 / month |
| Apple Developer Program | $99 / year |
| Google Play Developer | $25 (one-time) |
| Play Store closed-testing (Fiverr, then TestersCommunity) | small one-off, per app |
| ElevenLabs, image generation, design tools | Free tier |
To be exact about the headline: the recurring cost was $40 a month for the two AI agents. Everything else was either a fee any mobile developer pays ($99/year for Apple, $25 once for Google) or free: I paid a little to line up Play Store testers, and generated every sound and image on free tiers. Two $20 agents were enough to design, build, test, and ship 11 apps; I never needed a $100 or $200 plan. The real constraint was not money but patience: occasionally waiting four hours for a usage window to reset before the next phase.
If there is one thing to take from this, it is that AI did not replace the engineering. It compressed the typing. The judgment that made these apps shippable (a spec before code, tasks small enough to verify, tests on the parts that matter, two models checking each other, and a mistake turned into a permanent rule) is exactly the judgment I would bring to any codebase. That is what I mean by vibe coding: not less rigor, just less friction between a plan and a running app.
The skill is open source, and you can play everything I built:
- The skill: github.com/iseif/flutter-app-planner
- All 11 apps and games: iseif.dev/apps

