An AI-powered Irish-language oral exam coach for Leaving Cert students. Built in three stages: Node.js proof-of-concept → native Swift/iOS prototype → public landing page at vivaready.com.
🎨 See the full app designs in Figma → VivaReady Figma file Every screen referenced in this case study — Home, Sraith Pictiúr, Quick Drills, feedback, onboarding, and the full design system — lives there.
VivaReady helps Irish secondary-school students prepare for the Leaving Cert Irish (Gaeilge) oral exam — the Béaltriail. Students practise speaking Irish into their phone and get real-time, structured feedback on grammar, vocabulary, fluency and pronunciation. The product covers the full set of exam tasks: the Sraith Pictiúr picture stories, conversational drills, and timed mock exams.
This repository is a public case study — a write-up of how the product was built, the technical decisions behind it, what I learned, and what's next. The application source code lives in private repositories.
The Irish oral exam is worth ~40% of the Leaving Cert Irish grade, but realistic practice is hard to get:
- One-to-one practice is scarce. Most students get a few rehearsals with a teacher, at best.
- Self-study has no feedback loop. Reading vocab lists doesn't build fluency, and there is no way to know if your pronunciation, grammar, or sentence structure is examiner-ready.
- Existing tools are static. Workbooks and audio recordings don't respond to what the student actually says.
- AI tooling for Irish lags behind English. Until recently, Irish-language speech recognition was not accurate enough to support a conversational coaching loop.
The hypothesis: a phone app that lets a student hold a real spoken conversation in Irish and get instant, structured feedback could close that gap.
The project deliberately moved from "cheap to throw away" to "real product" in three stages.
A throwaway command-line script written in Node.js 18+. One terminal, one microphone, one continuous loop:
speak → transcribe → LLM responds in Irish → speak the response → repeat
The purpose was risk reduction, not product. Before committing to a native iOS build, the POC had to answer three questions:
- Is Irish-language speech-to-text accurate enough for a conversational product?
- What is the realistic end-to-end latency from "stop speaking" to "hear reply"?
- Is the experience actually pleasant, or does the awkward turn-taking kill it?
Building this in a few hundred lines of JavaScript was the cheapest way to find out. Building the iOS UI first would have hidden the real risk.
Once the loop was proven, the experience was rebuilt natively in Swift and SwiftUI using an MVVM architecture. Scope expanded well beyond a chat loop into a structured exam-prep product: Sraith Pictiúr narration with follow-up Q&A, topic-based Quick Drills, per-session feedback and scoring, and a Home / Tab navigation shell. The single-script POC became a Core/Services + Features/* layout where each feature owns its View and ViewModel and shares a small set of speech, LLM, and storage services.
A public marketing site at vivaready.com, built with React 18, TypeScript, and Vite 5, deployed on Vercel. It explains the product, captures waitlist signups, and hosts the Privacy / Terms / Contact pages. Designed in Figma, exported through Figma Make, then hardened with real routing and a serverless waitlist endpoint that proxies to MailerLite.
- Continuous microphone capture (16 kHz mono PCM 16-bit)
- Real-time Irish STT with partial and final transcripts
- LLM reply in Irish, deliberately short to keep turn-taking snappy
- TTS playback of the reply
- Clean shutdown; graceful degradation if TTS is unavailable
- Custom tab-bar navigation shell (Home, Sraith Pictiúr, Quick Drills, Profile)
- Sraith Pictiúr: story selection → preparation → narration → follow-up Q&A → session log
- Quick Drills: topic-based drill sessions → per-question detail → session summary
- AI feedback per answer: summary, strengths, improvements, score, model answer in Irish + English, category scores for grammar and vocabulary
- Connectivity gate at app root with auto-reconnect overlay (the product is useless offline)
- Hero + four-feature marketing layout
- Waitlist email capture → MailerLite
- Contact form, Privacy Policy, Terms of Service
- Mobile-responsive design with in-app screenshots
![]() Home Streak, stats, practice modes |
![]() Mock Oral Exam Real-time conversation with AI feedback |
![]() Sraith Pictiúr Picture-story narration + follow-up Q&A |
![]() Progress Analytics, streaks, improvement tracking |
For the full set of designs (including onboarding, settings, and the design system), see the VivaReady Figma file.
┌────────────────────────────────────────────────────────────────┐
│ iOS App (Swift) │
│ │
│ VivaReadyApp ──▶ MainTabContainerView │
│ │ │
│ ├─ Home │
│ ├─ Sraith Pictiúr (select → prepare │
│ │ → narrate → follow-up) │
│ ├─ Quick Drills (topics → session │
│ │ → detail → summary) │
│ └─ Profile │
│ │
│ Core / Services │
│ ├─ SpeechmaticsSTTService (WebSocket + AVAudioEngine) │
│ ├─ LLMClient (OpenAI Responses API) │
│ ├─ DrillsService / QuickDrillBank │
│ ├─ ProfileStore │
│ └─ ConnectivityMonitor │
└────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
Speechmatics Realtime (eu2) OpenAI Responses API
┌────────────────────────────────────────────────────────────────┐
│ Landing site — React + Vite on Vercel │
│ LandingPage / Contact / Privacy / Terms │
│ │ │
│ └─▶ /api/waitlist ──▶ MailerLite Subscribers API │
└────────────────────────────────────────────────────────────────┘
Per-turn data flow (POC and iOS): mic PCM → WebSocket → partial transcripts (UI) → final transcript → LLM call → structured feedback → render (POC also plays TTS).
More detailed diagrams (iOS internals, per-turn sequence, request flow, STT service boundary) live in architecture/architecture.md.
flowchart LR
subgraph Client["Client (iOS / CLI POC)"]
Mic["🎤 Microphone<br/>16kHz mono PCM"]
UI["UI Layer<br/>SwiftUI / Terminal"]
Audio["Audio Engine<br/>AVAudioEngine / mic"]
WS["WebSocket Transport"]
end
subgraph External["External Services"]
STT["Speechmatics<br/>Realtime STT<br/>(ga)"]
LLM["OpenAI<br/>gpt-4o-mini"]
TTS["ElevenLabs<br/>Multilingual TTS<br/>(POC only)"]
end
subgraph Web["Landing — vivaready.com"]
React["React + Vite<br/>on Vercel"]
API["/api/waitlist<br/>(serverless)"]
ML["MailerLite"]
end
Mic --> Audio --> WS --> STT
STT -- "partial + final transcripts" --> UI
UI -- "user turn" --> LLM
LLM -- "structured feedback / reply" --> UI
UI -. "POC only" .-> TTS
TTS -. "audio" .-> Audio
React --> API --> ML
Detailed reasoning lives in docs/technical-decisions.md. Highlights:
- Validate with a CLI before writing Swift. The cheapest way to find out whether Irish STT was viable and whether the latency was acceptable. The POC was discarded; the lessons were not.
- Speechmatics for Irish STT, not Apple's
SFSpeechRecognizer. Apple's recognizer was tried first; Speechmatics was more accurate forgaand exposed tunable real-time partials. - WebSocket streaming, not batch STT. Sub-second partial transcripts are essential for conversational feel. Request/response STT would have killed the UX before the product had a chance.
gpt-4o-minifor both POC and app. Cost and latency favoured the small model; outputs are short enough that larger models were not justified.- OpenAI Responses API in iOS (not Chat Completions). Better suited to structured feedback output.
- MVVM with a Core / Features split in Swift. Each feature owns its View and ViewModel; shared services live in
Core/Services. Made it easy to add new features without touching the speech pipeline. - MailerLite via a single Vercel serverless function. Keeps the API key server-side. No separate backend to operate.
- Figma Make → React export, then hardened. Faster path from design to deployable site than hand-building.
Measured during POC development on a residential connection in Ireland, against eu2.rt.speechmatics.com.
| Stage | Time | Notes |
|---|---|---|
| Partial transcript | 0.2 – 0.7 s | Speechmatics realtime, partials enabled, 250 ms max delay |
| Final transcript | ~1 s | After the user stops speaking |
LLM reply (gpt-4o-mini) |
1 – 3 s | Capped to ~80 tokens to keep replies snappy |
| TTS playback start | near-immediate | ElevenLabs multilingual, streamed audio |
| Total perceived turn | ~2 – 4 s | Stop-speaking → hear-reply |
What moves the needle:
- LLM token budget is the biggest UX lever. Short Irish replies (~80 tokens) keep the loop conversational; longer responses break the rhythm. Token count is a UX dial, not just a cost dial.
- Region matters. Picking the EU Speechmatics endpoint cut WebSocket RTT noticeably from Ireland.
- Audio format that worked: 16 kHz mono PCM 16-bit little-endian end-to-end. Higher sample rates added cost with no accuracy benefit.
- Irish STT is good enough to build on, but partial transcripts occasionally need cleanup at the final boundary to handle dialectal pronunciation. The iOS service stitches a sentence from collected words rather than trusting the last partial verbatim.
- Landing page: live at vivaready.com; waitlist open.
- iOS app: prototype complete with Home, Sraith Pictiúr, and Quick Drills flows wired end-to-end against Speechmatics + OpenAI; not yet in TestFlight.
- Designs: full app designs in Figma — every screen, plus the design system.
- POC: retired. It served its purpose.
The longer-form version is in docs/lessons-learned.md. The short version:
- POC-first saved weeks. Two evenings of Node.js proved that the entire product idea was viable. Had the speech loop been too slow or Irish STT too weak, I would have known before writing any Swift.
- Streaming is non-negotiable for conversational UX. Anything that waits for a full audio file before producing text is too slow to feel like a conversation.
- Token budget is a UX dial. Capping the LLM to short replies made the product feel snappy. The same model with longer answers feels broken.
- Don't ship secrets in
Info.plist. Anything in the iOS bundle is trivially extractable; secrets belong in a build-time config or behind a server-side proxy. - Designer-to-code pipelines (Figma Make) are a fast path for a landing page, but they leave artefacts you have to manage. Worth it for speed; not worth it for the core product.
- A connectivity gate is a feature, not a bug. If your app is useless offline, fail loudly and politely instead of letting individual features mysteriously break.
- On-device or hybrid STT to remove the network round-trip for partials. Apple's
SFSpeechRecognizerforga-IEhas improved; a hybrid mode (on-device partials + cloud final) could shave 200–400 ms per turn. - Speaker-side TTS in the iOS app so students can hear the model answer pronounced (the POC had this; the iOS app does not yet).
- Pronunciation-level scoring, not just grammar and vocabulary. Requires a phoneme-aware approach beyond what the current LLM feedback gives.
- Offline drills mode for vocabulary and grammar that doesn't need a live LLM.
- Teacher dashboard so a class teacher can assign drills and see anonymised progress.
- Move all secrets behind a thin backend instead of the current direct-from-app API calls. Reduces key-rotation pain and lets us add rate limiting, abuse protection, and usage tracking.
- TestFlight beta with Leaving Cert students and Irish teachers in late 2026.
This repository is a public case study intended as a portfolio piece. It contains write-ups, diagrams, and screenshots only.
The full source code for the iOS app, the POC, and the landing page lives in private repositories and is not included here. Code snippets, prompts, scoring rubrics, exam content seeds, and any business-sensitive implementation details have been intentionally omitted.
Product names, branding, and the Leaving Cert curriculum references belong to their respective owners. This case study is not affiliated with the State Examinations Commission.



