Files
GameTime/.ideai/memory/gametime-architecture-exercise-steps.md
Blomios 733b7bb597 docs(ideai): mémoire, sprint et tickets du chantier exercices à étapes
Ajoute les notes mémoire d'architecture et UX pour les exercices à
étapes, un nouveau sprint, clôture le ticket #56 et reflète les
tickets #46/#53, ajoute le cadrage des tickets #57 à #63.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:32:33 +02:00

167 lines
8.8 KiB
Markdown

---
name: gametime-architecture-exercise-steps
description: memory note gametime-architecture-exercise-steps
metadata:
type: project
---
# GameTime — Architecture exercices à plusieurs étapes
Décision d'architecture pour le ticket #54, basée sur la mémoire UX `gametime-ux-exercise-steps` et les patterns existants : snapshots, `ActiveSetResult` distinct des résultats détaillés, timers persistés via tables dédiées (`ActiveRestState`, `ActiveScoreStopwatchState`).
## Principe métier
Les étapes sont un rythme interne d'un exercice, pas une nouvelle mesure de série. Elles coexistent avec les mesures existantes `Temps`, `Répétitions`, `Score`.
- Si `Répétitions` est active sur la série, un passage complet de la séquence d'étapes = une répétition/passsage réalisé.
- Si `Temps` est actif sur la série, il reste une durée/fenêtre globale de série, indépendante des timers d'étapes.
- Le score de série reste porté par `ActiveSetResult` / `WorkoutHistorySetResult`.
- Les résultats d'étapes restent dans des entités dédiées et ne se mélangent jamais avec les résultats de série.
## Domaine exercice
Ajouter :
```dart
enum ExerciseStepType { time, reps }
final class ExerciseStep {
final String id;
final int position;
final String name;
final ExerciseStepType type;
final int defaultTargetValue;
final bool hasScore;
final ScoreInputMode scoreInputMode;
final String? scoreLabel;
final String? scoreUnit;
final double? defaultTargetScore;
final int? defaultTargetScoreTimeMs;
}
```
Ajouter `List<ExerciseStep> steps` sur `Exercise`.
Invariants :
- `steps.length <= 8`.
- positions uniques, contiguës et `>= 0` dans l'ordre affiché.
- `name` non vide.
- `defaultTargetValue > 0` ; unité métier : secondes si `type=time`, répétitions si `type=reps`.
- si `hasScore=false`, aucune valeur/label de score d'étape ne doit être significative.
- si `hasScore=true` et `scoreInputMode=manual`, `scoreLabel` et `scoreUnit` obligatoires ; `defaultTargetScore` nullable mais si renseigné `>= 0`.
- si `hasScore=true` et `scoreInputMode=stopwatch`, `defaultTargetScoreTimeMs` nullable mais si renseigné `> 0`; pas d'unité libre.
- score manuel et score chrono d'étape sont exclusifs.
- `type=time` + score chrono d'étape est autorisé mais doit rester un avertissement UX non bloquant.
## Snapshot pattern
Les étapes doivent suivre le même pattern que les autres propriétés d'exercice :
`Exercise.steps` -> snapshot dans `ProgramExercise` -> inclus dans `ProgramExercise.toSnapshotJson()` -> inclus dans `WorkoutTemplateProgram.programSnapshotJson` -> résolu dans `ActiveWorkoutSession.resolvedTemplateSnapshotJson` -> copié dans l'historique.
Recommandation Drift :
- source normalisée : table `exercise_steps` liée à `exercises`.
- snapshot programme : colonne `exercise_steps_snapshot_json` sur `program_exercises` plutôt qu'une table normalisée de snapshots pour le MVP.
- historique : les snapshots utiles sont copiés dans `WorkoutHistoryStepResult`, et le snapshot global reste dans `historySnapshotJson`.
Les modifications ultérieures d'un Exercise ne modifient pas les ProgramExercise existants.
## Modèle d'exécution
Ne pas étendre `ActiveSetResult` pour porter les détails d'étapes. `ActiveSetResult` reste le résultat global de série.
Ajouter une table/entité dédiée pour la progression courante : `ActiveExerciseStepProgressState`.
Champs recommandés :
- champs sync communs.
- `activeWorkoutSessionId`.
- `programIndex`, `exerciseIndex`, `setIndex`.
- `currentPassageIndex` : `>= 0`.
- `currentStepIndex` : `>= 0`.
- `currentStepSnapshotId`.
- `status` : `notStarted | waitingManual | runningTimer | pausedTimer | stoppedTimer | sequenceComplete`.
- `startedAt?` : horodatage du run timer courant.
- `accumulatedMs` : `>= 0`, temps déjà accumulé pour l'étape timer courante.
- `lastTransitionAt`.
Contraintes :
- unique `(active_workout_session_id, program_index, exercise_index, set_index)`.
- pas de compteur uniquement mémoire ; tout timer d'étape actif est reconstituable depuis `startedAt + accumulatedMs`.
- pause séance : un `runningTimer` devient `pausedTimer` en figeant `accumulatedMs`.
- kill app : à la reprise, recalculer depuis les horodatages et avancer automatiquement les étapes chronométrées écoulées jusqu'à la première étape manuelle ou fin de séquence.
Ajouter une table/entité de résultats actifs : `ActiveExerciseStepResult`.
Champs recommandés :
- champs sync communs.
- `activeWorkoutSessionId`.
- `programSnapshotId`, `exerciseSnapshotId`.
- `programIndex`, `exerciseIndex`, `setIndex`.
- `passageIndex`, `stepIndex`, `stepSnapshotId`.
- snapshots : `stepNameSnapshot`, `stepTypeSnapshot`, `targetValueSnapshot`, `hasScoreSnapshot`, `scoreInputModeSnapshot`, `scoreLabelSnapshot?`, `scoreUnitSnapshot?`, `targetScoreSnapshot?`, `targetScoreTimeMsSnapshot?`.
- `status` : `completed | skipped`.
- `startedAt?`, `completedAt?`.
- `actualTimeMs?` pour étape `time`.
- `actualReps?` pour étape `reps` si correction future ; MVP peut enregistrer la cible quand validée ou laisser null avec statut completed selon choix UI, mais l'historique doit rester lisible.
- `actualScore?` pour score manuel d'étape.
- `actualScoreTimeMs?` pour score chrono d'étape.
- `note?` optionnel.
Contraintes :
- unique `(active_workout_session_id, program_index, exercise_index, set_index, passage_index, step_index)`.
- `skipped` implique toutes les valeurs `actual*` nulles.
- `actualTimeMs` seulement pour `stepType=time`.
- `actualReps` seulement pour `stepType=reps`.
- `actualScore` seulement si `hasScoreSnapshot=true` et `scoreInputModeSnapshot=manual`.
- `actualScoreTimeMs` seulement si `hasScoreSnapshot=true` et `scoreInputModeSnapshot=stopwatch`.
- score manuel et score chrono jamais remplis simultanément.
## Historique
Ajouter `WorkoutHistoryStepResult`, distinct de `WorkoutHistorySetResult`.
Champs analogues à `ActiveExerciseStepResult`, avec `workoutHistoryId` au lieu de `activeWorkoutSessionId` et snapshots complets pour affichage autonome.
À la clôture d'une séance :
- créer `WorkoutHistory` et `WorkoutHistorySetResult` comme aujourd'hui pour les résultats globaux de série.
- copier tous les `ActiveExerciseStepResult` de la session vers `WorkoutHistoryStepResult`.
- ne jamais recalculer `WorkoutHistorySetResult.actualReps` depuis les step results sans décision explicite du use case ; les passages réalisés peuvent alimenter l'UI, mais le résultat de série reste sa propre source.
## Drift / migration
Le schéma actuel est `schemaVersion = 7`. Le ticket #54 doit passer à `schemaVersion = 8`.
Ajouts recommandés :
- table `exercise_steps`.
- colonne `exercise_steps_snapshot_json` sur `program_exercises`.
- table `active_exercise_step_progress_states`.
- table `active_exercise_step_results`.
- table `workout_history_step_results`.
- index de session sur les tables actives.
- index history sur `workout_history_step_results(workout_history_id)`.
- contraintes CHECK pour types, statuts, valeurs positives/non négatives.
## Audio / bips
Choix recommandé : introduire un port applicatif/presentation `ExerciseStepAudioCuePlayer` ou équivalent, avec méthodes métier `playShortCountdownBeep()` et `playLongCompletionBeep()`.
Adapter Flutter recommandé : `audioplayers` avec deux assets très courts bundlés (`short_beep`, `long_beep`), préchargés et joués en mode faible latence si possible.
Raison : solution mature et multiplateforme iOS/Android, fiable pour distinguer bip court et bip long. `SystemSound` est plus simple mais ne garantit pas un bip long distinct ni un contrôle suffisant. Une génération synthétique pure éviterait les assets mais augmente la complexité native/test.
Invariants de test :
- les widgets/use cases dépendent du port, jamais directement du lecteur audio réel.
- tests unitaires/widget avec fake player uniquement.
- l'absence/échec audio ne doit pas bloquer la progression d'étape.
## Tickets créés
- #56 `[DevBackend] Modèle domain + Drift pour exercices à étapes`.
- #58 `[DevBackend] Exécution persistante des étapes et résultats par passage`, dépend de #56.
- #59 `[DevFrontend] Éditeur d'exercice avec séquence d'étapes`, dépend de #56.
- #60 `[DevFrontend] Exécution de séance avec module séquence et bips`, dépend de #58 et #59.
- #61 `[DevFrontend] Plan de séance et historique avec résultats d'étapes`, dépend de #58.
- #62 `[QA] Validation exercices à étapes, persistance et historique`, dépend de #60 et #61.
Ordre recommandé : #56 -> #58 -> #59 -> #60 et #61 -> #62.
Note orchestration : le sprint dédié `Exercice editor enhancement` existe avec id `1bb8bdf2-9c35-4f53-9a31-1390a47bec63`, mais l'outil de création de ticket exposé à Architect ne permet pas de renseigner `sprintId`. Les tickets ont donc été créés liés à #54 et devront être rattachés au sprint par l'orchestrateur si nécessaire.