· 2 min read

Court Canvas: from React prototype to native Swift

A tactical tennis whiteboard went from Claude artifacts to a Next.js PWA to an App Store rejection to a SwiftUI rewrite.

Court Canvas is a free tactical whiteboard for tennis and pickleball coaches — drop player markers on a court, draw arrows, save plays, export GIFs. What sets it apart from a generic drawing app is the ATP-backed shot-probability heatmaps: where pros actually hit from each position. No login, works on your phone, built to use courtside between sets.

It started as a React component I iterated on with Claude in artifacts, then grew into a Next.js app on Vercel — Tailwind UI, i18n across English/Spanish/French, 252 tests, full PWA support with an offline fallback. The web version is solid. Shipping it to iOS is where it got interesting.

The Capacitor detour

I took the shortcut: wrap the React app with Capacitor, keep one codebase. Got it building, submitted it. Apple bounced it — classic wrapped-PWA Guideline 4.2 risk. Worse, in-app purchase through the Capacitor StoreKit plugin was broken: tap “Unlock Pro,” get “Store unavailable.” The JS-to-native bridge fought me the whole way.

Rewriting in SwiftUI

Rather than keep debugging a bridge I didn’t trust, I started fresh in SwiftUI — real StoreKit 2 for the $4.99 Pro unlock, native touch drawing that feels like drawing, actual native code Apple doesn’t have to squint at, iPhone-only to keep the build simple.

The IAP bug that ate days turned out to be mundane: a missing .entitlements file, and a literal $ I’d typed into the StoreKit price field. Fixed both and sandbox purchases went through first try. Days of debugging, seconds of fixing. I leaned on Claude Code hard through the rewrite — .pbxproj edits, entitlements, build settings — basically “describe the rejection, let it dig.”

The lesson: Capacitor saved weeks on v1 and cost them back on the rejection-and-IAP loop. For a touch-heavy drawing app where Pro conversion matters, native was always the destination. Capacitor first just meant learning that the hard way.

← all notes