Same Claude Opus 5.5: Why Some People Ship an App and Others Call It Dumb

Building a React Native + Expo app from scratch with Claude Code and Opus 5.5: three rejected UIs, dropping uni-app x and Lynx, adb device testing, OTA updates and a HarmonyOS build.

Same model, Claude Opus 5.5. Some people say it gets a week’s work done in half a day. Others say it gets two out of every three questions wrong.

I recently used it to build a mobile app called Ruzai (如在). When you’re away from your computer, it lets you use your phone to view the AI assistant’s conversations on that computer, send it messages, and approve the critical actions it wants to run.

Product design, UI, code, GitHub Actions pipelines that build Android APKs and push iOS builds to TestFlight, a self-hosted OTA update server, App Store listing assets: nearly all of it was done by Opus 5.5 inside Claude Code. Along the way we switched tech stacks once, evaluated two cross-platform frameworks, and I rejected the UI three times.

Looking back, model capability was almost never the bottleneck. With the same model, whether the results are good comes down to the person using it. This post breaks that down into a few concrete gaps, based on what actually happened during the project.

The tech stack, up front

Area What we ended up with Tried / abandoned along the way
Mobile framework React Native 0.88 + Expo SDK 58 uni-app x (abandoned), Lynx 4.0 (abandoned after a prototype)
Routing and compilation Expo Router, React Compiler, TypeScript 6 TypeScript 7 (incompatible with typescript-eslint)
Gestures and animation Reanimated 4.7, Gesture Handler 3.3
Builds GitHub Actions: Android APK, iOS upload to TestFlight
OTA updates expo-updates protocol, hosted on my own server
HarmonyOS Native ArkTS version, builds cleanly in CI
Development and debugging Expo Go plus a real device, with the AI driving the phone directly over adb

Every “abandoned” and “switched to” in that table was a disagreement between me and the AI. Let’s go through them one by one.

Gap 1: Say “it’s ugly” and the AI can only guess, three rounds in a row

When the first version of the UI came out, I said: it’s ugly.

Round one, Opus 5.5 changed the color scheme. It found three established palettes online and built them into the prototype for me to choose from:

Option Source Primary color
A Warm Cream Claude’s official palette Terracotta orange #c96442, off-white background #faf9f5
B Teal Radix Sage gray + Jade green #29a383
C Indigo Radix Slate gray + Indigo blue #3e63dd

All three palettes were perfectly sensible. I still thought it looked ugly.

Round two, it stopped just swapping colors and rebuilt the whole thing in the style of the 2026 Gemini App redesign: pure white or pure black backgrounds, no borders anywhere, an oversized greeting, and a floating pill-shaped input box.

In this round it also tracked down a cause I never would have guessed: the screenshots themselves looked cheap. The mockups were captured with headless Chrome on Windows, where Chinese text renders in Microsoft YaHei by default. After switching to Source Han Sans (Noto Sans SC), the look instantly got close to MiSans and PingFang on a phone. Part of what I’d been calling ugly in the first two rounds was really the screenshot font.

Round three, I figured the problem might be the icon colors, so I had it drop gradients entirely. It produced six solid-color icon options (black background with a bold sans, black background with a serif, a vermilion seal stamp…), and we settled on a white “Z” on solid black.

Only after three rounds did I realize I’d been handing out verdicts the whole time, never direction. If my first sentence had been “conversation-first like ChatGPT, black and white, no gradients,” the first version would have been close to final.

AI has no aesthetic preferences. What it gives you is the average of a huge number of designs. If you can’t articulate what you want, toss it a screenshot of an app you like. That beats ten rounds of “make it look more premium.”

Gap 2: Whatever the spec says, it does to the letter

This project had two computers doing the work: one handled the server and the mobile app, the other handled the Windows client. The AIs on each side didn’t share memory. They coordinated through spec documents in the code repository: the protocol, the APIs, what needed to change this time. One side wrote and committed the doc, the other pulled it down, followed it, and marked the status when done.

