RORK LABJP
ENGINE — Rork Max generates code on top of Claude Code and Claude Opus 4.6, which is worth knowing when you tune how specific your prompts areSPLIT — Rork Max is Apple-only. If you also need Android, the original Rork is the one that generates cross-platform apps with React NativeDEVICE — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, reaching territory React Native struggles to coverCREDIT — Billing runs on credits: one credit per AI interaction, reset on the 1st of each month with nothing carried overPLAN — Free gives 35 credits a month (5 per day); Junior is $25/mo, Senior $100/mo, and Rork Max $200/mo, with Senior the usual pick for MVP workFUND — Rork raised $2.8M from a16z and now draws over 743,000 monthly visitsENGINE — Rork Max generates code on top of Claude Code and Claude Opus 4.6, which is worth knowing when you tune how specific your prompts areSPLIT — Rork Max is Apple-only. If you also need Android, the original Rork is the one that generates cross-platform apps with React NativeDEVICE — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, reaching territory React Native struggles to coverCREDIT — Billing runs on credits: one credit per AI interaction, reset on the 1st of each month with nothing carried overPLAN — Free gives 35 credits a month (5 per day); Junior is $25/mo, Senior $100/mo, and Rork Max $200/mo, with Senior the usual pick for MVP workFUND — Rork raised $2.8M from a16z and now draws over 743,000 monthly visits
Articles/Dev Tools
Dev Tools/2026-07-27Advanced

When to Raise Your Minimum iOS Version — Count Leftover Branches, Not User Percentages

Judging a minimum OS bump by usage share produces the same answer every year, so the decision never happens. Here is the annotation convention, the sweep script that counts how many branches each candidate floor would retire, and what to watch for 30 days after.

Rork521Expo151React Native213Maintenance3iOS110Long-term Operations4

Premium Article

Last autumn I opened the same spreadsheet I had opened the two autumns before it.

I pasted in the session share by OS version from App Store Connect and looked at the oldest row. 0.9 percent. The year before it was 1.4, and before that 2.1. The number was shrinking on schedule.

The conclusion never did. "Still can't drop it," I typed, and closed the sheet.

By the third year I understood that my judgment wasn't the problem. The metric was. Usage share tells you how many people you would stop reaching. It says nothing at all about what you keep carrying if you don't.

Usage share can only argue for waiting

When you maintain several apps on your own, decisions get made in whatever minutes are left over. That's exactly why a single number is so tempting.

The trouble is that usage share only pushes in one direction.

Say your threshold is 1 percent. At 0.9 percent, can you actually pull the trigger? No — you tell yourself it'll be 0.5 next year, so wait. At 0.5 percent you say something different but equivalent: is it really worth taking on release risk to cut a mere half a percent? The denominator shrinks, the pain of cutting shrinks with it, and your internal threshold quietly shrinks right alongside both.

I run six wallpaper apps in parallel. The cost of deferring was accumulating six times over, in a place I wasn't looking.

That place is the branches that paper over OS generation differences.

// A real example, simplified
if (Number.parseInt(String(Platform.Version), 10) >= 17) {
  await Sharing.shareAsync(uri, { UTI: 'public.png' });
} else {
  // Older OS closes the multi-select share sheet halfway through, so send one at a time
  for (const one of uris) {
    await Sharing.shareAsync(one, { UTI: 'public.png' });
  }
}

That branch was correct the day it was written. The problem is that nowhere in the codebase does it say when it becomes safe to delete.

Six months later, I can't tell whether the else path is still load-bearing. Because I can't tell, I don't delete it. Because I don't delete it, every future change to this screen means verifying both paths.

So the real cost of deferring never showed up as lost users. It showed up as a count of branches I couldn't retire.

Which suggested an obvious move: count them directly, and let that count be the input to the decision instead of usage share.

Give the branches one place to live

The first step was deciding where OS checks are allowed to be written, and in what form.

Instead of reading Platform.Version from every screen, I wrapped it in a comparison function. Element-wise numeric comparison, so that string ordering never turns "9.0" into something greater than "10.0".

// src/lib/os.ts
import { Platform } from 'react-native';
 
const parse = (v: string | number): number[] =>
  String(v).split('.').map((n) => Number.parseInt(n, 10) || 0);
 
/** True when the running iOS is at least `target`. Always false off iOS. */
export function atLeastIOS(target: string): boolean {
  if (Platform.OS !== 'ios') return false;
  const cur = parse(Platform.Version);
  const req = parse(target);
  for (let i = 0; i < Math.max(cur.length, req.length); i++) {
    const a = cur[i] ?? 0;
    const b = req[i] ?? 0;
    if (a !== b) return a > b;
  }
  return true;
}

I ran the comparison logic against the boundary cases before trusting it: a bare "17" with the minor omitted, "17.0.1" against "17.1", "16.4" against "16.4.1". Seven cases, all matching expectations. Version comparison looks obviously right the moment you write it and falls apart the moment a third component appears.

The floor itself lives in exactly one file.

// src/config/supportMatrix.ts
// The OS floor this app officially supports. This file is the single source of truth.
// Never change it without running os-sweep first.
export const MIN_OS = {
  ios: '16.0',
  android: 31,
} as const;

And every OS branch carries an annotation:

// @drop-when ios>=17.0 -- multi-select share sheet closes early below 17.0
if (!atLeastIOS('17.0')) {
  return <LegacyShareSheet uris={uris} />;
}

The format is deliberately trivial: @drop-when ios>=X, then --, then the reason.

What matters isn't the rigor of the syntax. It's that "when can this go" and "why does this exist" sit two characters away from the branch itself. With both present, future-you can make the call in seconds.

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
The full sweep script that reads @drop-when annotations and reports how many OS branches each candidate minimum would let you delete
A three-level exit code design that fails CI on stale branches left behind after a bump, plus the GitHub Actions wiring
Why your crash-free rate improves right after a bump, and why recording that as a win will distort next year's decision
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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-05-28
Tracking Down BGTaskScheduler.submit Error Code=1 (Unavailable) in Rork iOS Apps
A field-tested checklist for diagnosing BGTaskScheduler.submit failing with Error Code=1 (Unavailable) in iOS apps built with Rork, walking through the six causes that account for nearly every case.
Dev Tools2026-04-02
Adding Native Share Functionality to Your Rork App — A Complete Share Sheet Guide
Learn how to implement native Share Sheet functionality in your Rork app. From sharing text and URLs to images and deep links, this beginner-friendly guide walks you through real code examples for iOS and Android.
Dev Tools2026-07-26
What Filtering Out Prerelease OS Devices Cost Me — Separating Telemetry Tracks Instead of Dropping Them
Filtering prerelease OS devices out of telemetry left me with no warning signs on GA day. Here is the rebuild: an os_track dimension, a remote GA baseline, split alert thresholds, and a release gate that separates blockers from fixes.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →