Skip to content

feat: training module — plan generator, session logging, progression#2

Merged
BrainMode merged 11 commits into
mainfrom
feat/training-module
May 4, 2026
Merged

feat: training module — plan generator, session logging, progression#2
BrainMode merged 11 commits into
mainfrom
feat/training-module

Conversation

@BrainMode
Copy link
Copy Markdown
Owner

@BrainMode BrainMode commented May 4, 2026

Summary

Equipment-aware Iron Mike fullbody_x2 plan generator + Home-Gym alternative + session logging:

Multi-Location: User definiert N Locations (z.B. Gym + Home), pro Location ein Equipment-Set + ein passender Plan:

  • Gym → Iron Mike GK 2× (6 Übungen, 1-2 Working Sets bis Versagen, 5-7 Reps RIR=0)
  • Home / Travel → Home Quick 30 (5 Übungen, KB > Band > Bodyweight Fallback, ~30min)

Session-Logging: Active session UI mit Brzycki-basierten Vorschlägen für Gewicht, Warmup vs Working Set Tracking, Progress-Indicator pro Übung.

Schema: Migrations 0001 (multi_location) + 0002 (22 home-gym exercises seeded).

Verification

  • npm run typecheck — green
  • npm run lint — green
  • npm run test — 82 passing (16 in plan-generator: gym + home + dispatch)
  • npm run build — 8 routes
  • Migrations applied to dev DB