This setup worked really well. When the desktop client got a new icon and was hooked up to the mobile relay, this side wrote the handoff doc first, and the other side just followed it and got it done.

But it has a side effect: any decision written into the doc, the AI will carry out without compromise.

At one point, the spec switched the mobile tech stack to uni-app x. Opus 5.5 followed it and wrote an entire uni-app codebase, committed it, fixed the issues, and did a beautiful job. Only after it was done did I realize this locked the product into a single platform vendor, DCloud: push notifications, builds, and OTA updates would all depend on them. So I tore it down and started over with React Native + Expo.

The AI won’t ask on its own, “Will you regret this choice three years from now?” Irreversible decisions like tech stack selection have to be made by a human.

Gap 3: Have it write code first, or have it explain why first

Later I wanted to try ByteDance’s Lynx: one codebase for Android and iOS, and possibly HarmonyOS down the road. Opus 5.5 quickly put together a prototype that compiled, connected to the server, and rendered conversations.

But the UI had gone ugly again, nothing like the finalized design.

This time I didn’t let it jump straight into fixes. Instead I said: no code yet. First let’s work out why, then come up with a plan, and only then write code.

The reasons it gave were very specific:

Area React Native version Lynx prototype Why
Icons SVG line icons ☰ + ↑ text symbols Lynx can’t render SVG out of the box
Sidebar Slides in from the left, 84% width, with an overlay Replaced by a full-page list No ready-made drawer or modal components
Bottom sheet Picking a computer, confirmation details with screenshots None Same as above
Overall Tuned piece by piece against the prototype Thrown together in an hour to prove feasibility Lynx can’t use React Native components; only pure logic can be reused

On top of that, testing turned up regressions: in Lynx 4.1.0, taps inside a scroll-view didn’t fire, and in ReactLynx 0.128 all taps stopped working entirely, so we had to roll back to 4.0.3 and 0.126.

Once the reasons were laid out, the decision was easy: stick with React Native.

If I’d just said “make the UI look good,” it would probably have spent hours re-tuning everything on Lynx, only for us to switch back in the end. The faster AI writes code, the further it runs in the wrong direction.

Gap 4: The more power you give it, the clearer the boundaries need to be

For on-device testing, I had the AI drive an Android phone directly over adb: tapping buttons, typing text, taking screenshots to check results. That’s how a lot of issues surfaced that the desktop prototype simply couldn’t reveal:

Symptom Cause Fix
Keyboard covers the input box With edge-to-edge enabled, the layout didn’t make room for the keyboard Switched KeyboardAvoidingView to padding
Taps do nothing after the screen dims The first tap gets used to wake the screen Send KEYCODE_WAKEUP before testing
Sidebar swipe gesture ends the moment it starts The native ScrollView stole the touch Switched to Gesture Handler’s ScrollView, plus a 36dp-wide transparent hit zone along the left edge
Long conversations stop halfway when opened During batched layout, the code’s own scroll-to-bottom also fired scroll events, which got misread as “the user scrolled up” Only finger drags can change the “at bottom” state
Text typed via adb never appears The WeChat keyboard intercepted the input Tap a candidate word to commit it

But things did go wrong once: it hit the back button, landed on the phone’s home screen, and its next few taps went straight into WeChat and Alipay.

No harm done, but it was enough of a wake-up call: an AI that can only chat can only talk; an AI hooked up to tools can verify its own work, and can also cause trouble on its own. The same model performs at a completely different level depending on whether it has tools. How much access to grant, which device to test on, and which apps are off-limits all need to be spelled out up front.

Gap 5: Where you review the work determines what you see

The screenshot font issue earlier was really a review problem: if you look at results in the wrong environment, even good work gets dismissed.

Builds are the same. Android packages built by GitHub Actions were signed with a different debug certificate every time, so installing a new version meant uninstalling the old one first. Committing a fixed debug.keystore to the repo finally solved it. You’d never catch that without installing twice on a real phone.

