App mobile-first per prepararsi a una gara HYROX: genera un piano di allenamento periodizzato, traccia i workout svolti e riceve coaching adattivo basato su AI.
Nasce da un'esigenza personale: prepararmi a una gara HYROX con un piano che tenga conto della data della competizione, del mio livello e dell'attrezzatura che ho a disposizione — cose che le app generiche non fanno. Invece di un foglio Excel, ho costruito un piccolo prodotto completo: generazione del piano, tracciamento, e un coach AI che commenta e adatta il lavoro nel tempo.
È un progetto personale, ma l'ho trattato come un lavoro serio: logica di dominio separata e testata, secret gestiti lato server, tipizzazione stretta e CI. Il codice è pensato per essere letto.
- Genera un piano periodizzato a ritroso dalla data della gara, con le fasi classiche
Base → Build → Peak → Taper. - Adatta i volumi al livello dell'atleta e sostituisce gli esercizi delle stazioni se manca l'attrezzatura.
- Traccia i workout svolti (durata, sforzo percepito, % completamento, note) e i benchmark di performance per stazione.
- Coaching AI (Claude): commenta le sessioni, analizza i punti deboli, chatta nel contesto del tuo piano e propone come adattare la settimana successiva in modo strutturato.
- Mobile-first / PWA, tutto in
localStorage: nessun account, i dati restano sul dispositivo.
Il coach usa il modello Claude Haiku tramite una serverless function (Netlify). Quattro modalità:
| Modalità | Cosa fa | Output |
|---|---|---|
workout_comment |
Commenta un singolo workout appena registrato interpretando RPE e completamento | Testo |
adapt_week |
Propone come adattare la settimana successiva (rispettando la fase: mai caricare in Taper) | JSON strutturato validato con schema |
weak_points |
Analizza i punti deboli a partire da benchmark e log | Testo |
chat |
Coach conversazionale nel contesto del piano e dei dati dell'atleta | Testo |
🔒 La API key non è mai nel client. Vive solo come variabile d'ambiente
ANTHROPIC_API_KEYsul server; il browser chiama/.netlify/functions/coach, mai direttamente l'API di Anthropic. L'adattamento della settimana usa un JSON schema per ottenere un output tipizzato e affidabile invece di parsare testo libero.
- React 19 + TypeScript (strict) — type-safety a compile-time
- Vite 6 — dev server e bundler
- Tailwind CSS v4 — design system a token in
@theme - React Router 7 — routing client-side
- Vitest — test della logica di dominio
- Netlify Functions — proxy serverless verso l'API di Claude
- PWA (
vite-plugin-pwa) — installabile, con asset generati
Separazione netta tra logica pura e presentazione — la stessa filosofia di un service-layer backend: il dominio non sa nulla di React né del DOM, quindi è testabile in isolamento.
src/
├── lib/ # Logica di dominio pura (nessuna dipendenza da React/DOM → testabile)
│ ├── engine.ts # Cuore: generatePlan() costruisce il piano a ritroso dalla gara
│ ├── coach.ts # Client verso la serverless function (la key sta sul server)
│ ├── types.ts # Modello dati (Profile, TrainingPlan, WorkoutLog, ...)
│ ├── storage.ts # Persistenza su localStorage
│ ├── util.ts # Date, formattazione tempi, id
│ └── *.test.ts # Test Vitest della logica
├── state/ # Store globale (Context) con auto-save su localStorage
├── pages/ # Le schermate: Dashboard, Generator, Plan, History, Performance, Coach
├── components/ # Layout (nav desktop/mobile), form riusabili, dettaglio workout
└── index.css # Design system (token Tailwind v4 in @theme)
netlify/functions/
└── coach.mts # Serverless proxy verso Claude — unico posto dove vive la API key
Flusso tipico: Generator crea profilo + piano → Plan mostra le settimane e da lì registri un workout → i log alimentano History e i contatori della Dashboard → il Coach commenta e propone adattamenti.
generatePlan(profile, today)è una funzione pura: dato lo stesso input produce lo stesso piano, quindi è testabile senza mock. I test verificano la somma delle settimane per fase, la presenza del race day, il troncamento della prima settimana e la sostituzione delle stazioni.- Output AI strutturato: l'adattamento della settimana non parsa testo libero ma usa un JSON schema, così il motore può applicarlo con sicurezza di tipo.
- Secret management: nessuna key nel bundle; solo variabile d'ambiente server-side.
uid()non usacrypto.randomUUID(): non è disponibile in contesto non sicuro (http://<ip-lan>), e l'app va provata proprio così dal telefono in LAN.
Serve Node 20+.
npm install
npm run dev # dev server con live reload, esposto in rete (vite --host) per provare da telefono
npm run build # type-check (tsc --noEmit) + build di produzione in dist/
npm run preview # serve la build di produzione
npm run test # esegue i test (Vitest)Per provare dal telefono: npm run dev stampa un URL Network: http://192.168.x.x:5173/ — aprilo dal telefono sulla stessa rete Wi-Fi.
Le funzioni AI girano solo su Netlify (o con
netlify dev+ unaANTHROPIC_API_KEYin.env). Con il semplicevitela chiamata al coach risponde 404: è atteso, il resto dell'app funziona.
npm run test # tutti i test
npm run test -- src/lib/engine.test.ts # un singolo fileDeploy statico su Netlify: build in dist/, redirect SPA verso index.html e serverless function in netlify/functions/. La sola configurazione necessaria è la variabile d'ambiente ANTHROPIC_API_KEY.
- App navigabile mobile-first
- Persistenza locale + motore di periodizzazione in codice puro (testato)
- Coaching AI via serverless (commento, adattamento, punti deboli, chat)
- PWA installabile
- Sincronizzazione multi-dispositivo (backend opzionale)
- Export/import dei dati
MIT © Matteo Aiello