Web Platform · Completed

Floofy

A personal Garmin workout builder and Claude training-plan importer. It replaces Garmin Connect's clunky step editor with a manual builder and a deterministic pipeline for expanding Claude-generated plans into pushable sessions.

Client

Personal Project

Year

2025

Duration

MVP + Backend Phase

Role

Solo Developer

339 Tests
Test Coverage
expand → classify → push
Core Loop
Live-Verified
Garmin Adapter
Zero (Mock Mode)
Config Required
Floofy

What it does

Floofy is a solo side project built to get away from Garmin Connect's clunky step editor. You can hand-build structured workouts with steps, repeats, and targets, or paste a Claude-generated `floofy_plan_v1` plan. The app deterministically expands the plan into concrete sessions, classifies them, and lets you push the structured ones to Garmin. It runs on a layered seam architecture with a typed DI container, so in-memory mocks swap for real Supabase and Garmin backends without touching the UI.

The problem

Garmin Connect's step editor is clunky. Building structured workouts by hand is tedious, and importing an LLM-generated training plan usually means manually translating prose into individual sessions. Floofy fixes this with a manual builder and a deterministic importer for Claude-generated plans.

How it works

The architecture uses a layered seam design with a lightweight typed DI container. Flow: ui → viewmodels → repository interfaces → concrete implementation. Concrete implementations bind via DI, so in-memory mocks stand in for the MVP and swap for real backends without touching UI, viewmodel, or domain code. Zod schemas are the single source of truth for domain types, shared unmodified between client and server.

The core is a deterministic transformation pipeline. Plan JSON goes through expand (weekly_template[day] ⊕ progression[week]), resolve (attach scheduledDate), classify (structured | open | display_only), select, compile (machine block → Garmin payload), and push (addWorkout/scheduleWorkout). No prose parsing anywhere in this path. Only the machine "structured" block Claude attaches to each session is ever compiled.

Garmin I/O is isolated behind a single server-side adapter over the unofficial @flow-js/garmin-connect package, invoked only from Next.js API routes. Garmin credentials and tokens never reach the client. With zero backend configuration, the app runs entirely on in-memory mocks. Configuring Supabase swaps in a real, RLS-scoped PlanRepository. Configuring real-mode swaps the Garmin push/session seams to the real adapter with an encrypted, per-user token vault in Supabase.

Status

The MVP is complete and running end-to-end on mock data. The backend phase is done: Supabase schema/RLS/RPCs, Supabase auth, the encrypted per-user Garmin token vault, and the real adapter are implemented and env-gated. The real Garmin adapter has been live-verified end-to-end against a real account — from login to createWorkout to read back and deleteWorkout, leaving no residue behind.

Not yet real-verified: scheduleWorkout (the actual calendar write) and on-watch confirmation are unit-tested only. The Garmin API used is unofficial — an internal, session-authenticated workout-service endpoint, not Garmin's official developer program. It can change without notice and carries real ToS risk if used for anyone other than the account owner. The project has ~339 tests across domain, pipeline, DI, features, ui, and infra. There is an optional unobtrusive tip jar toward the author's Absa Cape Epic mountain bike stage race entry.

Built with

Next.jsReactTypeScriptzodSupabaseTailwind CSSvitest

The screens

The build log

Development updates

No development updates yet. Check back soon for progress updates and insights.

More work

Related projects