App Store listing assets are an even bigger pile of details:

Asset Required dimensions
Title artwork 5244 × 2950 or 3840 × 1646
Search result artwork 5244 × 2950, 3840 × 2560, or 1920 × 1280
iPhone 6.3-inch screenshots 1206 × 2622 or 2622 × 1206

The AI got all these dimensions right on the first try, but the sample data in the screenshots only got swapped for neutral content after I looked them over and asked.

Then there was the store description. It wrote it based on our real internal use cases, with every feature covered, but describing it that literally would very likely have gotten stuck in review. The AI didn’t know that; I did. We ended up reframing it as “use your phone to remotely control and view the AI conversations on your computer,” with the subtitle “Remote-control and view the AI on your computer.”

AI can get things right, but only someone who’s been through it knows which things, done right, are actually risky.

Gap 6: Do you know what to ask?

Late in the project, I asked: “For small UI tweaks from now on, can we just push an update instead of rebuilding every time?”

That one question bought me a full OTA update system: using the expo-updates protocol, with update bundles hosted on my own server. runtimeVersion uses the fingerprint policy, so JS-only releases update automatically when the phone opens the app, and only changes to native code require a new build.

If I hadn’t asked, it wouldn’t have done it on its own. Not because it couldn’t, but because it didn’t know this mattered to me.

Likewise, the HarmonyOS version only started because I asked, “Can you build a native HarmonyOS version?” It wrote one in ArkTS that builds cleanly on GitHub Actions, and along the way worked through a few pitfalls you only find by actually doing it: Huawei’s ohpm registry frequently returns 503 to GitHub’s runners, so retries are needed; imageknife 3.2.8 and above requires API 18, so we had to stay on 3.2.7.

AI goes exactly as far as your questions take it. People who know how to ask naturally get more out of it.

Gap 7: Are you really using “the same model”?

One last fact that often gets overlooked: when two people say “I’m using Opus 5.5,” they may not be using the same thing at all.

Difference What it actually means
Client Web chat, Claude Code, all sorts of wrapper apps, each with a completely different system prompt
Tools Whether it can read files, run a terminal, drive a browser or a phone
Reasoning effort Thinking levels, fast mode; the same model varies a lot between high and low settings
Context 1M context or standard length; whether old conversations get compacted, and what gets lost when they do
Route Official API, a cloud provider, or various third-party relays; the version and limits behind them aren’t necessarily the same

I built Ruzai with Claude Code: it can read and write the entire repository, run commands, call adb, and use GitHub. Comparing that to a one-question-one-answer session in a web chat was never a fair fight.

Final thoughts

Putting the seven gaps side by side:

Feels “dumb” Feels “smart”
Aesthetics Just says “it’s ugly” Gives reference screenshots, says clearly what not to do
Spec docs Once it’s written, it’s settled Makes irreversible decisions yourself
Order Jumps straight into code Reasons and plan first
Tool access None, or wide open Gives tools, and draws boundaries
Review Looks at screenshots on a computer Checks on a real device, with real fonts and real data
Questions Waits for it to think of things Knows what to ask
Setup Only looks at the model name Looks at client, tools, effort level, route

Across the whole project, Opus 5.5 wrote nearly all the code, but every turning point was a human decision: dropping uni-app x, passing on Lynx, removing gradients, rewriting the store copy, adding OTA updates.

So next time you feel like the AI has “gotten dumber,” don’t rush to switch models. Take another look at the job description you gave it, and ask whether that was the part that wasn’t very smart.

For the more technical side of things, like retrieval, context curation, reasoning budgets, and verification, I wrote about those in Before Switching Models, Make Your AI Smarter. The two pair well together.

If you’re also building your own product with Claude Code or another AI, I’d love to chat: linktr.ee/weiyii

Read and share with WeChat

QR code for this article

Scan to open, then share from the WeChat menu.