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;
}
}
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
- Register webhooks, not just write handlers
- Structured data needs SQL, not RAG
- Stream from the start
- 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.
Top comments (0)