Shipping an App Store App with Full Vibe Coding: The LevelUpStudy Story
"Vibe coding" is the term of the year: instead of typing every line, you describe what you want, the model writes it, and you judge the result. The obvious question is whether that produces an app that actually ships on the App Store.
It does. LevelUpStudy was built exactly that way: a SwiftUI interface, a 3D study desk generated with SceneKit, a PHP + MySQL API, Firebase authentication, and a released build on the App Store. But the sentence "just tell the AI and it builds it" hides a lot of detail. This article covers the method that worked, the three concrete walls I ran into, and what I would tell anyone taking the same route.
What vibe coding is not
Let me set expectations first. Vibe coding:
- Does not make architectural decisions for you. If you ask the model where data should live, who should be trusted, or when a "day" ends, the answer will look reasonable and be inconsistent across the codebase.
- Does not audit itself. The model will not point out the duplication, the dead function, or the silent breakage in the code it just produced. If you do not ask, you will not see it.
- Does not make product decisions. "Should break minutes earn points?" is a product question; both answers are equally implementable.
What vibe coding genuinely accelerates is the implementation that follows a decision: building screens, defining models, writing endpoints, extending tests, refactoring. That is the bulk of the work in any app, so the gain is real.
What LevelUpStudy is
In one sentence: a social app that measures how long a student actually studies, turns those minutes into points, and lets them spend the points on a 3D study desk. In numbers:
- 94 Swift files, all SwiftUI
- A 3D desk scene generated from geometry in code — no imported model files
- My own PHP API and MySQL schema; Firebase only for auth, notifications, analytics and crash reports
- 39 unit tests for pure logic and 24 integration tests running against a real database
- 12 exam types and 40 achievement badges
I shipped an app of that size alone, working evenings. Here is how.
The method that worked: documents before code
The habit that made the biggest difference was writing decisions down before writing code. The project keeps two documents:
ARCHITECTURE.md— how the system works, where the trust boundary is, which value comes from where.NEW_VERSION.md— the phases of the next release: which file, which column, which endpoint.
Those documents double as a contract for the AI. Instead of "write the session completion endpoint", you can say "write session completion following the trust boundary rule: duration is the difference between two server timestamps, and the client-reported value is only an upper bound". The odds of getting correct code go up dramatically.
The single most important rule of the app came out of that document:
No value that drives the game economy ever comes from the client.
In practice:
duration = min(server delta, planned duration, client-reported duration)A client cannot claim "I studied for three hours"; it can only report finishing early. That one line is what keeps the leaderboard meaningful.
Wall 1: a time zone that burned everyone's streak
In the first architecture, a nightly job reset the streak of everyone who had not studied "yesterday". The job fired at 00:00 Istanbul time, but the server ran on UTC, so the day the server called "yesterday" was two days ago for the user. Every morning, users with a perfect record woke up to a broken streak.
The AI did not invent that bug: it appeared because I never defined what a "day" was. The fix was to define it in exactly one place: a day is always Europe/Istanbul. AppConfig.appTimeZone on the client, Domain\Time on the server. Calendar.current, date('Y-m-d') and new Date().setHours(0,0,0,0) are banned across the project — all three depend on the local clock of whatever machine runs them.
Lesson: to ask a model for "correct" code, you first have to write down what correct means.
Wall 2: 800 lines of dead code
The file that builds the 3D objects had grown to 2,615 lines. Reading it, I found eight functions defined twice: the old versions took no parameters, the new ones took itemName, and the call site only ever used the new set. It was the residue of a half-finished refactor — roughly 800 lines of code that nothing could reach.
This is the most typical side effect of vibe coding. Rather than updating a function, the model writes a new one and the old one stays. Swift does not warn about an uncalled private function, so the compiler stays quiet too.
Lesson: regularly ask "is there unreachable code in this file?" Before deleting, verify each call site once more with grep; a passing build is not proof on its own.
Wall 3: matching on display names
The 3D model for each desk item was chosen by looking at the item's Turkish display name:
if itemName.contains("Kalem Kutusu") { ... }
else if itemName.contains("Kalem") { ... } // order-dependent!It worked, and it had three problems: a new item added to the database showed up as a grey box in the app (so every new item meant an App Store update), renaming an item silently broke its model, and the moment I added English the whole matching would collapse.
The fix was to move matching from the display name to a stable key: a model_key column on shop_items and a switch modelKey on the client. Catalog experiments — price, level lock, seasonal items — can now be done from the server alone.
Lesson: AI makes what you asked for work by the shortest path. Asking about the gap between "works" and "maintainable" is your job.
Getting onto the App Store
Finishing the code does not finish the work. The release side took real time:
- Screenshots. 6.5" iPhone and iPad sizes are mandatory. Taking the raw simulator captures and adding a short headline on top has a visible effect on conversion.
- Privacy nutrition labels. In App Store Connect you declare, item by item, what you collect and whether it is linked to identity. It must match the app's real behaviour; a mismatch here is a rejection reason.
- A privacy policy and a support URL. Both have to be reachable URLs. That is why LevelUpStudy's privacy policy and support page live on my own site: a permanent address reads as far more trustworthy than a shared document link, both during review and to users.
- Age rating. Because the app has social features and user content, it is rated 13+.
- Account deletion. An app that creates accounts must let users delete them from inside the app. Plan for it up front instead of bolting it on later.
Most review problems come from these items disagreeing with the app. The code itself is rarely the issue.
A checklist for anyone taking this route
- Make the architectural decisions yourself, write them down, then hand that document to the model.
- Keep tasks small: not "build the session flow", but "write the session completion endpoint in one transaction so the same session can never be completed twice".
- Pin the correctness of money, points, duration and identity to tests. Those few tests are what stop the model from breaking the same spot in the next change.
- After each phase, ask: unreachable code, duplicated functions, magic strings?
- Put the release requirements — privacy, support URL, account deletion, screenshot sizes — at the start of development, not at the end.
Developing with AI does not make a good developer unnecessary. It changes how much one good developer can ship. Someone still has to make the decisions, set the boundaries and check the result — and that someone is you.
Frequently Asked Questions
Can you really ship an app from scratch with vibe coding?
Yes. LevelUpStudy is one example: a SwiftUI interface, a 3D scene, its own API and tests, live on the App Store. But architectural decisions, security boundaries and product calls still have to come from a human; the model accelerates carrying them out.
How do you check the quality of AI-written code?
Look at three things: unreachable or duplicated code, matching that relies on magic strings, and test coverage of the critical calculations. Isolating values like duration, points and identity into pure functions guarded by tests is the habit that pays off most.
What trips apps up in App Store review?
Declarations that disagree with the app, far more often than the code: privacy labels that do not match real behaviour, a privacy policy or support link that does not load, or an account-creating app with no in-app account deletion.
Firebase or my own server?
It can be both. In LevelUpStudy, authentication, notifications, analytics and crash reporting are on Firebase, while data, the game economy and scheduled jobs run on my own PHP + MySQL API. That keeps me on free tiers while letting aggregate queries such as leaderboards run as a single SQL statement.
How long did it take to ship?
A few months of evenings, from idea to the first App Store release. What stretched the timeline was not writing code but release preparation: screenshots, privacy declarations, the account deletion flow and the review cycle.
Related Posts
Introduction to Swift: Optionals, Guard Let and If Let Usage
After Flutter, I started learning Swift for iOS development. Sharing Optionals and safe unwrapping methods, one of the first concepts I encountered.
How to Find the Best Swift Teacher? 2026 Guide
How to choose the best Swift teacher for iOS development: what an experienced expert adds and how to find the right instructor.
What Is LevelUpStudy? A Study Tracker App for Exam Preparation
Do you actually know how many hours you study a day? Here is how LevelUpStudy turns exam preparation into something measurable with focus sessions, streaks, leaderboards and a 3D study desk.