●PUBLISH — Rork Max handles code signing and provisioning so you can submit to the App Store in two clicks●SIMULATOR — A browser streaming simulator feels close to real hardware, and a QR code installs to your device without TestFlight●MAX — Rork Max generates native Swift apps, powered by Claude Code and Claude Opus 4.6●NATIVE — Rork Max reaches Xcode-class territory: AR/LiDAR, Metal 3D, widgets, Live Activities, HealthKit, and Core ML●FUNDING — Rork raised $15M to power the next generation of App Store entrepreneurs●PRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/month●PUBLISH — Rork Max handles code signing and provisioning so you can submit to the App Store in two clicks●SIMULATOR — A browser streaming simulator feels close to real hardware, and a QR code installs to your device without TestFlight●MAX — Rork Max generates native Swift apps, powered by Claude Code and Claude Opus 4.6●NATIVE — Rork Max reaches Xcode-class territory: AR/LiDAR, Metal 3D, widgets, Live Activities, HealthKit, and Core ML●FUNDING — Rork raised $15M to power the next generation of App Store entrepreneurs●PRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/month
Which Generation Introduced This Bug? Building Provenance Into Rork Apps
When you regenerate the same screen again and again with an AI builder, a bug that shows up one day becomes impossible to trace to a specific generation. This is a design for keeping generation provenance across three layers—build stamp, ledger, and telemetry—so you can narrow a regression down to a single generation in minutes.
Last month a crash I had already fixed in one of my apps came back weeks later with the exact same symptom. The code looked untouched—the fix was still there. It took me half a day to see what happened: when I regenerated a different screen, that generation had quietly rolled the old implementation back with it.
As an indie developer running several apps on my own, this kind of silent rollback is not rare. Rork Max's two-click submission has lowered the bar for shipping, and because of that we regenerate screens more casually than we used to. The more generations pile up, the more the answer to "when, in which generation, did what change?" slips out of reach.
Stamp provenance onto what you generate, and when a bug appears you can narrow down the suspect generation in minutes. No special infrastructure is required. A single build-time constant and one text ledger are the starting point. Here is the design I use in day-to-day operation.
Why git blame Can't Follow This
With hand-written code, git blame tells you the last commit that touched a line. But a screen regenerated by an AI builder often replaces the entire file. The diff is recorded not as a one-line change but as a file replacement, and blame simply points at the single regeneration commit.
What makes it worse is that the intent of a change never survives in the commit. If a generation you asked to "fix the price display" also rewrites state management somewhere far away, the commit message still reads Update pricing screen. What you read back later is not what actually happened—only what you set out to make happen.
So the thing that's missing is not line-level history. It's generation provenance: which generation event corresponds to which version of the app, and what it touched. Supplying that is the subject of this piece.
Keep Provenance in Three Layers
Provenance always breaks down if you try to write it in one place. Your build artifact, your source tree, and your running app have different lifespans and different readers. I split it into three layers.
Layer
What it keeps
When it helps
Build stamp
Burns the generation ID and build time into an in-app constant
Answering "which generation is this?" on a device or review build
Generation ledger
One line per generation: intent, files touched, credits spent
Tracing back weeks later to "when was that change?"
Telemetry stamp
Attaches the generation ID to crashes and key events
Reverse-mapping a production bug to a generation
Each layer has value on its own, but the payoff jumps when you thread them with the same generation ID. Read the intent in the ledger, measure the blast radius in telemetry, reconcile your local build with the stamp. Whether you can move back and forth like this is what decides how fast triage goes.
✦
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
✦Stamping a generation ID at build time and reverse-mapping crashes and analytics back to it
✦A minimal GENERATIONS ledger format plus a script that diffs one generation against another
✦A triage sequence that narrows a bug down to the suspect generation in minutes
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.
First, decide on a string that uniquely names a generation. I use sequence number + short label (for example, g047-pricing). A bare number is hard to recall; a bare label collides.
For Expo (React Native), feed a build-time environment variable into extra in app.config.ts.
In the app, read this value from a single provenance module. Scatter the references and you invite stale copies, so keep the read point to exactly one.
// src/provenance.tsimport Constants from "expo-constants";const extra = Constants.expoConfig?.extra ?? {};export const PROVENANCE = { generationId: extra.generationId ?? "unknown", builtAt: extra.builtAt ?? "unknown",} as const;
The build just passes the variable: GENERATION_ID=g047-pricing eas build --profile production, updated on every generation. Show a small g047-pricing in a corner of your settings screen and you can read the generation off a device you handed out by QR at a glance.
The idea is the same on the Swift side that Rork Max generates. Put the value in a User-Defined build setting and read it back through Info.plist.
Why make the generation ID the primary key instead of the timestamp? A timestamp says "when it was built" but not "which intent it came from." When you investigate a bug, the latter is what you actually want. Keep the timestamp only as a secondary detail.
Generation Ledger — One Line per Generation
Put the ledger in GENERATIONS.md at the repo root. It needs no tooling, and it lives alongside your git history. Keep each entry short and at a grain you can grep later.
## g047-pricing — 2026-07-07- Intent: locale support for price display (fix currency symbol position)- Scope touched: PricingScreen, useLocalePrice- Suspected side effect: state initialization (watch)- Credits spent: 12- Build: eas build production (no TestFlight; QR device only)
The two lines that matter are "Intent" and "Suspected side effect." Right after a generation, glance at the diff once to check whether anything you didn't ask for has changed, and jot down whatever caught your eye. Those few dozen seconds of note-taking save you half a day weeks down the line.
Left to human memory alone, the ledger goes unwritten on exactly the busy days. So pair it with a small script that mechanically attaches the files that actually changed between generations. Tag each generation with git tag, and you can summarize the diff against the previous one.
Run node scripts/gen-diff.mjs g046-home g047-pricing and you get a summary of changed files and line counts. Paste it into the matching ledger entry and "intent" sits next to "what actually got touched," making any gap between them obvious. If you only asked about the pricing screen but store/auth.ts changed, that is your future fire.
Stamp the Generation Onto Telemetry
Even once your local build and the ledger connect, production bugs happen on users' devices. Attach the generation ID to crash reports and key events, and you can reverse-map from your dashboards to a generation.
import * as Sentry from "@sentry/react-native";import { PROVENANCE } from "./provenance";Sentry.setTag("generation_id", PROVENANCE.generationId);// Pass the same value to analytics as a super propertyanalytics.setSuperProperty({ generation_id: PROVENANCE.generationId });
Now you can group your crash list by generation_id. Comparisons like "crash rate went from 0.3% on g046 to 1.1% on g047" become something you state with numbers, not a guess. Slice AdMob impressions or purchase conversion by generation and you may find that a regenerated UI quietly dropped revenue.
Once the numbers point at a generation, open that generation's ledger entry and read the line you wrote under "suspected side effect." More often than not, the culprit is already noted there.
A Triage Sequence for When a Bug Appears
Once all three layers are in place, investigation becomes a fixed sequence of steps. This is the order I follow myself.
Group crashes and key metrics by generation_id and identify the generation where the numbers started to slip. Data is the starting point, not a hunch.
Open that generation's ledger entry and read "intent" and "suspected side effect." The gap between what you asked for and what happened shows up here.
Look at the gen-diff output and check for files touched outside your instruction. In most cases the cause of the production bug is somewhere in that list.
If your local build stamp matches the generation, reproduce on that version and roll back to the previous generation to see whether the symptom disappears.
The virtue of this order is that a hunch never enters until the very end. An investigation that used to mean trying reproduction conditions at random becomes a narrowing driven by generations.
How Far to Take It — Knowing Where to Stop
Provenance is insurance, not a goal. Try to record every generation perfectly and the recording itself becomes the burden, and you stop doing it. Here is the line I hold.
Situation
How much provenance
A generation you ship to production
All three layers (stamp, one ledger line, telemetry)
A throwaway experiment
Nothing. Don't pollute the ledger
A generation touching billing or the data layer
All three, plus one minimal test guarding what breaks
Recording provenance takes roughly a minute per generation. Even at a dozen-plus generations a month, that's a dozen-plus minutes. Cut a single half-day investigation just once and it has more than paid for itself.
Wrapping Up
AI builders make "easy to regenerate" their sharpest edge. To wield that edge safely over the long run, you need the discipline of recording the fact of regeneration itself. The build stamp for your local version, the ledger for the intent, telemetry for the production impact—thread those three with the same generation ID and bug investigation shifts from hunch to reverse lookup.
Starting with your next generation, add just one line to GENERATIONS.md. The first line does nothing for you. It starts to pay off on the day a bug comes back for the third time.
It's a small habit I picked up in day-to-day operation, but if it trims anyone's investigation time, I'll be glad. 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.