A friend and former colleague asked me to help build the travel app she'd started. What she handed me was a base44 export — generated React from an AI app builder, with a proprietary SDK threaded through every data call.
By June we had vess: an iOS app where you log the places you've been along with your honest reviews, and see what people you trust thought — recommendations from friends and family instead of sponsored posts and inflated ratings. It's also where you plan the trip itself — pulling in the restaurants and sights you've saved from friends' reviews.
Here's what I learned about shipping production-quality work when the model is doing most of the typing — including the two months we spent discovering that our inherited foundation had a security model that only looked like one.
Why Claude Code, and how I set it up
I picked Claude Code partly out of curiosity — I hadn't had much room to explore it at work and wanted to see what everyone was raving about — and partly because the work in front of me was archaeology, not authorship. I had to understand a generated codebase I hadn't written before I could safely change it, and I needed something that could read across a repo I couldn't yet hold in my head.
Three months of nights and weekends. Two people. An Expo app on a Supabase backend, being used by its two builders while we get it ready for a wider release. Claude Code was my primary coding interface from day one — even though I didn't yet know what it was capable of.
The structure that made this work — some deliberate, some lucky:
My loop was boring on purpose: describe the change and the constraint, let it propose a plan, read the plan before reading the code, then review the diff file by file. I wrote code myself when the problem was a decision rather than an implementation — the shape of a data model, an auth boundary, anything where the wrong choice would be expensive to unwind.
Where it was genuinely a 10x
Untangling base44, overnight. The proprietary SDK was threaded through every data call, and we wanted it out. Tracing an SDK through generated code you didn't write is exactly the task where reading is the bottleneck, not typing — a weekend of manual tracing, minimum, and I'd have been guessing about the parts I hadn't read. Overnight, we had our own data layer.
Web React to React Native in about 48 hours. Vess started as a web app; once we decided it should be iOS-native, we pointed Claude Code at the port. Neither of us had shipped an Expo app before. Screen by screen, the port went faster than learning the framework would have.
Tests we otherwise would not have written. 68 tests in a single commit, including a 371-line suite against the data layer, retrofitted onto code that had none. Let me be honest about the counterfactual: the alternative wasn't slower tests. On a two-person side project at 1am, the alternative was no tests.
The share importer. Pulling a place out of a TikTok or Reddit link — oEmbed extraction, then Google Places text search, then ranked candidates — plus native share-sheet integration through expo-share-intent. Four unfamiliar APIs stitched together in an evening.
Where it wasn't, and where I got burned
USING (true). The generated schema had row-level security enabled on every table. That reads as “we did the security thing.” But the SELECT policies were USING (true) — every private profile, every logged place, every trip comment readable by any authenticated user. Our is_public flag existed in the UI and meant nothing to the database. It took a manual audit — logging in as one user, trying to read another's private data — to catch it. Migration 015 is what finally replaced those policies with real ones.
Web idioms that fail silently in a native runtime. Two of the same species. The avatar upload used a Blob; React Native's Blob implementation is incomplete, Supabase Storage rejected it silently, and an empty catch {} swallowed the evidence. Uploads “succeeded” and produced nothing. Separately, crypto.randomUUID() — there is no crypto global in Hermes, so it crashed at runtime rather than at build. Both typechecked. Both passed my review.
Migration collisions across sessions. We have three collisions in our migrations folder — two 005s, two 015s, two 016s — because separate sessions each numbered “next” against a repo state that had already moved. Then it compounded into three consecutive commits fixing migrations that assumed a schema that didn't exist: making the SQL idempotent, dropping policies before altering a column, and finally skipping a type change because the live database already had it.
What stayed human
Taste doesn't delegate. We wanted vess to feel chic, warm, and elevated — an app you'd trust to help plan your next trip, not another AI-generated feed.
Days in Procreate and Figma. Fonts, color palettes, Pinterest boards. In a moment when a new AI-built app launches every other day, we wanted vess to feel considered — and that meant making the decisions ourselves.
It took longer than we'd like to admit — we both come from technical backgrounds — but I found the design work genuinely rewarding in a way engineering usually isn't.
Shipping faster didn't mean shipping thoughtlessly. It meant spending my attention on the twenty decisions that needed judgment instead of the two thousand that needed typing.
What I'd change
I'd audit the inherited schema before building three months on top of it.
base44's generated data model keyed identity on email — created_by, follower_email, user_email, select_emails, all the way out to a route at app/user/[email].tsx. Users' email addresses, in URLs, in a social app. Every RLS policy became a string comparison against auth.jwt() ->> 'email' instead of a UUID foreign key. Nobody can ever change their email, and every downstream feature that needed to know “who can see this row” had to re-derive it from email string-sets on the client.
Moving to auth.uid() would have cost two hours on day one. I inherited it, it worked, and I paid for it for three months. The mistake wasn't the schema — it was treating a working foundation as a decided one.
Close
The habit I kept: the model is extraordinary at the parts of the job that are reading and typing, and indifferent to the parts that are deciding. Knowing which is which — before you start, not after the audit — turns out to be most of the skill.







