← Posts
July 20, 2026building-in-publicai-agentsmobile-devcalli

I Built a Chinese Learning App Using AI — Here's Exactly How

The full story of building calli: a Chinese character stroke-order practice app for iOS, Android, and the web, built almost entirely with AI tools in a few weeks.

A few months ago I was learning Chinese and getting frustrated with the options.

Duolingo gamifies everything to the point where it stops feeling like learning. Apps that focus on stroke order are clunky and old. And the AI-powered conversation tools are great, but they assume you already know the characters.

So I built one.

The app is called calli. It teaches you to write Chinese characters from HSK 1 through 6 — 2,663 characters total — by having you draw each stroke on screen in the correct order. You get real-time pass/fail feedback as you go, a session-based practice queue that surfaces characters you haven't practiced or keep getting wrong, and a freemium model where HSK 1 is free and everything else is a $6.99/month subscription.

Here's how I built it, and how much of it AI actually wrote.

The stack

The app is a pnpm monorepo with three main pieces:

  • Web: Next.js 15, deployed to Vercel
  • Mobile: Expo / React Native 0.81, built and distributed via EAS Build
  • Backend: Supabase — Postgres for data, Auth for user sessions, Edge Functions for subscription webhooks

The interesting part is the @calli/stroke-engine package, which is the shared library both the web and mobile apps use for matching strokes. It's pure TypeScript with no runtime dependencies, which matters a lot on mobile where bundle size and offline reliability are real concerns.

What AI actually did

The honest answer: a lot.

I use Claude Code as my primary interface. Most sessions look like: I describe what I want to build, paste in the relevant files, and work through the implementation iteratively. I'm not copy-pasting code blindly — I read everything it produces and push back when something is wrong — but the gap between "this exists in my head" and "this exists in the codebase" is much smaller than it would be writing everything myself.

A few specific things where AI carried most of the weight:

The stroke matching algorithm. I described the coordinate system (hanzi-writer uses a 900×900 grid with origin at bottom-left), explained what "close enough" should mean for a stroke comparison, and Claude worked out the normalization math and direction scoring logic. I tested it against real characters, flagged edge cases, and iterated until it felt right.

The Supabase schema and RLS policies. Designing the tables for users, characters, quiz_sessions, and practice_sessions, along with row-level security policies that make sure users can only see their own data, is exactly the kind of thing AI is great at. The logic is mechanical once you know what you want — I described the access patterns and it wrote the SQL.

The RevenueCat integration. Wiring up in-app purchases to unlock subscription content involves a surprising amount of boilerplate: webhook handlers for purchase events, trial logic, entitlement checks on both platforms. I described the behavior I wanted and AI wrote most of the implementation.

The Metro bundler workaround. React Native's Metro bundler can't resolve module paths dynamically. We're loading HSK character data (JSON files with stroke paths for every character at every level) at runtime, but you can't do require(\./assets/hsk-data-$.json`)`. The fix was splitting the data into static per-level imports. Claude figured this out after I explained the constraint.

What AI couldn't do

There were things where I had to make the call myself.

Product decisions — what's free vs. paid, how practice sessions are weighted, what the UX should feel like when you fail a stroke — none of that came from AI. It gave me options when I asked, but deciding what was actually right for this app required thinking about what I'd want as the user.

The coordinate space bugs were a nightmare to debug. Strokes that looked correct were failing because the canvas coordinate origin (top-left) is different from the hanzi-writer coordinate origin (bottom-left). AI wrote the transform, but figuring out why strokes were off and which transform was wrong required me staring at SVG paths and pixel positions until the mental model clicked.

And anything that touched the Apple review process or the Expo / EAS ecosystem required reading actual documentation. AI's knowledge of the EAS build pipeline had gaps, especially around the newer SDK versions and the plugin configuration for native modules like RevenueCat.

Where things stand

The app is live on web, and the mobile build is running on my own devices while I finish the App Store submission. HSK 1 is free, no account required. Pro unlocks everything else.

If you're learning Chinese (or thinking about it), try it out. And if you're building something similar and want to compare notes on the AI workflow, I'm writing more about the specific pieces — stroke recognition, the Expo build setup, subscriptions — in the posts below.