← Back to blog

The AI Wrote the Code. That Was the Easy Part.

I can’t write code. Last month I put a production app on the internet anyway — real accounts, real financial data, a privacy policy that has to hold up under GDPR and Turkey’s KVKK. An AI wrote every line.

The easy assumption is that the AI is the story. It isn’t. The AI writing the code turned out to be the least demanding part of the whole build. What decided whether this shipped was product work — the kind that has nothing to do with who, or what, is doing the typing.

Here’s what I mean.

A PM who can’t code, building for real

Exptract is an expense tracker. You photograph a receipt, a vision model reads the amount, date, merchant, and category, you glance at it and confirm, and it’s logged. It installs like an app but runs in the browser, works in Turkish and English, and it’s live.

I built it solo with Claude Code — it wrote the code, I owned every decision: scope, design, security, compliance. I set out to test exactly that split. Can an AI be a real development partner on something shippable, not a toy? Shippable was the point. Toys don’t have real auth, and they don’t store other people’s money under row-level security.

A real product is where the model stops being the hard part.

Where it nearly broke

The receipt scanner — the one feature the whole app exists for — failed behind a single misleading error.

The browser said “CORS error.” That was a lie. The real problem was five layers deep, each one hiding the next: a missing database permission, then an auth token that never reached the server, then a function crashing before it could return the headers that would have explained itself — which is exactly why the browser fell back to blaming CORS. Then a config secret the function wouldn’t pick up until a redeploy. And underneath all of it, the AI provider’s actual answer: limit: 0. The specific model I’d picked had zero free quota. One config change, and it worked.

An AI wrote the code that broke. No AI told me the “CORS error” was a decoy, or that the fix was to make each layer say what was actually wrong before touching the next one. That’s debugging instinct, and it was mine to supply. The takeaway I kept — make failures readable first, then debug — is a working lesson, not a prompt.

The same trap, a missing database permission in Supabase, caught me three separate times before I finally wrote it down as a rule. Twice should have been enough. The AI didn’t learn it for me. I had to.

Deciding on purpose, not by default

The decisions that shaped the app most were the ones I made on purpose, instead of letting a default happen.

Account deletion is the clearest. “Let users delete their data” sounds like a checkbox. It isn’t. Hard delete versus soft delete is a real right-to-erasure question, and I chose hard — which then meant hunting down the receipt images in storage that a database cascade quietly leaves behind. Consent was the same shape: to prove someone agreed, I needed a record they couldn’t forge, written by the system at signup, at the exact moment there’s no user session to write it with.

None of that came up because the AI raised it. It came up because I stopped at each step and asked what the responsible version looked like, then picked it deliberately. When I signed up with an email that already existed and the app just hung, the fix knowingly traded a sliver of security for an honest error message — a trade I made with my eyes open rather than pretending it away.

That discipline didn’t clock out at launch, either. A month later Supabase threatened to pause the free project after a week of inactivity, and the measured answer was a small scheduled ping to keep it awake — not a panicked rewrite of a backend that already worked.

What actually did the work

Two real bugs survived every code review across the whole build. Both, I caught by using the app the way a user would — validation messages showing up in the browser’s language instead of the app’s, and the sign-up hang from a minute ago. Five minutes of clicking beat code review in every single phase.

That’s the whole thing, really. The AI removed the barrier that used to stop people like me: I don’t have to write the code. It did not remove the work. Scope, judgment, testing like a suspicious user, deciding on purpose instead of by default — that’s still the job, and it’s the part that decides whether what you ship is real.

The code was the easy part. It quietly always was.