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

8.8 KiB

name, description, metadata
name description metadata
gametime-architecture-exercise-steps memory note gametime-architecture-exercise-steps
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 :

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.