← all posts

Speakit Ships to TestFlight and Learns to Wind

2026-07-06/Speakit

Two weeks ago Speakit could talk. Last week it got a name and a spine. This week it did the thing that changes how a project feels: it got onto a real phone through TestFlight. Everything else — a redesigned home screen, a new listening exercise, phonetics practice, streaks — was in service of that shift from "a thing I demo on my laptop" to "a thing I can hand someone." Nothing sharpens your priorities like knowing a stranger is about to open build one.

Getting Past Apple's Front Door

TestFlight is where an iOS app stops being code and starts being a submission, and Apple's front door has a specific set of locks. I spent a chunk of the week clearing ITMS rejection codes one at a time: an app icon and CFBundleIconName (90022/90023/90713), the export-compliance declaration ITSAppUsesNonExemptEncryption=false (90683's cousin), and an NSCameraUsageDescription string so the binary wouldn't get bounced for touching the camera without explaining why.

None of these are hard. All of them are invisible until the validator throws them back at you, and each round trip costs you an upload-and-wait. The lesson I relearn every time I ship an iOS app: the last 5% of "done" is a checklist of Apple metadata you can't skip and can't guess. Better to treat it as its own task than to be surprised by it at 11pm when you just wanted to send a build to a friend.

A Home Screen That Winds

The old home tab was a static list — functional, forgettable. This week I replaced it with something I'm calling Meander: the current unit's lessons wind down a dashed path, node by node. A green check means done, one coral node with a "Start" pill marks where you are, an amber dashed star is a review, and dimmed nodes are locked ahead of you. It's the mental model every learning app converges on for a reason — you can see progress as distance travelled, and the next step is obvious without reading anything.

Then I pushed it further: instead of showing one unit at a time with a teaser card for the next, the path now renders every unit in the active level as a continuous scroll, each with its own pinned bar (numbered, or a green check when complete). Unit details load concurrently and the view auto-scrolls to wherever you left off. The teaser card is gone because it's redundant — the next unit is just further down the same path.

Alongside it I built a course/level switcher, since progress lives server-side per course and switching is non-destructive. That's the part that makes adding Spanish A2 or a second language feel free rather than fraught. A fair amount of the week was the unglamorous polish this kind of screen demands: a pill title that read blurry because a hard offset shadow was duplicating the text three points down (moved the shadow onto the capsule shape, not the whole button), horizontal breathing room for the winding wire, and shuffling the week strip into place. Custom-drawn UI is all edge cases until it isn't.

New Ways to Practice

Content and activity types were the other big thread. I added a tap-what-you-hear listening activity end to end — database, admin authoring, web, and iOS — where you assemble a spoken phrase from a word bank. The interesting design call was failure: the first version locked the chips after a wrong check, which is punishing and teaches nothing. I reworked it so the chips stay interactive, a wrong answer says "Not quite" and lets you retry without revealing the phrase, and only after three misses does a "Show answer" link appear and unlock Continue. Retry-not-punish is a better default for anything a learner is meant to learn from.

I also introduced a phonetics / pronunciation-foundation unit type with on-device text-to-speech for the sounds, plus a Speakit-styled quiz UI for it. Doing TTS on-device for phonetics keeps it instant and offline — no network round trip to hear a vowel. And I seeded Spanish A1 unit 3, "Free Time & Culture": five lessons and forty activities covering free-time verbs, gender and articles, regular present tense with hacer, and porque / para / querer + infinitive — every renderable activity type exercised at least once, including the new listening one in each lesson. Original content, textbook topics only.

Streaks and Live-Call Cleanup

Streaks got real this week: they now count completed lessons, wire into the iOS home screen, and I fixed a row-cap bug that was quietly truncating the count. A streak is a cheap, honest retention mechanic — but only if the number is trustworthy, so the bug fix mattered more than the feature.

The live-call feature from previous weeks got a round of refinement too. I fixed doubled text on the "Free conversation" card, collapsed the pronunciation review down to a single clear CTA, unblocked the chat composer, corrected a TTS onset issue, tidied furigana spacing, and added a voice picker so you can choose your tutor's voice. Small cuts, but they're the difference between a feature that demos well and one that survives daily use.

What's Next

Speakit is now something I can give people, which reframes everything: the next round of work is whatever the first testers trip over. But the shape is holding — a home screen that makes progress legible, activity types that teach instead of punish, content flowing through the course engine, and streaks that count. The plumbing has been done for a while. Now it's on a phone, and that's where the real feedback starts.

#ios#swift#testflight#design#ai