Shopify Drops React Native: Agent Port Tested
Shopify just killed React Native at the company that was its most famous champion. Today they announced their mobile apps — Shop, Shopify, Point of Sale, Inbox — are moving back to Swift and Kotlin, and the stated reason is not performance, not New Architecture pain. It's that coding agents made the 2020 premise ("don't build the same feature twice") obsolete. Their Shop app went from proof of concept to fully rebuilt native app in 12 weeks; native session stability climbed from 99.5% to 99.95%; the Android build shrank 109 MB and its release build time fell ~75%. Before you repeat the headline, you should test the premise underneath it — so I did, with 40,000 differential test cases.
Why Shopify Dropped React Native
The 2020 argument for React Native was arithmetic: building one shared implementation beats building two. Today's post inverts it — not because agents write more code, but because agents port code. Shopify's team found agents could implement an Android feature using the iOS version as reference, ramp developers outside their primary stack, and hold parity through shared specs and review checkpoints. Their words: agents "reduced the advantages of sharing implementation, while the advantages of building for each platform remain."
The receipts from the companion migration write-up are unusually specific:
- 12 weeks from PoC to a fully native Shop app shipped to both app stores
- 10x fewer crashing sessions — 99.5%+ → 99.95%+ session stability
- −109 MB Android release build; Android build time down ~75%
- 120 FPS feed scrolling on a Pixel with minimal optimization
- One engineer, one week: a close feature-for-feature RN→SwiftUI proof of concept
The most interesting admission: the native rewrite got better stability than the framework that existed to reduce bugs. Shared code isn't automatically correct code.
I Tested the Premise: 40,000 Differential Cases
The load-bearing claim is "agents can port a feature across platforms using the other implementation as reference, at negligible cost." That's testable at small scale. I built a canonical cart-pricing engine in Rust — order promos, fixed-discount distribution, per-category tax, half-up cent rounding — then ported it to TypeScript two ways, exactly the way an agent plausibly would:
- Naive port (53 lines): natural JS idioms, floating-point intermediate math,
Math.round. - Strict port (62 lines): BigInt arithmetic mirroring the Rust half-up helper exactly.
Then a differential harness: 20,000 randomized carts per seed, two seeds, three output fields each — 120,000 field comparisons against the Rust binary's output.
{
"seed_42": { "naive_field_mismatches": 56, "strict_field_mismatches": 0 },
"seed_1337":{ "naive_field_mismatches": 68, "strict_field_mismatches": 0 }
}
The disciplined port: zero mismatches across 120,000 comparisons. The naive float port drifted on ~0.3% of carts — always ±1–2 cents on tax, from float representation error at the half-up boundary. Money bugs, invisible in any screenshot, invisible to a model reviewing code that "looks right."
The Best Part: My Canonical Implementation Was Wrong
The experiment's sharpest finding wasn't the ports — it was the reference. My first Rust canonical had two latent bugs the differential harness caught before the ports did:
- Nondeterministic ordering. I iterated a
HashMapto distribute the fixed discount across categories. Rust's HashMap order is randomized per run — the "same" implementation could price the same cart differently across builds. Cross-language parity testing is impossible against a nondeterministic oracle. - A truncation bug.
half_up(p.value * sub / total, 1)does integer division before rounding — it rounds the wrong operand. Both TS ports implemented the written spec and "diverged" from a buggy reference.
That's Shopify's migration diary in miniature: "generated code could satisfy feature requirements while still introducing duplication, architectural drift, or performance problems." Drift isn't a model failure — it's the difference between intent and spec, and the reference implementation is where the spec hides. My harness caught it because it compared behaviors, not code.
Why Shopify Wrapped Agents in Checkpoint Gates
Shopify's tooling confirms the same lesson at production scale. One-shot porting failed explicitly: "you end up with a huge amount of unmaintainable code that can't be shipped." Their answer is Helix — a migration loop that never lets an imperfect attempt advance: each checkpoint must pass tests, match the running app visually, survive two adversarial code reviewers, and get human sign-off, with plan acceptance cryptographically bound to a hash of the reviewed plan. They built Tardis to give agents structured access to live app events, and deliberately decoupled business logic from the UI so agents iterate in milliseconds over a CLI instead of minutes over a simulator. That last one is the quiet revolution: they redesigned app architecture for agent feedback loops, not just for users.
graph TD
A[React Native source] --> B[Agent reads + proposes checkpoints]
B --> C[Implement checkpoint]
C --> D{Tests pass? Visual match?
2 adversarial reviewers? Human nod?}
D -->|No| C
D -->|Yes| E[Commit, next checkpoint]
E --> B
Bottom Line
Shopify's move is the first major platform reversal where AI is the cause, not the feature — a five-year architectural bet unwound because its core assumption got cheap. My 40,000-case experiment says the claim is real but conditional: a disciplined port hits 100% behavioral parity, a naive one silently misprices 1 in 300 carts by a cent. The "build twice is free" era isn't free — it's just been repriced from developer-hours to harness engineering: differential tests, checkpoint gates, and parity reviews. The teams that win this transition are the ones who treat the reference implementation as a behavioral oracle, not a text prompt.