feat(combination-exercises): enhance method profile integration and update specifications
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m4s

- Updated app version to 0.8.102, reflecting recent enhancements in combination exercises.
- Introduced structured method profiles for combination exercises, allowing for detailed planning and coaching support.
- Enhanced frontend components to display method profiles in the Exercise and Combination Coach views.
- Updated documentation to include new specifications and implementation details for method archetypes and profiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-05-13 07:16:58 +02:00
parent 919910d52a
commit 12fd3926b2
12 changed files with 564 additions and 27 deletions

View File

@ -9,6 +9,9 @@ Ausführliche fachliche Inhalte:
| [DOMAIN_MODEL.md](./DOMAIN_MODEL.md) | Domänenmodell, Variantenlogik (Abschnitt 11.2), **Medien-Archiv** (Abschnitt 2026-05) |
| [MEDIA_ASSETS_AND_ARCHIVE_SPEC.md](../technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) | Medien-Archiv, Lifecycle, **Inline-Medien** (Spec Abschnitt 11, umgesetzt) |
| [MULTI_TENANCY_RBAC_ARCHITECTURE.md](../technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md) | Zielarchitektur Mandanten/Rollen/Membership & Umsetzungsplan |
| [**Trainingsmodule & Kombinationsübungen (Fachspez V3)**](./Shinkan%20Trainingsmodule%20Kombinationsuebungen%20Spezifikation%20V2.md) | Produktlogik Module/Kombinationen, **Methoden-Archetypen**, **Coaching-Stufen (§10.4)**, kanonische Archetyp-IDs **§10.2.1**, **Anhang A** Implementierungsabgleich |
| [**Umsetzungsplan Trainingsmodule & Kombination**](../working/TRAINING_MODULES_IMPLEMENTATION_PLAN.md) | Phase 15, Coaching-Pakete 4a4d, Verweis auf Code-Stand |
| [**Technischer Entwurf Module/Kombination**](../technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md) | API/Daten-Ideen; aktueller Coach-/Archetyp-Abgleich im Kopfabschnitt |
**Lieferstand & Umsetzung (Stand Code):** [`../PROJECT_STATUS.md`](../PROJECT_STATUS.md), [`../library/FEATURES_DELIVERED_2026-Q2.md`](../library/FEATURES_DELIVERED_2026-Q2.md) (Abschnitt 12), Repo-Root **`docs/HANDOVER.md`**, **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`**.

View File

@ -1,7 +1,7 @@
# Trainingsmodule und Kombinationsübungen — fachliche Spezifikation V3
**Status:** fachlicher Spezifikationsentwurf
**Stand:** 2026-05-12
**Stand:** 2026-05-12 · **Coaching/Archetypen:** §10.2.1, §10.410.5, **§5.4/§6.3** Methoden/Archetypen/Zeitschicht · **Anhang A** (Abgleich Code vs. Spec)
**Zweck:** Produkt- und Fachspezifikation für Trainingsmodule, Kombinationsübungen, Trainingsmethodenbezug, Planungsintegration und Coaching-Modus in Shinkan.
**Wichtige Leitlinie dieser Version:**
@ -335,6 +335,30 @@ Für jeden Archetyp muss fachlich beschrieben sein:
Die technische Validierung und konkrete Ablage dieser Angaben soll der Coding Agent planen.
### 5.4 Einordnung: Trainingsformen wie HIIT, Dauer, plyometrisch ↔ Archetypen
**Wichtige Trennung (bleibt fachlich zwingend):**
* **Trainingsmethode im Methodenkatalog** (z.B. HIIT, extensive Intervallmethode, Dauermethode, plyometrisches Training) beschreibt primär den **didaktisch/belastungsmethodischen Kontext („was für eine Trainingsqualität ist das?“)**.
* **`method_archetype`** beschreibt **das Ablaufmuster („wie soll der Trainer den Block strukturieren und im Coach geführt werden?“)** — insbesondere **Parallelität, Rotation, Sequenz, Zeitdomänen**.
Dieselbe Methode kann in der Praxis mit **mehreren** Archetypen sinnvoll kombiniert sein; das ist **kein Widerspruch**.
| Beispiel (Methoden-/Belastungsbegriff) | Typischer Archetyp (Orientierung), nicht Pflicht |
| -------------------------------------- | ---------------------------------------------- |
| **HIIT**, Tabata-ähnlich, Kurzintervalle oft mit hoher Intention | sehr oft **`time_domain_interval`** oder innerhalb eines Zirkels **`circuit_rotate_time`** mit kurzen Arbeitsphasen; Partnerformen zusätzlich **`pair_superset`**. |
| **Klassisches Intervalltraining** (längere Arbeit, definierte Erholung, N Wiederholungen) | überwiegend **`time_domain_interval`** oder **`circuit_rotate_time`**, wenn die „Intervallschicht“ an Stationen gebunden ist. |
| **Dauermethode** (überwiegend durchgehend ohne harte Arbeit-Erholung-Takte) | eher **`free_method_block`** oder **`sequence_linear`** mit optionalen Hinweiten; **weniger** `time_domain_interval`, sofern kein geregeltes Intervallschema gemeint ist. |
| **Plyometrisch**, Explosivblöcke, Technik-Schichtung | häufig **`sequence_linear`** (Progression vor Ort) oder **`circuit_rotate_time`** / **`time_domain_interval`**, wenn klar Zeitfenster oder Wiederholungsblöcke vorgegeben sind. |
| Rein **organisatorisches** Stationslaufen ohne gemeinsamen Intervalltakt | **`circuit_rotate_time`** oder **`station_parcours`**, **`circuit_all_parallel`**, je nach ob rotiert wird oder parallel aktiv ist. |
**Shinkan-Zielrichtung bleibt trainerzentriert:** Es geht **nicht** um individuelle Pulsonomie eines Sportlers, sondern darum, dass der **Trainer Belastungs- und Erholungsphasen, Durchläufe und ggf. Umlauf-/Parallellogik** vorgibt und der **Coach** diese Vorgaben **sichtbar und später steuerbar** macht
(siehe **§6.3** zu Phasen jenseits „nur Gesamtminuten auf dem Planungsitem“).
### 5.5 Erweiterbarkeit von Archetypen (aktuell zurückgestellt)
Die Idee einer **von Superadmins zur Laufzeit editierbare Archetyp-Registry**, die den Coaching-Modus **völlig frei parametrierbar** macht, wird **zurückgestellt**. Vorerst reicht die **festgelegte, versionierte Liste** kanonischer Archetyp-IDs (**§10.2.1**); **weitere Archetypen** können später **wie bisher durch Produkt-/Release entschieden** ergänzt werden (Code oder kuratierter Import), ohne freies „Beliebig-Neuanlegen“ ohne definierten Coach-Verhaltens-Anker.
---
## 6. Kombinationsübungen
@ -370,10 +394,32 @@ Eine Kombinationsübung sollte fachlich enthalten:
* Archetyp,
* Slots / Stationen / Rollen / Schritte,
* mögliche Übungen je Slot,
* optionale Standardwerte für Dauer, Runden oder Wechsel,
* strukturierte **Zeitphasen und Belastungs-/Erholungsvorgaben** innerhalb der Kombination (**`method_profile`**, Überblick §6.3; Details §10.5) — **zusätzlich** zu allenfalls geplanten **Gesamtminuten am Planungseintrag**,
* optionale klassische Hinweise zu Dauer, Runden oder Wechsel aus der Übung heraus,
* Hinweise für den Coaching-Modus.
### 6.3 Slot- und Pool-Logik
### 6.3 Zeitschicht: Phasen innerhalb der Kombination (Bibliothek) und Anpassungen in der Planung
Ein **einzelnes** Feld „Geplante Minuten für diesen Eintrag in der Einheit“ kann die **innenliegende zeitliche Logik** einer Kombinationsübung **nicht** ersetzen. Für Trainersteuerung (und später für Coaching **Stufe C**) soll die Kombination in der Bibliotheksbeschreibung vorsehen können:
**A) Kombinationseinheit („global“ über die Slots)**
* **Arbeits-** und **Erholungszeiten** (Sekunden/Minuten) und **Anzahl der Durchläufe** oder **Intervalle**,
* ggf. **gemeinsamer Takt** für alle Teilnehmenden (z.B. rotierender Zirkel oder eine gemeinsame Intervalluhr),
* **Erklär- oder Aufbauzeit** vor dem eigentlichen Start,
* dort, wo der Archetyp passt: **Runden-/Umlaufzahl** oder vergleichbare Strukturen.
Alle diese Angaben sind **Anweisungen an den Trainer** und **CoachAssistenz**, **keine** individuelle Pulssonde oder ähnliche Personenmessung.
**B) Optional pro Slot oder Schritt**
* wenn fachlich sinnvoll: **von Station zu Station variierende Arbeitsphasen** oder MiniSequenzen innerhalb eines Slots — technisch z.B. als strukturierte Liste in `method_profile` mit Bezug zum `slot_index` (Ausarbeitung Coding Agent).
**Nach Einplanung in eine konkrete Trainingseinheit** muss diese Zeitschicht (oder ihr Abgleich mit der Einheitsposition) für den Trainer **bearbeitbar** bleiben, **ohne** die Bibliotheksvorlage still zu überschreiben (kopier-/instanzbasierte Anpassungen — siehe bereits §2.5 und §8.3).
**Coach:** soll die wirksamen Werte nach **Übernahme** und **Einheitsübersteuerungen** konsistent nachvollziehen (**§10.4**).
### 6.4 Slot- und Pool-Logik
Slots können fest oder variabel sein.
@ -466,6 +512,7 @@ Produktregel:
Nach dem Einfügen muss ein Planungsblock lokal angepasst werden können:
* Dauer ändern,
* **bei Kombinationsübungen:** im Idealfall **`method_profile` (Arbeit, Erholung, Durchläufe)** und Stations-/Slot-Anpassungen des **konkreten Vorkommens**, nicht nur Gesamtzeit,
* Übung austauschen,
* Station ergänzen,
* Hinweise anpassen,
@ -549,6 +596,22 @@ Grundsatz:
| Intervallblock | Globale Zeit, Intervallzähler, Aufgaben. |
| Freier Methodenblock | Kompakte Übersicht und manuelle Steuerung. |
#### 10.2.1 Kanonische Archetyp-IDs (Abgleich Fachbegriff, UI und API)
Damit Produktbeschreibung, Formularfelder (`method_archetype`), Trainingscoach und BackendValidierung **dieselben Werte** nutzen und es keinen dokumentationsbedingten Drift gibt, gelten diese **festen Schlüssel** (MaschinenIDs):
| Archetyp (fachlicher Name in §5.2) | Schlüssel `method_archetype` (`exercises.method_archetype`) |
| ----------------------------------- | ----------------------------------------------------------- |
| Lineare Sequenz | `sequence_linear` |
| Rotierender Zirkel (Zeit) | `circuit_rotate_time` |
| Parallele Stationen | `circuit_all_parallel` |
| Parcours | `station_parcour` |
| Partner- / Paarwechsel | `pair_superset` |
| Intervallblock (Zeitdomäne) | `time_domain_interval` |
| Freier Methodenblock | `free_method_block` |
Änderungen an dieser Zuordnung nur **gemeinsam** (Produkt, BackendEnum und UIKonstanten); siehe Implementierungsanhang weiter unten.
### 10.3 Durchführungsdokumentation
Perspektivisch sollte dokumentierbar sein:
@ -563,6 +626,43 @@ Perspektivisch sollte dokumentierbar sein:
Die konkrete technische Umsetzung wird nicht in dieser Spezifikation festgelegt.
### 10.4 Coaching-Reifegrade (Normierung ohne technisches Pflichtenheft)
Archetyp-spezifisches Coaching soll **nicht** als ein einziges UX-„Monolith“ gebaut werden, sondern in **nachvollziehbaren Stufen**, damit frühere Umsetzungen nicht überschrieben wirken und der Fortschritt in Doku/Umsetzungsplan nachverfolgt werden kann:
| Stufe | Bezeichnung (Arbeitstitel) | Inhalt aus Trainersicht | Abgrenzung |
| ----- | ---------------------------- | ------------------------ | ----------- |
| **A** | **Informations-/Struktursicht** | Pro Kombinationsübung: KopfKontext aus Katalog **plus** strukturierte Darstellung der **Slots** und der **einzelnen Kandidatenübungen** (Titel, Kurztext, Detail aufklappbar); **ein zeitlicher Schritt im Coach** entspricht weiter **einem** Planungseintrag (ein Item in der Einheit). | Kein eigener Rundenzähler, kein eigener StationsTimerState pro Archetyp. |
| **B** | **Archetyp-Steuerung in der bestehenden Zeitleiste** | Optionale Aufspaltung: z.B. bei **`sequence_linear`** pro Slot **ein CoachSchritt** (Weiter/Zurück pro Station), ohne die Datenbank-Semantik der Einheit zu zerstückeln (Virtuelle Schritte oder materialisierte HilfsEinträge technische Variante dokumentieren). | Bewusste Produkt-/Architekturentscheidung nötig, damit ISTZeiten und AbschlussPUT konsistent bleiben. |
| **C** | **Interaktive Assistenz je Archetyp** | Gemeinschafts-/StationsTimer, Wechselimpulse (**`circuit_rotate_time`**), Vorab„Erklärphase“Flag (**`circuit_all_parallel`**), Abhaken (**`station_parcour`**), gekoppelte A/BAnsicht (**`pair_superset`**), globale Intervalluhr (**`time_domain_interval`**) — jeweils an Parameter aus **`method_profile`** angebunden, wo diese in Stufe A/B bereits sichtbar gepflegt werden. | Keine verpflichtende KISteuerung; Trainer kann überspringen (Grundsatz §10.1). |
**Aktuelle Zielrichtung:** Stufe **A** soll für **alle** in §10.2.1 genannten Archetypen **inhaltsgleich** die Slot und Kandidateninformation liefern; **unterschiedliche Kopf-/Hilfstexte und UI-Mikrolayouts** nach Archetyp sind Teil von A und sollten gemeinsam mit Stufe B/C wachsen (kein „still“ abweichendes Verhalten ohne DokuUpdate).
### 10.5 Fachliche Mindestinfos im **Ablaufprofil** (`method_profile`) pro Archetyp
`method_profile` ist das **konkretisierende** JSON (o. ä.) zum gewählten Archetyp: Zeiten, Runden, Schalter. Technische Pflichtfelder und Validierung regelt die technische Umsetzung — **fachlich** gilt folgende Minimal-Erwartung, damit Stufe B/C sinnvoll nutzbar ist:
| Archetyp-Schlüssel | Mindest-Parameter (fachlich sinnvoll; Benennung in der Umsetzung kanonisch festlegen). Typische Zuordnung methodischer Überbegriffe: **§5.4** |
| ------------------ | ------------------------------------------------------------------------------------- |
| `sequence_linear` | Orientierungs-Arbeits-/Pausenhinweise je Schritt oder global; Reihenfolge = Slotreihenfolge — u.a. für **Skillschichtungen**, Aufwärmserien ohne festen Rotationstakt oder **Ausdauer-/Technikketten ohne Intervalltakt**. |
| `circuit_rotate_time` | **Arbeit** je Station oder Umlauf, **optional Wechsel/Transition**, **optional Erholung zwischen Runden**, **optional Rundenanzahl**; Kern für rotierenden Zirkel inkl. vieler HIIT-/Zirkelmischformen über Stationen hinweg (**§5.4**). |
| `circuit_all_parallel` | „Erst gemeinsame Erklärung, dann gleichzeitiger Betrieb aller Stationen“; Zeitfenster VorabErklärung optional — z.B. wenn keine Rotation, aber gemeinsamer Startzeitpunkt gewünscht ist. |
| `station_parcour` | Fokus Stationsbeschreibung; optional freie Besuchsreihenfolge (Profil/Archetyp); weniger zentral **feste Arbeit/Erholung-Takte**, mehr Navigation/Abhaken (später Stufe C). |
| `pair_superset` | Arbeit und Wechsel bei **gekoppelten** Rollen; typisch wenn zwei Linien oder Partnerblöcke im Takt gewechselt werden. |
| `time_domain_interval` | Klare Zeitdomäne: **Belastungs-, Erholungsblöcke** und **Anzahl Wiederholungen** der Domäne bzw. **Gesamtblockbegrenzung** — zentrale Schicht für viele Formen aus **„Intervall/HIIT/Zeitschachtelungs“**Methodenkatalog ohne individuelle Messung (**§5.4**). |
| `free_method_block` | Keine zusätzlichen Pflichtparameter; **unterstützt** etwa **reibungsarmere Dauer- oder Spielformen**, wo der Trainer keine starke Taktuhr braucht, aber Stationsideen strukturiert bündeln will. |
#### 10.5.1 Mehrschichtiges Planen (Überblick)
| Ebene | Inhalt zeitlicher Art |
| ----- | --------------------- |
| **Einheit / Planungsitem** | z.B. geplante **Gesamtminuten** dieser Platzierung („der Block soll heute etwa 25Min einnehmen“). |
| **Kombinationsübung (Bibliothek)** | strukturierte **Phasen in `method_profile`** (arbeiten, pausieren, Runden…) — §6.3. |
| **Einheitliche Planungsinstanz** | optionale Abweiche vom Bibliotheksprofil **nur für dieses Training**8.3). |
| **Coach** | liest wirksamen Stand (Bibliothek + Overrides) zur **Orientierung**, später automatisierte Taktassistenz (**§10.4**). |
Solange diese Mindestinfos in der Datenpflege noch **nicht** validiert oder nicht geführt erfasst werden, bleibt Coaching bei **Informations-Schicht und manuellen Timern des bestehenden Coach-Dialogs** die fachlich ehrliche Darstellung (siehe Anhang A).
---
## 11. Rahmenprogramm-Integration
@ -625,7 +725,7 @@ Für Methoden ist eine besondere Qualitätskontrolle sinnvoll, weil sie als fach
* freier Methodenblock,
* Planungsblöcke als fachliches Konzept,
* lokale Anpassbarkeit nach Einfügen,
* einfache Coaching-Ansicht.
* Coaching: mindestens **Stufe A** nach §10.4 für alle Archetypen aus §10.2.1 (strukturierte Slot-/Kombi-Darstellung; Archetyp-Hilfstexte); **zeitliche/mechanische Archetyp-Steuerung (Stufen B/C)** ausdrücklich als Ausbauschritte.
### 13.2 Sollte vorbereitet werden
@ -677,8 +777,32 @@ Die Spezifikation ist daher kein technisches Pflichtenheft, sondern ein fachlich
5. Trainingsmethoden sind eigenständige fachliche Katalogobjekte.
6. Eine Übung hat eine Hauptmethode und optional Nebenmethoden.
7. Methoden-Archetypen beschreiben Ablaufmuster, nicht die Methode selbst.
8. Ablaufprofile konkretisieren den Archetyp für Planung und Coaching.
8. Ablaufprofile konkretisieren den Archetyp für Planung und Coaching (siehe §10.5).
9. Einfügen aus Bibliotheken erzeugt lokal bearbeitbare Planungsinhalte.
10. Vorlagenänderungen verändern historische oder konkrete Planungen nicht automatisch.
11. Rahmenprogramme sollen dieselbe Planungslogik nutzen wie konkrete Einheiten.
12. Der Coding Agent entscheidet die technische Umsetzung anhand der bestehenden Codebasis.
13. Archetyp-IDs und Coaching-Stufen (§10.2.1, §10.4) sind die **Referenz gegen Code-Drift**; Änderungen nur mit Anhang A und technischer Doku.
14. **Zeitliche Phasen** einer Kombination liegen vorrangig in **`method_profile`** und **Gesamtzeit am Planungseintrag**; **Übersteuerungen nur in der Planungsinstanz**, nicht still in der Bibliothek (§6.3, §8.3, §10.5.1).
---
## Anhang A — Implementierungsabgleich (Stand Code: App **0.8.102**, grob)
Zweck: dieselbe Tabelle für **Produkt / Architekt / Agent** beim nächsten Schritt; verhindert „wir haben X gebaut, die Spec sagt aber Y“ ohne dass es dokumentiert wird.
| Thema (fachliche Headline aus dieser Spez) | Kurz beschrieben | Stand Code / UX (Referenz nur) | Lücke / nächste sinnvolle Schritte |
|--------------------------------------------|-----------------|---------------------------------|-------------------------------------|
| **Trainingsmodule (Bibliothek)** | Wiederverwendbare Blöcke, Kopier-Einfügen in Einheit | Bibliothek, API, Übernahme-Modal, Lineage-Spalte | **Phase 3** des Umsetzungsplans: erweiterter Übernahmemodus |
| **Kombinationsübung im Katalog** | `exercise_kind=combination`, Slots, Pools (Kandidaten) | Migration 056, CRUD Übung mit `combination_slots`, GET liefert Slots + Kandidatentitel | Fachbezug Haupt-/Nebenmethoden aus §4/§6 dort umsetzen, wo die Domäne es noch nicht abdeckt |
| **Archetyp + Ablaufprofil am Katalogobjekt** | `method_archetype`, JSON `method_profile` | Persistenz; Übungsformular: **geführte Felder** nach Archetyp (`CombinationMethodProfileEditor`, `combinationMethodProfileUi.js`) + eingeklapptes RohJSON | SchemaValidierung serverseitig noch offen; UI für Pflicht je Archetyp (§10.5) weiter schärfen |
| **Einplanbarkeit (normale Planung)** | Kombi wie Übung in Sektionen; **ZeitprofilOverrides** nach §8.3 / §10.5.1 | Picker, `exercise_kind` in Form/PUT, keine Variante bei Kombi; **Overrides von `method_profile` am Platzierungseintrag fehlen** | Planungs-UI/API: kopiertes **`method_profile` pro Einheit/item** bearbeitbar; Planungsblöcke (Phase 3) |
| **Zeitphasen (global / pro Slot)** | §6.3 | Über `method_profile` teilweise (globale Schlüssel im Formular); **keine strukturierten slotgebundenen Zeitlisten** im UI | `slot_timing[]` oder äquivalent definieren und editieren |
| **Coaching Stufe A** | Slots + Kandidaten sichtbar, ArchetypHinweis, Profil lesbar | `CombinationCoachSlots` zeigt **Key/Value** aus `method_profile`, sonst wie zuvor | Profilwerte **lesend** benutzerfreundlicher labeln (statt nur Schlüsselnamen) |
| **Coaching Stufe B** | Zeitleiste archetypnah (z.B. Schritt pro Station) | **Nein** — ein CoachSchritt = ein Planungsitem | Designentscheid: virtuelle Substeps vs. DBMaterialisierung; Auswirkung auf IstZeit pro Item |
| **Coaching Stufe C** | Timer/Wechsel/Abhaken nach Archetyp | Nur **generischer** CoachTimer pro Planungsitem | Pro Archetyp UIState + Anbindung an `method_profile` |
| **Rahmenprogramm** | Gleiche Inhalte wie Einheit | SlotBlueprint, `from-framework-slot` | Modul-/KombiUX in Rahmen wie in Einheit konsolidieren (Phase 5) |
| **Coaching-Vorschau im Editor** | §9.3 Schritt 7 | **Nein** / nicht als eigener Modus | Optional: dieselbe `CombinationCoachSlots`Ansicht readonly im Übungseditor |
**Pflege:** Bei jeder relevanten Codeänderung diese Tabelle **in demselben PR / derselben Session** anpassen (kein stiller Drift).

View File

@ -3,6 +3,12 @@
**Status:** Entwurf zur fachlichen und technischen Abstimmung · **Stand:** 2026-05-12
**Zweck:** Rahmen für Umsetzung, Integration in Planung/Rahmenprogramm und Durchführung im assistierten Training (Coaching-Modus). Dieses Dokument ist **nicht** implementierungsbindend, bis die markierten **offenen Entscheidungen** geschlossen und der Status angehoben wurde.
**Abgleich mit Code (Stand ~0.8.101, Drift vermeiden):**
- **Kanonische Archetyp-IDs:** fest in `backend/routers/exercises.py` (`COMBINATION_ARCHETYPE_IDS`); fachliche Tabelle und UI-Labels in `frontend/src/constants/combinationArchetypes.js` — die **fachliche Master-Zuordnung** Name↔ID steht in `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` §10.2.1.
- **Coaching:** Stufe **A** (informations-/strukturierte Slot- und Kandidatenansicht + Archetyp-Hilfstext) umgesetzt im Trainings-Coach (`ExerciseFullContent` / `CombinationCoachSlots`); Stufen **B/C** bewusst offen — siehe Fachspez §10.4 und **Anhang A** dort.
- **Umsetzungsplan:** `working/TRAINING_MODULES_IMPLEMENTATION_PLAN.md` (Phasen 2/4 mit „teilweise“).
**Verwandte Dokumente:**
| Dokument | Bezug |

View File

@ -1,8 +1,8 @@
# Umsetzungsplan: Trainingsmodule & Kombinationsübungen
**Bezug:** `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` (Kopf „V3“, Stand 2026-05-12)
**Bezug:** `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` (Kopf „V3“, inkl. **§10.2.1**, **§10.4 Coaching-Stufen**, **Anhang A** Implementierungsabgleich — Drift-Schutz)
**Technische Entwurfsspezifikation:** `technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md`
**Stand dieses Dokuments:** 2026-05-12
**Stand dieses Dokuments:** 2026-05-12 (Abgleich mit Code ~App **0.8.102**)
## Ziele
@ -13,11 +13,20 @@ Umsetzung der MVP-Punkte aus der Fachspezifikation ohne die bestehende Planung z
| Phase | Inhalt | Status |
|-------|--------|--------|
| **1** | **Trainingsmodule (Bibliothek):** Tabellen `training_modules`, `training_module_items`; REST CRUD mit Governance wie andere Bibliotheken; Übernahme in eine bestehende Einheit per `POST /api/training-units/{id}/apply-training-module` (Anfügen ans Ende eines Abschnitts via `section_order_index`); optionale Lineage-Spalte `source_training_module_id` auf Planungsitems; UI: Liste/Editor unter `/planning/training-modules`, Link von der Planung, Modal „Modul übernehmen“ | **umgesetzt (MVP Schritt 1)** |
| **2** | Kombinationsübungen: `exercise_kind`/`combination_*`, Slots, Pools, Haupt-/Nebenmethoden-M:N, Archetyp + Ablaufprofil | geplant |
| **2** | Kombinationsübungen: `exercise_kind`/`combination_*`, Slots, Pools, `method_archetype`, `method_profile` (JSON) | **teilweise** — Migration 056, CRUD/API, Picker/Liste; Übungsformular: geführtes **`method_profile` nach Archetyp** (`CombinationMethodProfileEditor`, `combinationMethodProfileUi.js`) plus RohJSON; **Backend:** keine strenge Validierung Profil ↔ Archetyp | Haupt-/Nebenmethoden an Kombi wo Spec es verlangt; serverseitige Validierung für ProfilSchlüssel optional |
| **3** | Planungsblöcke: Gruppierung, Auflösen, „als Modul speichern“, erweiterter Übernahmemodus (Zwischenposition) | geplant |
| **4** | Coaching-Ansicht: Archetyp-spezifische Darstellung für MVP-Archetypen | geplant |
| **4** | Coaching: Archetyp-Support | **teilweise:** **Stufe A** nach Fachspez §10.4 (Slotliste, Kandidaten aus Katalog geladen, Archetyp-Hilfstexte in `CombinationCoachSlots`/`combinationArchetypes.js`); **Stufe B/C** (Zeitleisten-Splitting, Stations-/Intervall-Timing) — **offen**, siehe Anhang A der Fachspez |
| **5** | Rahmenprogramm: Modulübernahme UX in Slot-Blueprint-Editor konsolidieren | geplant |
## Coaching — verbindliche Arbeitspakete (gegen Spec-Drift)
| Paket | Spec-Referenz | Kurzinhalt |
|-------|----------------|-----------|
| **4a (Ist/Ziel)** | §10.2.1 | Archetyp-Schlüssel bleiben identisch zu `backend/routers/exercises.py` (`COMBINATION_ARCHETYPE_IDS`) und `frontend/src/constants/combinationArchetypes.js`. |
| **4b** | §10.4 Stufe A | Slots + Kandidaten; Archetyp-Hilfstext; `method_profile` **lesend** unter der Kopf-Zeile (Key/WertListe wenn gepflegt); Feintuning Labels optional. |
| **4c** | §10.4 Stufe B | Entscheidung: virtuelle Substeps vs. persistierte Items; Konsistenz `sectionsToPutPayload`/Ist-Zeit. |
| **4d** | §10.4 Stufe C | Archetyp-spezifische Timer/Wechsel/Abhaken an `method_profile` — nach 4b/4c. |
## Phase 1 (technische Notizen)
- **Governance:** `visibility`/`club_id`/`created_by` analog `training_plan_templates`; Listenfilter `library_content_visibility_sql`.
@ -28,3 +37,4 @@ Umsetzung der MVP-Punkte aus der Fachspezifikation ohne die bestehende Planung z
- `DATABASE_SCHEMA.md` bei größeren Schema-Erweiterungen ergänzen.
- `ACCESS_LAYER_ENDPOINT_AUDIT.md` bei neuen mandantenbezogenen Endpunkten fortpflegen.
- **Nach jeder Kombi-/Coach-Änderung:** `functional/… Spezifikation V2.md` **Anhang A** und diese Tabelle Phasen 2/4 abstimmen.

View File

@ -1,6 +1,6 @@
# Shinkan Jinkendo Version Information
APP_VERSION = "0.8.101"
APP_VERSION = "0.8.102"
BUILD_DATE = "2026-05-12"
DB_SCHEMA_VERSION = "20260512056"
@ -21,7 +21,7 @@ MODULE_VERSIONS = {
"groups": "0.1.0",
"skills": "0.1.0",
"methods": "0.1.0",
"exercises": "2.24.1", # Coach/Kombination: Stationen laden Einzelübungen + Archetyp-Hilfstext (Frontend ExerciseFullContent)
"exercises": "2.24.2", # Kombi: geführtes method_profile im Übungsformular nach Archetyp + Coach zeigt Profil als Key/Wert
"training_units": "0.2.0",
"training_programs": "0.1.0",
"planning": "0.9.1", # Kombinationsübungen: Sektionen PATCH/validator + exercise_kind GET; Frontend KEINE Varianten bei combination
@ -35,6 +35,13 @@ MODULE_VERSIONS = {
}
CHANGELOG = [
{
"version": "0.8.102",
"date": "2026-05-12",
"changes": [
"Kombinationsübung beim Anlegen/Bearbeiten: archetypbezogenes Ablaufprofil (geführt) + eingeklappt Roh-JSON (`CombinationMethodProfileEditor`); Schlüsselmanifest `combinationMethodProfileUi.js`. Coach: angelegtes method_profile unter Stationenliste lesbar.",
],
},
{
"version": "0.8.101",
"date": "2026-05-12",

View File

@ -2,7 +2,7 @@
**Zweck:** Überblick über die **wesentlichen, produktiv nutzbaren Funktionen** aus Nutzer- und Fachperspektive zur Weitergabe an Design, Product Discovery oder externe Fachplanung.
**Technischer Detailstand:** App-Version und Schema siehe `backend/version.py` (Stand Code: **0.8.96**, **DB_SCHEMA_VERSION** siehe dort).
**Technischer Detailstand:** App-Version und Schema siehe `backend/version.py` (Stand Code: **0.8.101**, **DB_SCHEMA_VERSION** siehe dort).
**Vertiefung:** Domänenmodell `.claude/docs/functional/DOMAIN_MODEL.md`, Lieferdetal `.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md`, Projektstatus `.claude/docs/PROJECT_STATUS.md`, Entwickler-Handover `docs/HANDOVER.md`.
@ -59,6 +59,7 @@ Die sichtbaren Funktionen hängen von **Rolle** und **Kontext** ab (eingeloggter
- **Medien an der Übung:** Upload, Einbettung, Verknüpfung aus dem **Archiv**; Darstellung in Detail- und Bearbeitungsansicht.
- **Rich-Text-Felder** (Ablauf, Ziele, Hinweise): **Inline-Verweise auf verknüpfte Medien** über eine einheitliche Platzhalter-/Renderlogik (konsistent mit Archiv-Governance).
- **Exercise Blocks** („Bausteine“) und gespeicherte Suchpräferenzen, wo implementiert.
- **Kombinationsübungen** („combination“, Migration 056): Sonderform im Übungskatalog mit **Stationen/Slots**, **Trainingsmethode-/Archetyp** (`method_archetype`), optionalem strukturierten **Ablaufprofil** (`method_profile`). In der Planung wie eine Übung ohne Variante einsetzbar; im **Coach** werden Stations-Kandidaten und Archetyp-Hilfstexte angezeigt (Ausbauschritte B/C nach Fachspez §10.4 dokumentiert unter `.claude/docs/functional/… Kombinationsübungen Spezifikation V2.md` Anhang A).
### 4.2 Fähigkeiten, Methoden, Kataloge
@ -77,7 +78,7 @@ Die sichtbaren Funktionen hängen von **Rolle** und **Kontext** ab (eingeloggter
- **Trainingsvorlagen / Mikrovorlagen** (wo eingerichtet): Struktur wiederverwenden.
- **Trainingsrahmenprogramm (Bibliothek):** übergeordnete Programme mit **Zielen** und **Slots**; Slot-Inhalt technisch als **Blueprint-Trainingsunit** abgebildet.
- **Materialisierung:** aus einem Rahmen-Slot kann eine **konkrete Kalender-Einheit** für eine Gruppe erzeugt werden (API vorhanden; UI-Anbindung kann erweitert werden).
- **Durchführung:** Ansicht zum Abarbeiten einer Einheit; **Coaching-Modus** als separater Erlebnispfad.
- **Durchführung:** Ansicht zum Abarbeiten einer Einheit; **Coaching-Modus** als separater Erlebnispfad (generischer Zeit-Block pro Platzierung); bei **Kombinationsübungen** zusätzliche **Stations-/Kandidaten-Schicht und Archetyp-Hinweise** siehe Kombination-Fachspez **Anhang A** (implementierter Umfang vs. nächste Stufen).
### 4.5 Medienbibliothek und Archiv
@ -117,7 +118,7 @@ Nicht als „broken“ gemeint, sondern als **typische nächste Ausbaustellen**
- Kalender-UX: **„Aus Rahmen übernehmen“** flächendeckend und ggf. bulkfähig anbinden.
- **Policies** für geteilte Rahmen (Wer darf Bibliotheks-Rahmen sehen/kopieren?).
- **Skill-Kategorie-Admin-UI**, **Dark Mode/Responsive/PWA-Ausbau**, **KI-Suche** über Volltext hinaus je nach Backlog.
- **Moderations-Fläche**, Uploader-Benachrichtigung bei Sperre, **Beschwerdeverfahren** laut Handover bewusst noch nicht umgesetzt (Nachfolge von P-13).
- **Coach / Kombination:** nächste Stufen **Zeitleisten-Splitting** und **Archetyp-Timer** (Fachspez §10.4 Stufe B/C; Umsetzungsplan Phase 4bd); **geführtes Erfassen** von `method_profile` im Übungseditor.
---
@ -126,3 +127,4 @@ Nicht als „broken“ gemeint, sondern als **typische nächste Ausbaustellen**
| Datum | Änderung |
|-------|----------|
| 2026-05-12 | Erstfassung für Übergabe an fachliches Design; Abgleich mit Code-Navigation, `version.py`, `HANDOVER.md`, `FEATURES_DELIVERED`, `DOMAIN_MODEL`. |
| 2026-05-12 | Kombinationsübungen + Coaching Stufe A; Verweise auf Fachspezifikation (`…Kombinationsuebungen…` V3 Anhang A) und `TRAINING_MODULES_IMPLEMENTATION_PLAN.md`. |

View File

@ -1,7 +1,7 @@
# Shinkan Jinkendo Entwicklungsstand & Handover
**Stand:** 2026-05-12
**App-Version / DB-Schema:** App **0.8.96**, DB-Schema siehe `backend/version.py` (`DB_SCHEMA_VERSION`)
**App-Version / DB-Schema:** App **0.8.101**, DB-Schema siehe `backend/version.py` (`DB_SCHEMA_VERSION`)
Diese Datei ist die **Einstiegs-Doku für neue Chat-Sessions**: Anforderungen im Detail stehen in `.claude/docs/` (siehe unten); hier der **implementierte Stand**, **Medien-Meilenstein** und **sinnvolle nächste Schritte**.
@ -29,6 +29,8 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
| Zugriffsschicht, Mandant, Governance | `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` |
| Tenant-Endpoints (Audit) | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` |
| Rahmenprogramm · Planung | `.claude/docs/technical/TRAINING_FRAMEWORK_SPEC.md` |
| **Trainingsmodule & Kombinationsübungen (Fachspez, Drift-Schutz)** | `.claude/docs/functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md`10.2.1 Archetyp-IDs, §10.4 Coaching-Stufen, **Anhang A** Code-Abgleich) |
| **Umsetzungsplan** (Module/Kombination/Coach) | `.claude/docs/working/TRAINING_MODULES_IMPLEMENTATION_PLAN.md` |
| Überblick DB | `.claude/docs/technical/DATABASE_SCHEMA.md` |
| Domäne | `.claude/docs/functional/DOMAIN_MODEL.md` |
| **Lieferliste inkl. Medien** | `.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md` §12 |
@ -72,6 +74,12 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
- **036 / 037:** Bibliotheks-Rahmen, Slot-Inhalt als **`training_units`** mit **`framework_slot_id`**; **`POST /api/training-units/from-framework-slot`**.
- **Code:** `training_framework_programs.py`, `training_planning.py`; Frontend **`TrainingFrameworkProgramEditPage.jsx`**, **`createTrainingUnitFromFrameworkSlot`** in `api.js`.
### Trainingsmodule, Kombinationsübungen und Coach (Stand ~0.8.101)
- **Fachspez & Drift-Schutz:** `.claude/docs/functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` (**§10.2.1** IDs, **§10.4** Coaching-Stufen, **Anhang A** Abgleich).
- **Umsetzungsplan:** `.claude/docs/working/TRAINING_MODULES_IMPLEMENTATION_PLAN.md` (Phase **2** / **4** teilweise; Pakete **4ad**).
- **Ist kurz:** Trainingsmodule-Bibliothek (Phase **1**) umgesetzt; Kombi-Katalog (**056**) + Einplanung + Coach **Stufe A** (`CombinationCoachSlots`, `combinationArchetypes.js`). Coach **Stufe B/C** und geführtes **`method_profile`** offen — siehe Fachspez Anhang A.
---
## 4. Stand: Medien-Management (Ist, 2026-05-07)
@ -152,6 +160,7 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
5. **S3/Adapter:** Speicher-Abstraktion (Spec Abschnitt 7) — wenn Produkt es verlangt.
6. **Rahmen/UI:** Kalender „aus Rahmen übernehmen” weiter anbinden (parallel, unabhängig von Medien).
7. **Fachlicher Nutzerüberblick:** bei größeren UX-Änderungen **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`** mitpflegen.
8. **Kombinations-Coach (Archetyp B/C):** Fachspez §10.4; nach Implementierung **Anhang A** + `TRAINING_MODULES_IMPLEMENTATION_PLAN.md` aktualisieren (kein Doc-Drift).
---
@ -159,7 +168,8 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
| Bereich | Einstieg |
|---------|----------|
| Backend API | `backend/main.py`; u.a. **`media_assets.py`**, **`exercises.py`**, **`profiles.py`**, **`training_framework_programs.py`**, `tenant_context.py` |
| Backend API | `backend/main.py`; u.a. **`media_assets.py`**, **`exercises.py`** (`COMBINATION_ARCHETYPE_IDS`, `enrich_exercise_detail`), **`profiles.py`**, **`training_framework_programs.py`**, `tenant_context.py` |
| Coach-Kombination (Frontend) | `TrainingCoachPage.jsx`, `ExerciseFullContent.jsx`, `CombinationCoachSlots.jsx`, `constants/combinationArchetypes.js` |
| Migrationen | `backend/migrations/` (040+ Mitgliedschaft/Governance; **045+** Medien-Stack) |
| Frontend API | `frontend/src/utils/api.js` |
| Aktiver Verein (UI) | `frontend/src/utils/activeClub.js`, `AuthContext.jsx` |

View File

@ -11,7 +11,7 @@ import {
sortCombinationSlotsForDisplay,
} from '../constants/combinationArchetypes'
export default function CombinationCoachSlots({ combinationSlots, methodArchetype }) {
export default function CombinationCoachSlots({ combinationSlots, methodArchetype, methodProfile }) {
const slots = useMemo(() => sortCombinationSlotsForDisplay(combinationSlots), [combinationSlots])
const candidateIds = useMemo(() => {
@ -111,6 +111,42 @@ export default function CombinationCoachSlots({ combinationSlots, methodArchetyp
{archetypeCoachHint(archeKey)}
</p>
{methodProfile && typeof methodProfile === 'object' && !Array.isArray(methodProfile) && Object.keys(methodProfile).length ? (
<div
style={{
margin: '0 0 14px',
padding: '8px 10px',
borderRadius: '8px',
background: 'var(--surface)',
border: '1px solid var(--border)',
}}
>
<div style={{ fontSize: '0.7rem', fontWeight: 700, color: 'var(--text3)', textTransform: 'uppercase', marginBottom: '6px' }}>
Geplantes Ablaufprofil (Katalog)
</div>
<dl style={{ margin: 0, fontSize: '0.82rem', lineHeight: 1.45 }}>
{Object.entries(methodProfile)
.sort(([a], [b]) => a.localeCompare(b, 'de'))
.map(([k, val]) => (
<div key={k} style={{ marginBottom: '4px', display: 'grid', gridTemplateColumns: 'minmax(72px,1fr) minmax(0,2fr)', gap: '6px 10px' }}>
<dt style={{ margin: 0, fontWeight: 600, color: 'var(--text3)', wordBreak: 'break-all' }}>{k}</dt>
<dd style={{ margin: 0, color: 'var(--text1)' }}>
{typeof val === 'boolean'
? val
? 'ja'
: 'nein'
: typeof val === 'number'
? String(val)
: typeof val === 'string'
? val
: JSON.stringify(val)}
</dd>
</div>
))}
</dl>
</div>
) : null}
{!slots.length ? (
<p style={{ margin: 0, color: 'var(--text3)', fontSize: '0.88rem' }}>Keine Stationen hinterlegt.</p>
) : (

View File

@ -0,0 +1,175 @@
import React, { useMemo, useState } from 'react'
import { archetypeCoachHint, combinationArchetypeLabel } from '../constants/combinationArchetypes'
import {
METHOD_PROFILE_GUI_FIELDS,
parseProfileJson,
setFullProfileRawJson,
updateProfileGuided,
} from '../utils/combinationMethodProfileUi'
function clampInt(n, min, max) {
if (!Number.isFinite(n)) return null
let x = n
if (typeof min === 'number' && x < min) x = min
if (typeof max === 'number' && x > max) x = max
return Math.round(x)
}
/**
* Kombination: geführtes Ablaufprofil + optionales Roh-JSON.
*/
export default function CombinationMethodProfileEditor({
methodArchetype,
methodProfileJson,
onChangeMethodProfileJson,
}) {
const arch = typeof methodArchetype === 'string' ? methodArchetype.trim() : ''
const fieldsGui = METHOD_PROFILE_GUI_FIELDS[arch]
const fields = Array.isArray(fieldsGui) ? fieldsGui : null
const parseState = useMemo(() => parseProfileJson(methodProfileJson || '{}'), [methodProfileJson])
const [rawOpenError, setRawOpenError] = useState(null)
const [rawDraft, setRawDraft] = useState(null)
const profileObj = parseState.ok ? parseState.obj : {}
const applyGuided = (key, value, kind) => {
if (kind === 'bool') {
const res = updateProfileGuided(arch, methodProfileJson || '{}', key, value, 'bool')
if (!res.ok) return
onChangeMethodProfileJson(res.json)
return
}
if (value === '' || value === undefined || value === null) {
const res = updateProfileGuided(arch, methodProfileJson || '{}', key, '', 'int')
if (!res.ok) return
onChangeMethodProfileJson(res.json)
return
}
const num = typeof value === 'number' ? value : parseInt(String(value), 10)
if (!Number.isFinite(num)) return
const def = METHOD_PROFILE_GUI_FIELDS[arch]?.find((f) => f.key === key && f.kind === 'int')
const c = clampInt(num, def?.min, def?.max)
if (c == null) return
const res = updateProfileGuided(arch, methodProfileJson || '{}', key, c, 'int')
if (!res.ok) return
onChangeMethodProfileJson(res.json)
}
const archeLabel = arch ? combinationArchetypeLabel(arch) : null
const openAdvanced = () => {
setRawOpenError(null)
const p = parseProfileJson(methodProfileJson || '{}')
setRawDraft(p.ok ? JSON.stringify(p.obj, null, 2) : String(methodProfileJson || ''))
}
return (
<div style={{ marginTop: '10px' }}>
{arch ? (
<p style={{ fontSize: '12px', color: 'var(--text2)', lineHeight: 1.48, margin: '0 0 12px' }}>
<strong style={{ color: 'var(--text1)' }}>
Coach &amp; Planung:{' '}
{archeLabel && archeLabel !== arch ? `${archeLabel} · ` : ''}
</strong>
{archetypeCoachHint(arch)}
</p>
) : (
<p style={{ fontSize: '12px', color: 'var(--text3)', margin: '0 0 12px' }}>
Wähle einen Archetyp, um das Ablaufprofil strukturiert zu erfassen oder nur das JSON weiter unten.
</p>
)}
{!parseState.ok ? (
<p style={{ color: 'var(--danger)', fontSize: '13px', marginBottom: '10px' }}>{parseState.error}</p>
) : null}
{fields && fields.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px', marginBottom: '12px' }}>
{fields.map((def) => {
if (def.kind === 'bool') {
const ck = !!profileObj[def.key]
return (
<label
key={def.key}
style={{ display: 'flex', gap: '8px', alignItems: 'center', cursor: 'pointer', fontSize: '0.9rem' }}
>
<input type="checkbox" checked={ck} onChange={(e) => applyGuided(def.key, e.target.checked, 'bool')} />
<span>{def.label}</span>
</label>
)
}
const v = profileObj[def.key]
const str =
v === undefined || v === null ? '' : typeof v === 'number' && Number.isFinite(v) ? String(v) : String(v)
return (
<div className="form-row" key={def.key}>
<label className="form-label" style={{ fontSize: '12px' }}>
{def.label}
</label>
<input
type="number"
className="form-input"
min={def.min}
max={def.max}
value={str}
placeholder="optional"
onChange={(e) => {
const t = e.target.value
if (t.trim() === '') applyGuided(def.key, '', 'int')
else applyGuided(def.key, parseInt(t, 10), 'int')
}}
/>
</div>
)
})}
</div>
) : arch && fields && fields.length === 0 ? (
<p style={{ fontSize: '12px', color: 'var(--text3)', margin: '0 0 10px' }}>
Für diesen Archetyp gibt es keine vorgegebenen Profilfelder nutze die Freitexte der Kombination oder RohJSON bei Bedarf.
</p>
) : null}
<details
style={{
marginTop: '4px',
borderRadius: '8px',
border: '1px solid var(--border)',
padding: '8px 10px',
background: 'var(--surface)',
}}
onToggle={(ev) => {
if (ev.target.open) openAdvanced()
}}
>
<summary style={{ cursor: 'pointer', fontSize: '0.82rem', fontWeight: 600 }}>Erweitert: JSON direkt bearbeiten</summary>
<p style={{ fontSize: '11px', color: 'var(--text3)', margin: '8px 0 6px', lineHeight: 1.4 }}>
Zusätzliche Schlüssel (Piloten). Geführte Felder können dieselben Schlüssel beim nächsten Speichern überlagern.
</p>
<textarea
className="form-input"
rows={8}
style={{ fontFamily: 'Consolas,monospace', fontSize: '12px' }}
value={rawDraft != null ? rawDraft : methodProfileJson || '{}'}
onChange={(e) => {
setRawDraft(e.target.value)
setRawOpenError(null)
}}
spellCheck={false}
onBlur={() => {
const src = rawDraft != null ? rawDraft : methodProfileJson
const res = setFullProfileRawJson(src || '{}')
if (!res.ok) {
setRawOpenError(res.error)
return
}
setRawOpenError(null)
setRawDraft(null)
onChangeMethodProfileJson(res.json)
}}
/>
{rawOpenError ? <p style={{ color: 'var(--danger)', fontSize: '12px', marginTop: '6px' }}>{rawOpenError}</p> : null}
</details>
</div>
)
}

View File

@ -114,6 +114,7 @@ export default function ExerciseFullContent({ exercise, loading, error, exercise
<CombinationCoachSlots
combinationSlots={exercise.combination_slots}
methodArchetype={exercise.method_archetype}
methodProfile={exercise.method_profile}
/>
) : null}
<h2 style={{ margin: '0 0 8px', fontSize: '1.2rem', lineHeight: 1.35 }}>{exercise.title}</h2>

View File

@ -7,6 +7,7 @@ import ExerciseProgressionGraphPanel from '../components/ExerciseProgressionGrap
import ExerciseMediaThumbTile from '../components/ExerciseMediaThumbTile'
import MediaPreviewModal from '../components/MediaPreviewModal'
import ReportContentModal from '../components/ReportContentModal'
import CombinationMethodProfileEditor from '../components/CombinationMethodProfileEditor'
import {
SHINKAN_EXERCISE_MEDIA_DRAG_MIME,
buildExerciseMediaDragPayload,
@ -1016,13 +1017,13 @@ function ExerciseFormPage() {
{formData.exercise_kind === 'combination' ? (
<>
<div className="form-row">
<label className="form-label">Methoden-Archetyp (optional)</label>
<label className="form-label">Methoden-Archetyp (für Coach &amp; Planung empfohlen)</label>
<select
className="form-input"
value={formData.method_archetype || ''}
onChange={(e) => updateFormField('method_archetype', e.target.value)}
>
<option value=""> später wählen </option>
<option value=""> noch nicht festgelegt </option>
{COMBINATION_ARCHETYPE_OPTIONS.map((o) => (
<option key={o.id} value={o.id}>
{o.label}
@ -1031,14 +1032,11 @@ function ExerciseFormPage() {
</select>
</div>
<div className="form-row">
<label className="form-label">Ablaufprofil (JSON, optional)</label>
<textarea
className="form-input"
rows={4}
value={formData.method_profile_json || '{}'}
onChange={(e) => updateFormField('method_profile_json', e.target.value)}
spellCheck={false}
placeholder='{"work_seconds":45,"rest_seconds":15,"rounds":3}'
<label className="form-label">Ablaufprofil (über Archetyp)</label>
<CombinationMethodProfileEditor
methodArchetype={formData.method_archetype || ''}
methodProfileJson={formData.method_profile_json || '{}'}
onChangeMethodProfileJson={(s) => updateFormField('method_profile_json', s)}
/>
</div>
<div>

View File

@ -0,0 +1,165 @@
/**
* Geführtes method_profile für Kombinationsübungen Felder nach method_archetype.
* Unbekannte JSON-Schlüssel bleiben beim Zusammenführen erhalten (Erweiterbarkeit).
*/
const INT_MAX = 86400
function parseProfileJson(raw) {
if (typeof raw !== 'string' || !raw.trim()) return { ok: true, obj: {} }
try {
const p = JSON.parse(raw)
if (!p || typeof p !== 'object' || Array.isArray(p)) {
return { ok: false, error: 'Ablaufprofil muss ein JSON-Objekt sein.' }
}
return { ok: true, obj: { ...p } }
} catch {
return { ok: false, error: 'Ablaufprofil (JSON): Syntax ungültig.' }
}
}
/** Pro Archetyp: UI-Feldbeschreibungen (Werte werden in method_profile geschrieben) */
export const METHOD_PROFILE_GUI_FIELDS = Object.freeze({
sequence_linear: [
{
key: 'hint_step_duration_sec',
kind: 'int',
label: 'Orientierung: Sekunden je Station/Schritt (optional)',
min: 5,
max: INT_MAX,
},
{
key: 'block_intro_sec',
kind: 'int',
label: 'Einführung / Demon am Block Gesamt (Sek., optional)',
min: 0,
max: INT_MAX,
},
],
circuit_rotate_time: [
{
key: 'work_seconds',
kind: 'int',
label: 'Arbeitszeit pro Station (Sek.)',
min: 5,
max: INT_MAX,
},
{
key: 'transition_seconds',
kind: 'int',
label: 'Wechsel / Rotation (Sek., optional)',
min: 0,
max: INT_MAX,
},
{
key: 'rest_seconds',
kind: 'int',
label: 'Pause zwischen Runden oder Stationen-Folgen (Sek., optional)',
min: 0,
max: INT_MAX,
},
{
key: 'rounds',
kind: 'int',
label: 'Runden (optional, wenn alle Station je Runde angefahren werden)',
min: 1,
max: 999,
},
],
circuit_all_parallel: [
{
key: 'explain_before_seconds',
kind: 'int',
label: 'Zeitfenster VorabErklärung aller Stationen (Sek., optional)',
min: 0,
max: INT_MAX,
},
{
key: 'simultaneous_start',
kind: 'bool',
label: 'Alle Stationen starten zusammen nach Erklärung',
},
],
station_parcour: [
{
key: 'allow_free_visit_order',
kind: 'bool',
label: 'Reihenfolge der Besuche frei (Parcours / Abhaken-Logik später im Coach)',
},
],
pair_superset: [
{
key: 'switch_seconds',
kind: 'int',
label: 'Orientierung: Wechselpause A↔B (Sek., optional)',
min: 0,
max: INT_MAX,
},
{
key: 'work_seconds_per_side',
kind: 'int',
label: 'Arbeit pro Rolle oder Seite (Sek., optional)',
min: 5,
max: INT_MAX,
},
],
time_domain_interval: [
{
key: 'work_seconds',
kind: 'int',
label: 'Intervall: Belastungszeit (Sek.)',
min: 5,
max: INT_MAX,
},
{
key: 'rest_seconds',
kind: 'int',
label: 'Intervall: Erholungszeit (Sek., optional)',
min: 0,
max: INT_MAX,
},
{
key: 'interval_rounds',
kind: 'int',
label: 'Anzahl Wiederholungen / Runden der Domäne (optional)',
min: 1,
max: 999,
},
],
free_method_block: [],
})
/**
* Aktualisiert method_profile unter Beibehaltung nicht-GUI Schlüssel.
*/
export function updateProfileGuided(archetype, rawJson, key, parsedValue, kind) {
const arch = typeof archetype === 'string' ? archetype.trim() : ''
const parsed = parseProfileJson(rawJson)
if (!parsed.ok) return parsed
const next = { ...parsed.obj }
if (kind === 'bool') {
if (parsedValue) next[key] = true
else delete next[key]
} else if (kind === 'int') {
if (parsedValue === null || parsedValue === undefined || parsedValue === '') {
delete next[key]
} else {
const n = typeof parsedValue === 'number' ? parsedValue : parseInt(String(parsedValue), 10)
if (!Number.isFinite(n)) delete next[key]
else next[key] = n
}
}
const outJson = JSON.stringify(next)
return { ok: true, obj: next, json: outJson === '{}' ? '{}' : outJson }
}
export function setFullProfileRawJson(rawEditable) {
const parsed = parseProfileJson(rawEditable)
if (!parsed.ok) return parsed
const j = JSON.stringify(parsed.obj)
return { ok: true, obj: parsed.obj, json: j === '{}' ? '{}' : j }
}
export { parseProfileJson, INT_MAX }