{"slug": "i-built-a-voice-first-ai-sports-journal-and-why-i-didn-t-over-engineer-it", "title": "I Built a Voice-First AI Sports Journal (And Why I Didn't Over-Engineer It)", "summary": "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.", "body_md": "There is a fundamental problem with fitness tracking apps today: friction.\n\nAfter 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.\n\nBecause 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.\n\nThat’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 (\"\n\nBut 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.\n\nWhen 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)**.\n\nHere are the 7 technical lessons I learned building a resilient AI app without over-engineering it.\n\nThe core interaction of Trainlog relies on the browser-native `MediaRecorder`\n\nAPI to capture audio, which is then sent to a Vercel Serverless Function and processed by Groq's Whisper model.\n\nWhisper 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.\n\n**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.\n\nWhen 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.\n\nInstead 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.\n\nWhen 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.\n\nI 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.\n\nInstead of rewriting the codebase in React Native or Swift, I used `vite-plugin-pwa`\n\n. With a simple `manifest.json`\n\nand 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`\n\n), and smooth CSS micro-animations (`animate-in fade-in`\n\n).\n\nIf you are a solo developer building a consumer app, don't build two codebases. Build a spectacular PWA.\n\nOne of the biggest missing pieces of Web Apps used to be engagement. Users forget to log their workouts. I needed a daily reminder system.\n\nThanks 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`\n\n), 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.\n\nGetting Apple to play nice with Web Push on iOS was a journey, but it works flawlessly now.\n\nWhen 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`\n\nendpoint, your API bills will skyrocket.\n\nI 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`\n\n. It’s a 5-minute implementation that saves you from a massive headache.\n\nHealth 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.\n\nFor 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.\n\nWhen dealing with health data, sleep metrics, and personal reflections, privacy is paramount.\n\nI 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.\n\nRegular 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.\n\nIf there’s a single takeaway from building Trainlog, it’s this: **Start with the smallest stack that solves the problem.**\n\nI 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.\n\nComplexity 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.\n\nI’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.\n\nI’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)! 🏋️♂️", "url": "https://wpnews.pro/news/i-built-a-voice-first-ai-sports-journal-and-why-i-didn-t-over-engineer-it", "canonical_source": "https://dev.to/druedaro/i-built-a-voice-first-ai-sports-journal-and-why-i-didnt-over-engineer-it-2i27", "published_at": "2026-08-28 11:15:08+00:00", "updated_at": "2026-08-28 11:50:46.213819+00:00", "lang": "en", "topics": ["ai-products", "ai-tools", "developer-tools", "machine-learning", "natural-language-processing"], "entities": ["Trainlog", "React 19", "Firebase", "Tailwind", "Zod", "Groq", "Whisper", "Llama 3"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-voice-first-ai-sports-journal-and-why-i-didn-t-over-engineer-it", "markdown": "https://wpnews.pro/news/i-built-a-voice-first-ai-sports-journal-and-why-i-didn-t-over-engineer-it.md", "text": "https://wpnews.pro/news/i-built-a-voice-first-ai-sports-journal-and-why-i-didn-t-over-engineer-it.txt", "jsonld": "https://wpnews.pro/news/i-built-a-voice-first-ai-sports-journal-and-why-i-didn-t-over-engineer-it.jsonld"}}