half-training-app
active personal privatePersonal half marathon training tracker + AI coach for AthHalf 2026
- Primary: https://github.com/klill6506/half-training-app
- GitHub: https://github.com/klill6506/half-training-app
- Local:
D:\Personal\half-training-app
README
# AthHalf Training App Personal half marathon training tracker for **AthHalf — Athens GA, October 11, 2026**. 24-week plan, AI coach powered by Claude, installs to iPhone home screen as a PWA. > **Single user.** This app is just for me. The plan dates and goal times are > baked in. Fork at your own risk; nothing here generalizes. ## Stack - Vite + React 19 + TypeScript - React Router v7 - Tailwind CSS v3 - Zustand (state) - Supabase (Postgres + magic-link auth + Edge Function) - Anthropic API (Sonnet 4.6 with prompt caching), proxied via Edge Function - Render Static Site (hosting) - vite-plugin-pwa (Add to Home Screen on iOS Safari) ## Local development ```bash # 1. Install npm install # 2. Copy env template, fill in Supabase URL + anon key from the # `ken-personal` Supabase project (Settings → API) cp .env.example .env # 3. Run npm run dev ``` Then run `supabase/migrations/20260429_init.sql` in the Supabase SQL editor once. It creates tables, enables RLS, and installs a trigger that auto-creates a `settings` row on signup. ## Edge Function The coach is a Supabase Edge Function (`supabase/functions/coach`). Deploy with the Supabase CLI: ```bash supabase functions deploy coach supabase secrets set ANTHROPIC_API_KEY=sk-ant-... # optional: pin a different model supabase secrets set COACH_MODEL=claude-sonnet-4-6 ``` ## Tests ```bash npm test # vitest watch npm run test:run # one shot ``` ## Build + deploy ```bash npm run build # → dist/ ``` Render Static Site is configured for `npm install && npm run build` with publish dir `dist`. Set `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` in the Render dashboard so they're baked into the bundle at build time. ## Project files - `SPEC.md` — original build spec (read-only reference) - `CLAUDE.md` — project rules + tech stack (this is what Claude Code reads) - `MEMORY.md` — accumulated context, decisions, gotchas - `STATUS.md` — what's shipped, what's next, blockers (update before every push …(truncated for upload size)
STATUS
# AthHalf Training App — Status
*Last updated: 2026-05-22 (weather city override shipped)*
## V2e — Manual weather city ✅ shipped (2026-05-22)
Closes the "Settings-page location override" deferred item below. Ken was
travelling and the Today card still showed Athens.
- `src/lib/weather.ts`: `fetchWeather()` now takes an optional `GeoCity`
(defaults to the active city). New `searchCities()` uses Open-Meteo's free
geocoding API (same provider, no key). `getActiveCity()` / `saveCity()` /
`clearSavedCity()` persist the chosen city in **localStorage** (`athhalf.weatherCity`).
- Storage is device-local on purpose — "which city am I in" belongs to the
phone in hand, not the account. No Supabase schema change.
- Forecast `timezone` switched from hardcoded `America/New_York` to `auto`, so
sunrise/sunset + best-window read in the destination's local time. Athens
still resolves to ET — no change at home.
- `src/components/WeatherCitySetting.tsx`: new Settings card — search a city,
tap a result, "Back to Athens (home)" to reset. WeatherCard header shows the
active city ("Conditions · Portland").
- Coach Sherpa picks up the travel city for free (`buildCoachContext` calls
`fetchWeather()` with no args → active city).
- Build clean, 101/101 tests pass (+3 for `formatCityLabel`).
## V2d — Weather refinements (3 commits) ✅ shipped (2026-05-06)
**A. Layout + 1-10 score + Georgia recalibration**
- Today's run + Weather are side-by-side on desktop (3:2 grid), stacked on mobile with workout first.
- Compact WeatherCard (~30% shorter than before).
- Run-quality is now a 1-10 score with matching label + dominant-factor reason.
- Brackets recalibrated for Southeast US: 7/10 = typical nice Georgia morning (Ken's "today is nice maybe a 7"), 2/10 = 90°F+80%RH brutal, 1/10 dangerous extreme.
**B. Best running window today**
- Hourly forecast scan from now → sunset, picks the highest-scoring 2-hour daylight stretch.
- Displays as "6:00 AM – 8:00 AM · 9/10 · ~62°, dew 55°"
…(truncated for upload size)
DECISIONS
# AthHalf Training App — Architectural Decisions A log of non-trivial choices. Each decision lists what we picked, what we ruled out, and why. Don't re-litigate without new information. --- ## 2026-04-29 — Tech stack baseline **Decision:** Vite + React 19 + TypeScript, React Router v7, Tailwind v3, Zustand v5, Supabase (Postgres + magic-link auth + Edge Function), `vite-plugin-pwa`, Render Static Site for hosting. **What we ruled out:** - React 18 (spec's original) — no migration cost on a fresh project. - React Router v6 — same reasoning, v7 is current. - Tailwind v4 — keeping v3 for familiarity (Ken used v3 in the artifact prototype). v4's CSS-first config is a learning cost we don't need today. - Next.js / SvelteKit / Remix — overkill for a single-user PWA with no server. **Why:** Smallest stack that gets the three things working (log a run, see today's workout, ask the coach). No backend service to run; Supabase Edge Functions handle the AI proxy. --- ## 2026-04-29 — AI model: Sonnet 4.6 with prompt caching **Decision:** Use `claude-sonnet-4-6` for the coach, with prompt caching on the system prompt. Model is configurable via `COACH_MODEL` env var. **What we ruled out:** - Opus 4.7 — overkill for short coaching responses, ~5x the cost. - Haiku 4.5 — workable, but tone/specificity matters here. Coaching reads flatter. - Gemini — used in tts-tax-app for IRS-grounded RAG (different job). Coaching tone is Claude's strength. **Why:** Sonnet is the right balance for tone-driven, instruction-following work. Prompt caching cuts ~90% off input cost since the ~3KB system prompt is static across every call. Estimated cost: ~$1–3/month at expected usage. Env var means we can A/B with Haiku later without redeploy. --- ## 2026-04-29 — Personal Supabase (`ken-personal`) is separate from Sherpa **Decision:** Create a new Supabase project `ken-personal` for the half marathon app and all future personal apps. Do not share with the Sherpa firm Supabase. **What we ru …(truncated for upload size)
MEMORY
# AthHalf Training App — Memory
Standing facts, decisions, and gotchas. The kind of thing you'd want to
remember in 3 months when you forget why something is the way it is.
---
## Project Constants (V2c marathon pivot, 2026-05-04)
- **Race**: **Richmond Marathon** — Saturday, November 14, 2026 (was AthHalf, dropped)
- **Plan start**: Wednesday, April 29, 2026
- **Plan length**: **29 weeks**
- **Working target**: sub-4:00 (9:09/mi)
- **Stretch target**: BQ for Men 60-64, ~3:42-3:45 (8:30/mi). Boston standard is 3:50:00 but practical entry needs 5-7 min buffer.
- **Floor**: 4:15 (9:43/mi)
- **Run days**:
- Mon (recovery, W14-29 except W28+W29) — 25-30 min @ 11:00-11:30/mi
- Tue (easy/MLR, W9-29 except W29) — grows from 2.5 mi to 10 mi peak
- Wed (easy/strides, every week)
- Fri (LONG, every week, peaks at 20 mi W26)
- Sun (medium-quality, every week except W29)
- **Peak weekly volume**: 46.5 mpw at W26 (under 47 cap)
- **Long-run rule**: no forward week-over-week jump > 2 mi (cutback exits exempt)
## Stack Decisions (Apr 29, 2026)
- **React 19 + Router v7** — chosen over spec's React 18 / RR v6. New project, no migration cost.
- **Tailwind v3** (not v4) — deliberate. Matches artifact prototype; v4 has different config model.
- **Sonnet 4.6 with prompt caching** — coach's system prompt is ~3KB and static, so caching cuts input cost ~90% per call. Env var `COACH_MODEL` so we can A/B with Haiku 4.5 if cost matters.
- **Auto-create settings row via DB trigger** — instead of manually inserting after first signup. Set-and-forget.
- **Personal Supabase = `ken-personal`** — separate from the Sherpa firm Supabase. Reasons: blast-radius separation, different security postures, estate-planning ("one URL to find Ken's stuff"), and it's the seed for future personal apps (health, finance, legal).
## Auth + RLS Pattern
- Magic-link only (`signInWithOtp`).
- Every table has RLS enabled with `USING (user_id = auth.uid())`.
- **Never write `USING (true)`** — Postgres OR'
…(truncated for upload size)
CLAUDE.md
# AthHalf Training App — Project Instructions *Last updated: 2026-04-29* ## Owner Ken Lill — turning 60 in August 2026. CPA running The Tax Shelter in Athens, GA. Learning to code, not a career engineer. Keep code readable; over-explain where it helps. ## This Repo `half-training-app` — Personal half marathon training tracker for AthHalf (Athens, GA, October 11, 2026). 24-week plan, AI coach powered by Anthropic. Local path: `D:\Personal\half-training-app` ## Read This First Every Session Read `SPEC.md` (the full build spec), `STATUS.md` (current state), and `DECISIONS.md` (architectural choices) at the start of each session. ## Tech Stack (locked in — do not change without discussion) | Layer | Choice | |-------|--------| | Frontend | Vite + React **19** + TypeScript | | Routing | React Router **v7** | | State | Zustand v5 | | Styling | **Tailwind CSS v3** (deliberate — not v4) | | DB + Auth | Supabase (Postgres + magic-link auth, RLS on every table) | | AI proxy | Supabase Edge Function (Deno) → Anthropic API | | AI model | `claude-sonnet-4-6` (env-var swappable via `COACH_MODEL`) with prompt caching | | Hosting | Render Static Site (free tier, never sleeps) | | PWA | vite-plugin-pwa (installs to iPhone home screen via Safari) | ## Supabase Project Personal Supabase project named **`ken-personal`** — separate from the Sherpa firm Supabase. This personal project is the planned home for future personal apps too (health, finance, legal). Estate-planning consideration: one credential set for "Ken's personal data lives here." **Never put Sherpa firm data in this Supabase. Never put personal data in the Sherpa Supabase.** ## Project Rules (enforced) - **No secrets in repo** — all credentials via `.env` (gitignored). - **RLS on every table** — `USING (user_id = auth.uid())`. Never write `USING (true)`. - **No PII or real client data** — this is personal, but treat `.env` and DB contents as private. - **Time zones** — all dates in DB are DATE type. Frontend uses …(truncated for upload size)
Diary mentions
No recent diary mentions for this app.