I Built a Voice-First AI Sports Journal (And Why I Didn't Over-Engineer It) A developer built Trainlog, a voice-first AI sports journal that uses speech recognition and large language models to help users log workouts without friction. The project uses a deliberately simple stack—React 19, Firebase, Tailwind, Zod, and Groq—and incorporates human-in-the-loop transcription editing and strict schema validation to ensure reliability. The developer shared seven technical lessons learned, emphasizing the importance of avoiding over-engineering in AI applications. There is a fundamental problem with fitness tracking apps today: friction. After a grueling workout, when your hands are sweaty, your heart rate is 160 BPM, and you are trying to catch your breath, the absolute last thing you want to do is open an app, navigate through five different dropdown menus, search for the exact exercise variant you did, and type out how you felt on a tiny mobile keyboard. Because of this friction, most of us either default to raw data Apple Watch rings which lacks context, or we stop tracking our subjective experiences entirely. That’s why I built Trainlog — a voice-first sports reflection journal that uses AI to understand your training through natural speech. You just hit record, talk about your session for 15 seconds " But this isn't just a product pitch. Building an AI wrapper in 2024 is easy. Building a production-ready AI application that feels native, respects user privacy, and doesn't break when the LLM hallucinates is hard. When planning the architecture, it was tempting to fall into the classic AI trap: vector databases, multi-agent orchestrators, and infinite LangChain wrappers. Instead, I chose a boring, robust stack: React 19, Firebase, Tailwind, Zod, and Groq Whisper + Llama 3 . Here are the 7 technical lessons I learned building a resilient AI app without over-engineering it. The core interaction of Trainlog relies on the browser-native MediaRecorder API to capture audio, which is then sent to a Vercel Serverless Function and processed by Groq's Whisper model. Whisper is incredibly fast and accurate, but ambient gym noise or heavy breathing can still confuse it. Initially, I passed the transcription directly to the AI for analysis. This was a mistake. If the transcription missed a "not" e.g., "I did not sleep well" , the entire analysis was poisoned. The Fix: Human-in-the-loop. Before any AI analysis happens, the user is presented with the raw transcription in an editable text area. They can fix typos or add context before hitting "Analyze". Never trust raw sensory input without giving the user the steering wheel. When you start building AI features, every tutorial tells you to use a heavy agentic framework. But for extracting structured data Activities, Intensity, Fatigue level, Emotions , a massive agent graph is pure overhead that introduces latency and points of failure. Instead of an agent, Trainlog relies on Strict Schema Validation . I defined the exact TypeScript interfaces I needed for the frontend, translated them into a Zod schema, and instructed Llama 3 to output raw JSON matching that exact shape. When the response hits the server, Zod parses it. If the LLM hallucinates a string where a number should be for the "fatigue" metric, Zod catches it, the server gracefully handles the error, and the UI doesn't crash. Strict boundaries Complex Agents. I wanted Trainlog to feel like a native app on iOS and Android — full screen, no browser UI, offline capabilities, and an app icon on the home screen. Instead of rewriting the codebase in React Native or Swift, I used vite-plugin-pwa . With a simple manifest.json and a Service Worker, Trainlog became installable. To make it feel truly native, I implemented Mobile-First UI patterns: bottom navigation bars on mobile, side navigation rails on tablets/desktop md:flex-row , and smooth CSS micro-animations animate-in fade-in . If you are a solo developer building a consumer app, don't build two codebases. Build a spectacular PWA. One of the biggest missing pieces of Web Apps used to be engagement. Users forget to log their workouts. I needed a daily reminder system. Thanks to Firebase Cloud Messaging FCM and the Web Push API, Trainlog now asks for notification permissions during the onboarding flow. If granted, the frontend registers a Service Worker firebase-messaging-sw.js , securely stores the FCM token in Firestore, and a Vercel Cron Job runs every evening at 20:00 to ping users who haven't logged a session that day. Getting Apple to play nice with Web Push on iOS was a journey, but it works flawlessly now. When you expose an endpoint that calls an LLM, you are essentially putting an open tab at a bar on the internet. If a malicious user or a bot spams your /api/analyze endpoint, your API bills will skyrocket. I didn't want to build a complex queueing system, so I reached for Upstash Redis . With just a few lines of code in the Vercel Serverless functions, I implemented a sliding window rate limiter. If an IP or User ID requests more than 10 analyses per minute, the server responds with a 429 Too Many Requests . It’s a 5-minute implementation that saves you from a massive headache. Health and fitness apps love "Streaks" doing something for X consecutive days . But streaks are psychologically punishing; if you get sick or take a rest day, losing a 100-day streak feels devastating and often causes users to abandon the app entirely. For Trainlog, I built a gamification system based on Milestones and Exploration, not just consistency . Users earn badges for emotional resilience logging after a bad day , exploration trying new activities , and total volume, rather than punishing them for breaking a chain. The logic is handled seamlessly on the backend when saving an entry, comparing their historical Firestore data against achievement criteria. When dealing with health data, sleep metrics, and personal reflections, privacy is paramount. I architected Trainlog with strict Firebase Security Rules to ensure per-user data isolation. But more importantly, I applied PII Sanitization before the data ever reaches the LLM. Regular expressions scrub potential phone numbers, emails, or IDs from the voice transcripts before they are sent to Groq. Furthermore, no entry is ever saved to the database until the user explicitly reviews the AI's analysis and clicks "Confirm". If they delete their account, a cloud function wipes their entire Firestore document tree instantly. If there’s a single takeaway from building Trainlog, it’s this: Start with the smallest stack that solves the problem. I didn't need a vector database because users can filter their calendar history by date and activity natively in the UI. I didn't need a heavy memory layer because the AI Coach "Anna" simply loads the user's recent Firestore history into the system prompt context window. Complexity is easy to add and brutally hard to remove. The best AI apps aren't the ones with the most impressive architecture diagrams. They are the ones that solve a real user problem with zero friction. I’ve open-sourced the entire project. You can check out the code, steal the prompt architecture, or just use the app to track your next gym session. I’d love to hear your thoughts Have you fallen into the AI over-engineering trap recently? Drop a comment below or let me know what you think of the app. Happy coding and training 🏋️♂️