← all posts

Speakit Gets a Name, a Look, and a Course Engine

2026-06-29/Speakit

Last week I dragged a language-learning app from an empty repo to live, spoken AI conversations. This week was about turning that raw capability into something that feels like a product. It got a name — Speakit — a coat of paint that's actually a design system, a real content model so lessons aren't hardcoded, and the live-call feature finally moved off my laptop and into the cloud. Less firefighting than last week, more shaping.

From Prototype to a Product With a Name

A pile of working features isn't a product until it looks like one decision instead of fifty. So I built a Speakit design system — brand tokens, a set of reusable UI primitives, and a restyle of the core surfaces — and applied it on both sides of the stack. The web app got the tokens and primitives first; then I carried the same design language into iOS and reorganized the app around a five-tab shell.

The five-tab structure matters more than it sounds. Last week the app was a collection of capabilities — practice, learn, vocab, live calls — that I'd bolted on as I built them. Giving them a home in a proper tab bar forces you to answer "what is this app for?" out loud. Speakit's answer: you talk, you learn, you review, and you track. Once the navigation said that clearly, a lot of smaller UI choices stopped being arbitrary.

The lesson I keep coming back to: design systems feel like overhead right up until the moment you're styling your fourth screen and realize you're copy-pasting padding values. Doing it now, while the surface area is still small, is the cheap version. Doing it after launch is the expensive one.

A Course Engine Instead of Hardcoded Lessons

Last week's lessons were seeded content — honest test data, but baked in. This week I replaced that with an actual content hierarchy: Course → Level → Unit → Lesson → Activity. That's the spine of any serious learning app, and getting the shape right early means I can add Spanish, Japanese, or anything else later without touching code.

To make that content manageable, I stood up a local admin site — a small internal tool for managing languages and authoring the course tree. It's not glamorous, but the alternative is editing JSON by hand or writing migrations every time I want to add a unit, and that's exactly the kind of friction that quietly kills a content pipeline. Build the tool that lets you create content quickly, and you'll actually create content.

On the iOS side, the app now reads courses over a JSON API and renders a real course browser that walks the full unit/lesson/activity tree. One nice fix along the way: the browser originally only listed courses for the user's target language, which meant published courses in other languages were invisible. Now it lists all published courses, so the catalog reflects what actually exists rather than a filtered slice. Small change, but it's the difference between an app that feels stocked and one that feels empty.

Live Calls Grow Up

The real-time voice feature was the hard-won prize of last week — it worked, but it lived entirely on my machine. This week it moved to the cloud. I added the LiveKit Cloud deploy wiring (a Dockerfile and the agent plumbing), committed a livekit.toml, and shipped the agent to LiveKit Cloud as a deployed worker. The call feature now runs on real infrastructure instead of a process I babysit in a terminal.

With deployment handled, I spent the rest of the time on the call screen itself, which is where the experience either feels alive or feels broken. The big thread was the audio visualizer. I went deep on it — built a flowing, water-like "aura" ring, iterated through hollow rings and two-tone strands for readability, then ultimately swapped over to ElevenLabs' own components: their Orb as one option, and their BarAudioVisualizer for the bars mode. I went back and forth (the bars initially never grew because of a track-binding bug), then simplified hard — dropped the visualizer toggle entirely and kept just the ElevenLabs bars. Classic case of exploring five options to earn the confidence to ship one.

A few quieter robustness wins rounded it out. The call now keeps running when the phone locks, using background audio — because a tutor that hangs up the second your screen dims is useless on a real commute. The transcript only shows final user segments now, so you don't get a distracting flash of romaji before the text settles. And on the agent side I taught it to read the tutor language from job metadata and handle code-switching gracefully, so it stays in the target language even when the learner drifts into English.

What's Next

Speakit went from "a thing that technically talks" to "a thing with a name, a look, and a structure I can grow." The skeleton is in place: a design system to keep it coherent, a course engine to keep it fed, and live calls running on real infrastructure. The next push is content — actually filling that Course → Activity tree with lessons worth doing — and hardening the live call for flaky networks instead of clean Wi-Fi. The plumbing is done. Now it needs to be worth talking to.

#ios#swift#design-system#livekit#ai