DEV Community

naoki_JPN
naoki_JPN

Posted on

How I Built a Personal Training Coach in One Week

Note: This article reflects information as of April 2026.

Introduction

"What if I had an AI coach that knows all my Garmin data?"

That thought sparked Stride Mate — a personal training coach app. The first commit was on April 17th, and one week later it has Square billing, Garmin online sync, and a usage dashboard running.

This is the 7-day development journal.


What is Stride Mate?

Stride Mate (https://stride-mate.vercel.app) is an AI coach you talk to via LINE, backed by your Garmin training data.

  • "How was my training last week?"
  • "What should I do this week for my upcoming race?"
  • "My HRV has been low lately — should I rest?"

It answers these questions by referencing your actual activity history, heart rate, and sleep data.

Tech Stack

Layer Tech
Chat UI LINE Messaging API
Web (Dashboard) Next.js 15 / Vercel
Background Sync Node.js / Railway
DB Supabase (PostgreSQL)
AI Claude API (Haiku / Sonnet)
Billing Square Subscriptions
Garmin garmin-connect (unofficial API)

Day 1 (4/17): Getting the MVP Running

From design to implementation

Created the repo at 1pm. Locked in the architecture in 3 hours, pushed MVP by 5pm.

What the MVP had working:

  • LINE Webhook -> Claude API -> reply
  • Supabase Auth (LINE OAuth)
  • Garmin ZIP upload from dashboard
  • Activity storage and RAG search

The deployment grind

Railway Nixpacks could not resolve the @tcb/shared monorepo package. Switched to a Dockerfile build. Finally deployed after 8pm.


Day 2-3 (4/18-19): Auth Bug and ZIP Import Fix

The ES256 problem with LINE OAuth

Could not log in the next day. Supabase custom OIDC does not support ES256. LINE JWKS returns ES256, Supabase expects HS256.

Solution: manually implemented LINE OAuth in a Next.js API route.

ZIP import OOM

Garmin export ZIPs can be several GB. Loading the whole thing into memory crashed with OOM.

Fixed by fetching via signed URL and processing as a stream instead of using storage.download().


Day 4-5 (4/20-21): Architecture Rethink

Dropped RAG

Started with vector embeddings, but direct SQL was more accurate for Garmin data.

"Last week mileage" or "30-day HRV trend" is answered precisely with WHERE date BETWEEN, not vector similarity. Deleted the embeddings table and all RAG logic.

Garmin Online Sync Problem

Added auto-sync from Garmin Connect at 3am daily. But initial full fetch works, incremental diff sync is unstable. Sessions expire, date ranges are limited.

Strava fills the gap

Strava has an official OAuth API with Webhooks. Since Garmin devices sync to Strava automatically, every run flows into Stride Mate too.

  • Initial full import: Garmin ZIP
  • Daily incremental: Strava Webhook + official API

Day 5-6 (4/21-22): Square Billing

The webhook that was never registered

Billing was implemented but the plan never switched. Investigation: Square Webhooks had never been registered.

Perfect code means nothing if the webhook endpoint is never registered in Square's dashboard.

Square cancellation event gotcha

I was handling subscription.canceled — an event type that does not exist.

Cancellations come through subscription.updated with status === "CANCELED".

case "subscription.updated": {
  if (sub.status === "CANCELED" || sub.status === "DEACTIVATED") {
    await db.from("users").update({ plan: "free", plan_expires_at: null })
      .eq("square_customer_id", sub.customer_id);
    break;
  }
}
Enter fullscreen mode Exit fullscreen mode

Day 7 (4/24): Dashboard Consolidation

Tried tabs, got feedback that tabs are conceptually the same as separate pages. Removed them. Everything on one scrolling page.


7 Lessons from 7 Days

  1. Register webhooks, not just write handlers
  2. Structured data needs SQL, not RAG
  3. Stream from the start
  4. Implement non-standard auth manually

Current State

Working: LINE chat with Garmin data, ZIP bulk import, Garmin auto-sync, Strava integration, Square billing, usage dashboard.

Currently just me and a few friends. If you use Garmin, give it a try. Reactions will determine how far this gets built out.

https://stride-mate.vercel.app


Closing

Building with Claude Code, I kept running into the same pattern: code works, configuration missing. The skeleton comes together fast now — but the connective tissue (webhook registration, spec edge cases) still bites just as hard.

Repo: https://github.com/bokuno-studio/training-coach-bot

Top comments (0)