Get in touch

Work Penned & Plated

Penned & Plated

“Could I photograph a handwritten recipe card, hand it to Claude, and get a clean digital recipe back?”

5 days
first commit to a readable archive
144+
recipe cards transcribed

It started with that question. My family has a box of recipe cards, handwritten, typed, clipped from magazines, and often written on both sides. I gave it a shot. Within two days most of the box was on a static site simple enough to run on a 2010 iPad, and by the end of the week all 144 cards were there.

The clean cards were done. The rest weren’t: about thirty needed fixes, and each misread ingredient or garbled step meant finding the right file among 144 and editing its Markdown by hand. Now I knew the idea worked, and what it needed next: a place to triage what the model got wrong, so every card could end up a finished recipe. At that moment, Penned & Plated was born.

A handwritten Cowboy Caviar recipe card held up beside red flowers, listing red wine vinegar, hot sauce, oil, garlic, pepper, black-eyed peas, corn and diced tomatoes The same Cowboy Caviar recipe in the Penned & Plated web app, with ingredients listed by quantity and the method in numbered steps
A card, and the recipe it became. Notes like “w/ no spices in them” are kept, not cleaned up.

How a Card Becomes a Recipe

Today, every card takes the same path into the archive:

A card is photographed and Claude transcribes it. With no doubts, it's filed to the archive. If flagged, an unreadable word goes to someone with the card and a contradiction goes to someone who makes a call; once settled, it's filed. A card is photographed Claude transcribes it no doubts (123 of 144) flagged Filedautomatically Waits for a person Unreadable wordneeds the card Contradictionneeds a call In the archive

It’s a web app, an iPhone app, and the pipeline between them, built by one engineer working with AI agents:

  • One design system, made in Claude Design, is shared by the web app and the iPhone app, so the two feel like one product.
  • The pipeline started as a few local scripts that read most of the box in two days. About three weeks later it moved into the web app, which reads each card with Claude Opus 5 as it’s uploaded.
  • The web app is a Rails app. The archive was imported and readable five days after its first commit.
  • The iPhone app is a SwiftUI app that scans a card, or a whole stack, front and back, and shows each reading as it happens. It uploads to the web app, which does the reading. Its very first version could already scan and upload a card; 26 days later it was out for testing.
The iPhone app's Add cards screen, with four scanned recipe cards read and ready to check
Four cards scanned in one pass, each read and ready to check.

Handling the Cards the Model Gets Wrong

Claude does a good job reading most cards, but I still needed a plan for the ones it gets wrong. I wanted to be confident in what lands in the archive automatically, and just as confident in what gets held for a person to review.

A few safeguards back that up:

  • A recipe only shows up in the archive once its reading is settled, so nothing half-finished is ever visible.
  • Unreadable words and contradictions go into separate lists, since different people usually handle them.
  • Quantities are stored as text. Cards have entries like “1 1/4”, “1 to 2” and “2 [?]”, and converting those incorrectly could turn a teaspoon into a tablespoon.
  • Duplicates are caught two ways. Repeat photos are flagged, and a text-similarity check catches the same card photographed differently. Titles alone wouldn’t work – the box has two different guacamole recipes, and we want to keep both.

Keeping the Two Apps in Sync

The web app and the iPhone app were built in separate Claude Code sessions, each with its own codebase. A shared contract for the API kept them in step.

The web app owns that contract: it’s written in TypeSpec, published as OpenAPI, and every request and response in its tests is checked against it. The iPhone app keeps its own copy and builds its connection to the web app from it, so it only picks up a change when it’s ready. The web session changed the contract nine times, and each time it messaged the iOS session, which updated its copy and built its half.

The contract is written in TypeSpec and published as OpenAPI. The web app's tests check every call against it, and the iPhone app builds from its own copy, updated when it's ready. The web session told the iOS session about a change nine times. The contract, in TypeSpec Published as OpenAPI checked against copied when ready Web apptests check every call iPhone appbuilds from its own copy "the contract changed" (9 times)

Where AI Needed Correcting

Nearly all of the code in both apps was written by AI agents, with a human in the loop: I reviewed every change before it landed. Some of what that review caught:

  • Uploading a card the phone couldn’t read on its own would have crashed the iPhone app every time. The automated tests used a stand-in for the network, so they never saw it – a test against the real thing did.
  • The agents left notes in the code explaining why certain approaches wouldn’t work. They sounded convincing, so nobody tried them. They turned out to be wrong.
  • The agents sometimes built something by hand that the framework already provided. Those got removed.
  • One change deleted a database update that had already been applied, which could have broken the live data. It was restored before it did any damage.

These turned into standing rules for the agents, like “Measure queries, do not reason about them” and “Count duplication, do not estimate it”. Measuring also showed that shrinking photos before sending them to the model saved nothing and made them harder to read, so photos are stored as they arrive.

Where It Stands

The web app has been live since September 2026, invite-only, and the iPhone app went into alpha testing on TestFlight in fall 2026. A closed beta, where testers start their own recipe boxes, is planned for Christmas 2026.

I’m a Rails and back-end engineer by trade. I’m comfortable designing the architecture and schemas that support an interface, but I’ve rarely been the one designing or building the interface itself. This project had me working across the full stack, including the initial design.

Working on something interesting?

I read every message and reply personally.

Get in touch