●MAX — Rork Max builds native Swift apps rather than React Native, spanning iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●APIS — It reaches native Apple APIs including SwiftUI, ARKit, HealthKit, HomeKit, Core ML, and Metal, so AR/LiDAR scanning and on-device ML are in scope●CLOUD — Rork Max writes Swift, compiles it on cloud-hosted Macs, and streams a live simulator that accepts real touch input●SUBMIT — From there it prepares the build for device install or App Store submission●SEED — Rork raised a $15M seed led by Left Lane Capital in April, joined by Peak XV and a16z Speedrun●PRICE — It's free to start at roughly 5 prompts a week, paid plans begin at $25/month, and Rork Max runs $200/month●MAX — Rork Max builds native Swift apps rather than React Native, spanning iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●APIS — It reaches native Apple APIs including SwiftUI, ARKit, HealthKit, HomeKit, Core ML, and Metal, so AR/LiDAR scanning and on-device ML are in scope●CLOUD — Rork Max writes Swift, compiles it on cloud-hosted Macs, and streams a live simulator that accepts real touch input●SUBMIT — From there it prepares the build for device install or App Store submission●SEED — Rork raised a $15M seed led by Left Lane Capital in April, joined by Peak XV and a16z Speedrun●PRICE — It's free to start at roughly 5 prompts a week, paid plans begin at $25/month, and Rork Max runs $200/month
Regenerable Zones in Rork Max Code: Keeping the Freedom to Rebuild
Generated code carries an invisible asset: the option to throw it away and rebuild it. Every hand edit quietly expires that option. Here is how I track it with a ledger and CI checks across six live apps.
At the end of June I changed one line in a wallpaper browser I had generated with Rork Max. Corner radius from 12 to 16. Not worth writing down.
The next week I wanted a filter on that screen, so I added a sentence to the prompt and regenerated it. The new screen worked fine. The corner radius was back to 12.
I lost 4 points. But I stopped there, because something else had happened. Until the week before, that screen had been a screen I could throw away and rebuild. The moment I touched one line, it became a screen that loses something when rebuilt — and nothing anywhere recorded that change.
When you run apps solo as an indie developer, this kind of quiet decay is the dangerous kind. This piece is about deliberately keeping — and deliberately giving up — the option that rides along with generated code.
Generated code ships with an option that expires
Code you write by hand is a maintenance obligation from the first keystroke. There is no option attached. You either fix it or delete it.
Output from a tool like Rork Max is different. A freshly generated file comes with the option to throw it away and rebuild. When the spec changes, instead of chasing a diff you rewrite the prompt and emit the whole thing again. That is not merely convenient — it is a right to buy your own hours back.
The option has a shelf life. Every hand edit quietly expires a little of it. The moment I changed 12 to 16, regenerating that screen became "an operation that loses 4 points." If someone later adds a VoiceOver label, regenerating becomes "an operation that loses accessibility work." Three months on, nobody knows whether the screen is still safe to rebuild — including the person who made the edit.
I run six wallpaper apps in parallel. The first thing that actually hurt after I started using generated code was not credit burn or output quality. It was not knowing which files still carried a live option. Without a ledger you default to hand-fixing when in doubt. Hand-fixing expires more of the option. Nobody stops the cycle, and the $200/month gradually turns into an empty promise.
Three states, not two
As long as you sort files into "generated" and "not generated," this decay is invisible. Split it three ways and it shows up.
State
Definition
How to treat it
regenerable
Untouched since generation. The ledger's prompt plus contract can produce an equivalent file
An asset. Protect the boundary
drifted
Hand-edited, but the delta is written down nowhere
A liability. Never leave it here
sealed
Deliberately promoted to hand-maintained. Removed from the prompt and from regeneration
Ordinary code. Guard it by hand
Drifted is the dangerous one. Regenerate it as if it were regenerable and something disappears silently. Hand-maintain it as if it were sealed and you never collect the benefit of generation. Neither assumption holds, which is the definition of a state you cannot act on.
The operating goal is simple: drive drifted toward zero. When you want to hand-edit, pick one of two paths.
Fold the change back into the prompt or the contract, keeping the file regenerable
Record the decision in the ledger, promote the file to sealed, and guard it by hand from now on
Those 4 points belonged in path one. A single line in the prompt — "corner radius 16" — would have settled it. I did not do that. I fixed my working copy only. I had not postponed a decision so much as failed to notice I was making one.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A working ledger that tracks every generated file as regenerable, drifted, or sealed — plus the mechanism that decides the state for you
✦A 40-line CI script that fails the build when a regenerable zone reaches into billing, persistence, or domain code
✦The 2 metrics from a quarterly regeneration drill, and how to judge whether $200/month for Rork Max is buying a real option or an empty one
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Trying to keep everything regenerable bloats the contract until regeneration becomes less stable, not more. Sealing everything reduces Rork Max to a one-time scaffolding tool. The line has to go somewhere.
Layer
Default state
Why
SwiftUI screen layout
regenerable
Changes often; breakage never reaches money or data
Display formatters
regenerable
Stable once the contract carries pre-formatted strings
Domain models and state machines
sealed
Wobble in regeneration surfaces as wobble in the spec
Persistence (SwiftData schema)
sealed
Break a migration and past users do not come back
Billing (StoreKit 2, receipt verification)
sealed
Never bet correctness about money on regeneration
Ads (AdMob init and consent)
sealed
Consent mistakes hit revenue and review at once
Navigation skeleton
regenerable, behind a contract
Drifts faster than anything else if left alone
When the call is close, three questions settle it.
If it breaks, does a user lose money or data? Then seal it. A 90% regeneration success rate is not a bet worth taking when the remaining 10% is a purchase receipt.
Does the spec change at least once a quarter? Then regenerable is worth paying for. Forcing a contract onto a screen you touch once a year costs more in contract maintenance than it returns.
Can correctness be checked automatically? If not, lean toward sealed. Tests are the only safety net regeneration has. A promise to "check it visually" survives about three weeks once you are running six apps.
Put only the types the generator may know at the boundary
Drawing a line does not hold it. The moment a regenerable zone touches a sealed type directly, the boundary is gone. So narrow what the generator is allowed to know.
The contract is hand-maintained. This part is sealed.
// rork-zone: sealed// The only type the generated side sees. Hand-maintained.import Foundationpublic struct WallpaperCardState: Equatable, Sendable { public let id: String public let title: String public let subtitle: String public let isDownloaded: Bool /// Pre-formatted for display. No Decimal, no Locale crosses this line. public let priceLabel: String? public init(id: String, title: String, subtitle: String, isDownloaded: Bool, priceLabel: String?) { self.id = id self.title = title self.subtitle = subtitle self.isDownloaded = isDownloaded self.priceLabel = priceLabel }}public enum WallpaperCardIntent: Sendable { case tapped(id: String) case saveRequested(id: String)}
The view in the regenerable zone knows those two types and nothing else, which is exactly why it can be thrown away.
Making priceLabel a pre-formatted String rather than a Decimal is the load-bearing decision here. Hand over a Decimal and the judgment about currency symbol placement and locale leaks to the generated side. Wherever judgment leaks, output wobbles on every regeneration.
The difference was clear enough to measure. Regenerating the same screen five times under each contract, the Decimal version placed the currency symbol incorrectly for the Japanese locale in 3 of 5 runs. The pre-formatted String version: 0 of 5. The thinner the contract, the less room the generator has to be wrong.
The closure on send follows the same logic. Name a ViewModel type there and the generator starts inferring its internals. Inference is where the next drift begins.
Catch drift with a mechanism, not with your eyes
A ledger maintained by human memory breaks in about two weeks. Use a header marker plus a hash captured at generation time.
#!/usr/bin/env python3"""zone_inventory.py — reconcile declared zones against what is on disk."""import hashlibimport jsonimport pathlibimport reimport sysLEDGER = pathlib.Path("tools/zone_ledger.json")ZONE_RE = re.compile(r"^//\s*rork-zone:\s*(regenerable|sealed)\s*$", re.MULTILINE)def digest(path: pathlib.Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest()[:16]def main(root: str = "Sources") -> int: ledger = json.loads(LEDGER.read_text()) if LEDGER.exists() else {} drifted, untagged = [], [] for path in sorted(pathlib.Path(root).rglob("*.swift")): text = path.read_text(encoding="utf-8") m = ZONE_RE.search(text) if not m: untagged.append(str(path)) continue if m.group(1) != "regenerable": continue key = str(path) if key in ledger and ledger[key] != digest(path): drifted.append(key) for p in untagged: print(f"untagged : {p}") for p in drifted: print(f"DRIFTED : {p}") print(f"\nuntagged={len(untagged)} drifted={len(drifted)}") return 1 if drifted else 0if __name__ == "__main__": sys.exit(main(*sys.argv[1:]))
Record the hash into the ledger right after generation, and any later change surfaces as drift. A file declared regenerable whose hash has moved is a file whose declaration and reality have parted ways.
The boundary itself gets checked too. Forty lines cover it.
#!/usr/bin/env python3"""zone_boundary.py — fail CI when a regenerable zone reaches across the line."""import pathlibimport reimport sysZONE_RE = re.compile(r"^//\s*rork-zone:\s*regenerable\s*$", re.MULTILINE)FORBIDDEN_IMPORTS = ("Persistence", "Billing", "DomainModel", "AdsCore")FORBIDDEN_SYMBOLS = ("ModelContext", "Transaction.currentEntitlements", "URLSession(")def violations(path: pathlib.Path) -> list[str]: text = path.read_text(encoding="utf-8") if not ZONE_RE.search(text): return [] found = [] for module in FORBIDDEN_IMPORTS: if re.search(rf"^import\s+{module}\s*$", text, re.MULTILINE): found.append(f"import {module}") for symbol in FORBIDDEN_SYMBOLS: if symbol in text: found.append(symbol) return founddef main(root: str = "Sources") -> int: failed = 0 for path in sorted(pathlib.Path(root).rglob("*.swift")): for v in violations(path): print(f"{path}: regenerable zone must not reference '{v}'") failed = 1 return failedif __name__ == "__main__": sys.exit(main(*sys.argv[1:]))
There is one trap worth naming. Regenerate through Rork Max and the header markers are almost certainly gone. The generator has no knowledge of those comments, so this is the expected behavior. Once a marker disappears the file drops out of the checks, and the ledger empties itself in silence.
I hit this twice. The first time it took three weeks to notice. The fix is dull: add one post-processing step that re-stamps the marker right after generation.
Right after generation, or right before commit. Those are the two places it fits, and the second one is the one you forget.
Once a quarter, actually throw something away
Ledger declarations drift toward wishful thinking if left alone. Four times a year I set aside time to test them for real.
Pick one screen declared regenerable
Cut a working branch and delete the file
Regenerate using only the prompt and contract recorded in the ledger
Record the diff, the credits consumed, and how many retries it took
If it does not pass within 3 retries, that screen is sealed in practice. Rewrite its ledger state
Two metrics, no more: regeneration success rate, and average retries to pass. Add a third and the drill stops happening.
The first time I ran this across six apps, 6 of the 11 screens I had declared regenerable passed within 3 retries. That is 55%. The other five had drifted without anyone noticing. As a number it is unflattering — but knowing the state of all 11 today is far safer than the period when I assumed all 11 were "probably fine."
Two cautions. Never run the drill on your production branch. Regeneration assumes you retry until it passes, so doing it somewhere history matters makes you pull your punches. And skip the week before an App Store submission. Discovering that a supposedly rebuildable screen cannot be rebuilt, while sitting in review anxiety, makes judgment sloppy.
$200/month buys an option, not maintenance
Rork Max costs $200/month. The free tier runs around five prompts a week, and paid plans start at $25/month (check Rork's site for current pricing).
Evaluate that spend by asking "what did I build with it this month" and you will want to cancel in any month you built nothing. I nearly talked myself into exactly that.
Change the frame and the axis changes. As long as you keep your regenerable zones intact, the payment is not a production cost — it buys the right to rebuild at any time. It is the price of an option. An option is worth the probability you can exercise it multiplied by what exercising saves, not the number of times you happened to use it.
My own estimate: rebuilding one screen by hand runs 4 to 6 hours. When regeneration passes, prompt tuning plus review lands near an hour. Even at a 55% success rate, if three screens a quarter genuinely need rebuilding, the expected value clears the price.
Which also tells you when to stop. Below a 20% success rate, the option is an empty promise. At that point most of the regenerable entries in your ledger are fiction, and it is more honest to drop to the $25/month plan and use it for initial scaffolding only, or move to a sealed-by-default operation. I put my line at 20%; the higher your hourly cost of hand work, the lower you can reasonably set it.
What matters is holding an actual number. Without one, the $200/month call gets made by mood, every month.
Where to start
Trying to build the complete ledger first is usually how the whole thing fails to begin.
Here is today's version. Pick one screen in your app that you expect to regenerate soon, and write the three marker lines at the top of that file. The ledger and the CI checks can come later. It starts with declaring to yourself that this file can still be rebuilt.
I did not notice this asset existed until I lost 4 points of corner radius to it. If you can pass the same lesson at a lower tuition than I paid, that would make the writing worthwhile. Thank you for reading.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.