Test plan

  • Login → /training redirects to /training/setup (no plan yet)
  • Wizard zeigt Gym + Home als Default mit sinnvollen Equipment-Vorauswahlen
  • Toggle Equipment für beide Locations, "+ Location" für Travel optional
  • "Pläne erstellen" → /training mit zwei Cards (Gym + Home)
  • Klick "Heute hier trainieren (Gym)" → /training/session/[id] mit erster Übung
  • Klick "Heute hier trainieren (Home Gym)" → andere Session, KB/Band-Übungen
  • Log Sätze → Brzycki-Suggestion für nächste Session ist sinnvoll
  • Frau (recipe_only) sieht /training/* nicht (RLS + Middleware)

Next

Nach Merge: Phase 4 = Recipes-Modul. Externe Blocker: ElevenLabs API-Key + yt-dlp.

🤖 Generated with Claude Code

BrainMode and others added 11 commits May 4, 2026 16:03
What this adds:

- Equipment-aware Iron Mike plan generator (lib/training/plan-generator.ts)
  - Pure function, fullbody_x2 with fallback chain per exercise slot
  - Day A: squat + horizontal press + vertical pull + tricep + bicep + calves
  - Day B: hip hinge + incline press + horizontal pull + OHP + side delts + glutes
  - 7 unit tests covering equipment fallback + preference overrides

- Server actions (app/(app)/training/actions.ts):
  - saveEquipment — wipes + reinserts user_equipment rows
  - generatePlan — deactivates old plan, inserts plan + days + exercises
  - startSession / startNextSession (alternates A/B based on last session)
  - logSet — writes a workout_set with set_type
  - endSession — sets ended_at

- UI:
  - /training — landing page (active plan summary + recent sessions + Start button)
  - /training/setup — equipment wizard with grouped checkboxes (Hanteln, Maschinen,
    Bodyweight, Cardio); generates plan on submit
  - /training/session/[id] — active session with progress indicator,
    auto-advances through exercises, set-by-set logging with Brzycki-based
    weight suggestions from progression.ts
  - /training/history — list of past sessions

- Queries layer (lib/training/queries.ts) with explicit row types
  to handle Supabase's ambiguous nested-relation typing

- scripts/setup-household.mjs — links a Supabase auth user to a Household
  with admin role + sets default user_preferences/training_preferences/macro_targets

Verified:
- npm run typecheck — green
- npm run lint — green
- npm run test — 73 passing (was 66, added 7 plan-generator tests)
- npm run build — 8 routes, training/* compiles

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each user now has multiple equipment-locations (Gym, Home, optional Travel/custom)
with its own equipment inventory and active plan.

Schema migrations (idempotent, in supabase/migrations/):

- 0001_multi_location.sql:
  - new table equipment_locations (user_id, key, display_name, position)
  - user_equipment.location_id (FK), PK migrated to (location_id, equipment)
  - training_plans.location_id, workout_sessions.location_id
  - data migration: existing user_equipment rows assigned to a default 'gym' location

- 0002_home_exercises.sql:
  - 22 new exercises seeded for KB / Bands / Bodyweight (idempotent ON CONFLICT)
  - kb-goblet-squat, kb-swing, kb-floor-press, kb-rdl, kb-bent-row,
    kb-press-overhead, kb-suitcase-deadlift, kb-curl, kb-tricep-extension,
    kb-bulgarian-split-squat
  - band-pulldown, band-row-seated, band-pull-apart, band-lateral-raise,
    band-press, band-curl, band-tricep-pushdown, band-overhead-tricep
  - push-up, inverted-row, bw-bulgarian-split-squat, bw-pike-push-up

Plan generator (lib/training/plan-generator.ts):

- generateFullbodyX2 — unchanged, for gym
- new generateHomeQuick30 — 5 slots, KB > Band > Bodyweight fallback chain,
  reduced warmup (1 set instead of 2), ~25-30 min working
- new generatePlanForLocation(key, available, prefs) — dispatches by location key
- 9 new tests for home variant and dispatch logic (was 7, total now 16 in this file)

UI:

- /training/setup — wizard now lists Locations as cards with editable name +
  per-location equipment grid; "+ Location hinzufügen" button supports custom
  locations beyond gym/home
- /training — landing page shows one card per Location with its plan + a
  "Heute hier trainieren ([Location])" button
- StartSessionButton takes locationId, picks next plan day specifically for
  that location's history

Server actions:

- saveLocations: upserts the full list, deletes locations no longer in input
  (cascades to user_equipment)
- generateAllPlans: iterates locations, generates an active plan for each
  using the appropriate generator (Gym → Iron Mike, Home/Travel → Home Quick 30)
- startNextSessionForLocation(locationId): per-location alternation of A/B days

Migration tooling:

- scripts/apply-migration.mjs — runs a single SQL file, idempotent

Verified:
- npm run typecheck — green
- npm run lint — green
- npm run test — 82 passing (was 73, +9 plan-generator tests)
- npm run build — 8 routes
- Migrations applied successfully to dev DB

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…laceholder routes

- 0003_fix_rls_membership.sql: drop the recursive 'or household_id = current_household_id()'
  clause from the household_members SELECT policy. The recursive lookup made the role
  return null in the app layout, hiding admin-only nav items.

- src/app/(app)/page.tsx: home cards are now Link components routing to /training and
  /nutrition, with hover state.

- New placeholder pages: /nutrition, /body, /chat — phase-N teaser content so the
  bottom nav links don't 404.

- scripts/debug-membership.mjs: small helper that prints each auth user's membership
  role + household for sanity-checking.

Verified:
- npm run typecheck — green
- npm run lint — green
- 82 tests still passing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration 0004 + 0004b — split because Postgres ALTER TYPE ADD VALUE darf nicht
in derselben Transaktion wie INSERTs den neuen Wert nutzen:

- 0004_trx_equipment.sql: alter type equipment_type add value 'trx'
- 0004b_trx_exercises.sql: 6 TRX-Übungen (idempotent ON CONFLICT)
  - trx-row (Horizontal Pull, Body-Angle modulierbar)
  - trx-push-up (Horizontal Push mit Suspension-Instabilität)
  - trx-bulgarian-split-squat (Quads, hinterer Fuss in Schlinge)
  - trx-pike (Vertical Push surrogate)
  - trx-curl (Bicep, suspension)
  - trx-tricep-extension (long-head stretch via Schulter-Position)

Code-Updates:
- src/lib/db/schema.ts equipmentType enum bekommt 'trx'
- src/lib/training/plan-generator.ts EquipmentType union + Home-Slot-Optionen:
  - Day A Quads: TRX-BSS als 2. Wahl nach KB-BSS
  - Day A Horizontal Press: TRX-Push-up als 2. Wahl nach KB-Floor-Press
  - Day A Tricep + Bicep: TRX-Variante als Mittelweg
  - Day B Horizontal Pull: TRX-Row als BESTE Wahl (vor KB-Row)
  - Day B Vertical Press: TRX-Pike als Mittelweg
- src/app/(app)/training/setup/setup-wizard.tsx: 'TRX (Suspension Trainer)'
  als Toggle in Bodyweight-Gruppe, default aktiv für Home-Location

Tests: 84 passing (+2 TRX-spezifisch)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beim Project-Setup hatten wir 'Automatically expose new tables' deaktiviert
(Sicherheits-Empfehlung von Supabase). Folge: keine SELECT/INSERT/UPDATE/DELETE
GRANTs für die app-roles → 'permission denied for table equipment_locations'
beim Plan-Erstellen.

Migration 0005 setzt explizit:
- authenticated + service_role: full CRUD (RLS filtert für authenticated, bypass für service_role)
- anon: nur SELECT (für künftigen Demo-Mode auf shared catalogs wie exercises)
- Default privileges für künftige Tables/Sequences/Functions, sodass dieser
  Fehler bei neuen Migrationen nicht wieder auftritt.

scripts/check-grants.mjs zum schnellen Audit was gewährt ist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User wollte vor dem Plan-Erstellen eine KI-Anamnese statt Defaults — und Mike's
Methodik genauer respektiert (Beinpresse statt Kniebeuge, Pec Deck vor Bench).

KI-Anamnese (Phase 6 vorgezogen):
- src/lib/ai/prompts/training-anamnese.md: System-Prompt mit Mike's Regeln,
  alternativen Stilen (klassisch 3×8-12), Gesprächs-Regeln (max 7 Fragen, knapp,
  auf Deutsch), tool-driven Output
- src/app/api/coach/anamnese/route.ts: streamText-Endpoint mit Vercel AI SDK v4
  - Tool 'set_training_preferences': upsert in training_preferences mit
    style → split_style + volume_style mapping
  - Tool 'complete_anamnese': signal für UI-Redirect
  - maxSteps: 8 (Coach kann mehrere Tool-Calls + finale Antwort machen)
  - Modell: anthropic/claude-opus-4.7 via OpenRouter
- /training/setup/anamnese: Chat-UI mit useChat hook, Stream-Display für Tool-
  Calls, "Weiter zu Equipment-Setup" Button erscheint nach complete_anamnese
- /training/setup wird umgangen wenn keine Locations: jetzt zu /anamnese first

Mike-konforme Plan-Generator-Updates:
- Quads-Slot: Beinpresse FIRST → Hack-Squat → Beinstrecker → Kniebeuge LAST
  (Mike: Kniebeuge ineffizient wegen aktiver Insuffizienz, Beinpresse + Iso
  trainiert Quads vollständiger)
- Chest-Slot: Pec Deck (chest-fly-machine) FIRST → Cable Crossover → DB-Bench
  → Barbell Bench LAST (Mike: konstante Spannung über ROM wichtiger als
  klassisches Bankdrücken)
- Tests: 86 passing (war 84, +2 für Mike-konform-Slot-Order, -2 weil 2 alte
  Tests zur neuen Reihenfolge updated)

Podcast-Quelle:
- scripts/extract-podcast-text.mjs: extrahiert Text aus dem Iron-Mike-JSON
- docs/iron-mike-transcript.txt: 138k chars Transkript für Referenz

Verified: typecheck + lint + 86 tests + build alle grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn ein User schon Locations + Pläne hat, leitet /training nicht mehr zur
Anamnese — Coach war damit verdeckt. Jetzt:
- Card auf /training landing mit 'Coach starten' Button
- Tipp-Banner auf /training/setup mit Link zur Anamnese vor dem Equipment-Setup

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… ergibt 10)

Bug: Plan-Generator hat repRangeMin als targetReps verwendet → Iron Mike kam
mit 5 Reps statt 6 raus. Mike sagt explizit Ziel ist 6 Reps RIR=0, Range 5-7
ist der akzeptable Korridor.

Fix: rangeTarget(min, max, fallback) nimmt die Mitte (gerundet).
- Iron Mike 5-7 → 6
- Classic 8-12 → 10
- RIR Mitte: 0-1 → 1, 1-3 → 2

Tests: 87 passing (war 86, +1 für classic-style verification).

User muss bestehende Pläne neu generieren (auf /training → Locations + Equipment
editieren → Pläne erstellen) damit die korrigierten Target-Reps in
training_plan_exercises landen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem: KB-Swing landet im Home-Plan mit 2×6 Reps RIR=0 — das ist
Hardstyle-Conditioning (15-25 Reps mit Power), nicht klassisches Working-Set
bis Versagen. Bodyweight-Push-up trifft Versagen erst bei 15+ Reps. Festgewichts-
KBs erlauben keine Brzycki-Progression mit 5-7 Reps.

Schema-Update (Migration 0006):
- exercises bekommt 4 neue Spalten: recommended_rep_min, recommended_rep_max,
  recommended_rir_min, recommended_rir_max
- Werte gesetzt für KB / Bands / TRX / Bodyweight / Iso-Maschinen / Waden / Core
- Compound-Lifts (Bench, Squat, Press, Row) bleiben null → folgen User-Default

Plan-Generator-Logik (actions.ts generateAllPlans):
- Beim Insert von training_plan_exercises: lade exercise-Recommendations
- Wenn vorhanden, Mitte der Range → target_reps + target_rir
- Sonst: Template-Default (basierend auf user training_preferences)

Konkrete Effekte:
- KB-Swing: 2×20 Reps RIR 2 (Conditioning, Mitte von 15-25)
- KB-RDL / KB-Goblet-Squat / KB-Curl etc.: 2×10 RIR 1 (Mitte 8-12)
- KB-Floor-Press / KB-OHP: 2×7 RIR 1 (Mitte 5-8 — KB-Pressen sind schwer)
- Push-up / Inverted-Row: 2×12 RIR 1 (Mitte 8-15)
- TRX-Übungen: 2×12 RIR 1 (Schwierigkeit über Körperwinkel)
- Bands: 2×13 RIR 1 (Tension nicht standardisiert)
- Bench Press / Squat / Bench: keine recommendation → User-Preference (6 RIR 0)

Plus: HOME_DAY_B Hinge-Slot reordered. KB-RDL FIRST (lineare Progression
möglich), KB-Suitcase mid, KB-Swing LAST (nur als Conditioning-Move wenn
nichts anderes verfügbar).

User muss bestehende Pläne neu generieren um die Werte in
training_plan_exercises zu sehen — auf /training/setup → 'Pläne erstellen'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y B Rear Delts

Drei Mike-Konformitäts-Korrekturen aus User-Feedback:

1. Übung 'chest-fly-machine' umbenannt: 'Brustpresse Maschine (Fly)' →
   'Pec Deck (Butterfly)'. Pec Deck ist Iso-Adduktion, keine Press-Bewegung.
   Migration 0007.

2. DAY_A Slot 'Calves' → 'Hamstring iso (Beinbeuger sitzend/liegend)'.
   Begründung: Mike trifft jeden Muskel 2×/Woche. Mit dem alten Plan kam
   Hamstring nur in Day B (RDL) — jetzt auch in Day A. Beinbeuger sitzend
   ist Mike's Lieblings-Iso wegen Knie-Flexion mit gestreckter Hüfte.

3. DAY_B Slot 'Vertical Press (Front Delts)' entfernt → ersetzt durch
   'Rear Delts (Cable Reverse Fly / Face Pull)'. Mike sagt explizit:
   Front-Press ist redundant — Frontdelt wird durch Brust-Pressen schon
   getroffen. Hinterer Delta ist dagegen Posture-relevant und sonst unter-
   repräsentiert. Day B hat jetzt zusätzlich Calves Slot (1×/Woche reicht).

Tests: 90 passing (war 87, +3 für Mike-konform-Verifikation):
- Day A enthält Hamstring iso
- Day B hat KEIN Schulterdrücken
- Day B hat Rear Delts + Calves

User muss wieder neu generieren — auf /training/setup → 'Pläne erstellen'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e limit

CI Runner teilen sich IP → der yt-dlp postinstall download hits 5k/h Limit.
Binary wird nur im Recipe-Worker (Phase 4 runtime) gebraucht, nicht für die
CI-Steps (typecheck/lint/test/build). Also: skip download im CI.

Lokal funktioniert npm install weiter wie gewohnt — Binary wird heruntergeladen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BrainMode BrainMode merged commit 882cd3e into main May 4, 2026
2 checks passed
@BrainMode BrainMode deleted the feat/training-module branch May 4, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant