All work

Wine education for iOS

A wine label is a lookup first and a model second.

Winelingo went from first commit to the App Store in 56 days. A scan reads the label with Claude, checks a shared catalog before it infers anything, and caches the story so the second person to scan the same bottle waits about two seconds.

Winelingo explore screen
Winelingo label scan result
56
days from the first commit to the App Store
484
pages on winelingo.app, built from the app's own data
170
course lessons, each in four languages
139
database migrations since June

Fifty six days to the store, then a new release every two weeks.

  1. First commit, and label scanning the same day

    An Expo and Supabase scaffold in the morning, and by evening a label scan that sends the photo to Claude vision, with manual search as the fallback. The technical design had planned to license a recognition API. It was never needed.

  2. The Explore library grows

    Regions went from 20 to 82 and countries from 10 to 15 in one day, the catalog the whole app is built on.

  3. Wine App becomes Winelingo

    The rename, and the first freemium gate: one free scan a week. That rule would change twice more before launch.

  4. CI with locale parity

    GitHub Actions runs typecheck, Deno tests, and a check that fails the build if any of the four languages is missing a key.

  5. The website mirrors the database

    74 grapes, 82 regions, 15 countries, 45 styles, 32 pairings, and 13 classifications were rendered to static pages straight from the app's Supabase data.

  6. Two step scanning

    Claude Haiku reads only the wine's identity, a database match checks the catalog, and Claude Sonnet builds a taste profile only for wines nobody has scanned before. Repeat scans measured at around 2.4 seconds.

  7. App Review asks for more information

    A 2.1 rejection, answered with a resubmission kit the same day.

  8. 1.0 live on the App Store

    Fifty six days after the first commit. Launched free, with the paywall switched off behind a flag.

  9. 1.0.1: the journal persists

    Reopen any saved bottle and your rating, aromas, tastes, and notes are there. Built locally because the cloud build quota was spent.

  10. 1.1: the monetization release

    RevenueCat subscriptions in the app, and the free tier settled at five scans per rolling 30 days, enforced on the server.

  11. 1.2: friends, cheers, and badges

    The first social features.

  12. 1.3: Learn 2.0

    The Explore library became a course path with games.

  13. The course goes on the web, with web payments

    Twenty six units in English and Danish on winelingo.app, sold through RevenueCat Web Billing.

  14. 1.4: Course 2.0 and the Vinkortet map game

    Every lesson rewritten in version two, in all four languages. 170 lessons as of 14 October.

The stack

TypeScript on the phone, Postgres and Deno behind it, and a website with no framework at all.

App
Expo and React Native
Expo SDK 56, React Native 0.85, React 19, TypeScript 6. iPhone only.
Camera
expo-camera
The photo is resized to 1024 pixels wide and sent as JPEG. No on device recognition; the model reads the label.
Data
Supabase Postgres
139 migrations and 15 Edge Functions. Scheduled jobs run on pg_cron. Content translations live in their own table.
AI
Claude
Haiku 4.5 reads the label, Sonnet 4.6 infers the taste profile, Opus 5.5 writes the producer story. Called directly over the Messages API, no SDK.
Suggestions
TypeSafe Jev
Picks the next lesson to suggest.
Auth
Supabase Auth
Email and password, plus Sign in with Apple.
Payments
RevenueCat
StoreKit in the app. Web Billing with Paddle on the site for the course.
Analytics
PostHog and Umami
PostHog over plain HTTP in the app, no SDK. Umami on the website.
CI and builds
GitHub Actions and EAS
Typecheck, locale parity, and 41 Deno tests on every push. Builds and submissions through EAS.
Website
Static HTML
No framework and no build step. Node scripts turn JSON into guide, course, glossary, and test pages. Served from GitHub Pages.

A scan is a lookup first, a model second

The first build sent the whole photo to Claude Sonnet and asked for everything. It worked, and it was slow and expensive for the common case, which is a bottle someone has already scanned.

The scan now has two steps. Claude Haiku reads only the wine's identity from the photo. A database function matches that against the shared catalog, ignoring case and accents and tolerating small misreads. A known wine returns at once. Only a new wine goes to Claude Sonnet, which infers the taste profile from text without resending the image, and its producer is queued for research.

The story on the result page is written once per wine per language and cached. Only the personal comparison is generated live, so the page shows immediately and fills in while you read.

The website is the database, rendered

winelingo.app has 484 pages and no framework. Grapes, regions, countries, styles, and pairings were mirrored from the app's Supabase tables into static HTML in July, so the site and the app describe the same wine the same way. The course pages are built from one syllabus file, 26 units in English and 26 in Danish.

There is no build step to break and nothing to host. GitHub Pages serves the files.

Free, then paid, then both

The free tier was one scan a week in June, then nothing at all: the app launched free with the paywall behind a flag, so review and launch could not be blocked by billing. Two weeks after launch, 1.1 turned subscriptions on and settled the free tier at five scans per rolling 30 days, counted on the server.

In October the course went on the web with its own checkout, so a lesson can be bought without the app.

Next case

Cardzo