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>
8.8 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| gametime-architecture-exercise-steps | memory note gametime-architecture-exercise-steps |
|
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étitionsest active sur la série, un passage complet de la séquence d'étapes = une répétition/passsage réalisé. - Si
Tempsest 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
>= 0dans l'ordre affiché. namenon vide.defaultTargetValue > 0; unité métier : secondes sitype=time, répétitions sitype=reps.- si
hasScore=false, aucune valeur/label de score d'étape ne doit être significative. - si
hasScore=trueetscoreInputMode=manual,scoreLabeletscoreUnitobligatoires ;defaultTargetScorenullable mais si renseigné>= 0. - si
hasScore=trueetscoreInputMode=stopwatch,defaultTargetScoreTimeMsnullable 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_stepsliée àexercises. - snapshot programme : colonne
exercise_steps_snapshot_jsonsurprogram_exercisesplutô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 danshistorySnapshotJson.
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
runningTimerdevientpausedTimeren figeantaccumulatedMs. - 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 étapetime.actualReps?pour étaperepssi 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). skippedimplique toutes les valeursactual*nulles.actualTimeMsseulement pourstepType=time.actualRepsseulement pourstepType=reps.actualScoreseulement sihasScoreSnapshot=trueetscoreInputModeSnapshot=manual.actualScoreTimeMsseulement sihasScoreSnapshot=trueetscoreInputModeSnapshot=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
WorkoutHistoryetWorkoutHistorySetResultcomme aujourd'hui pour les résultats globaux de série. - copier tous les
ActiveExerciseStepResultde la session versWorkoutHistoryStepResult. - ne jamais recalculer
WorkoutHistorySetResult.actualRepsdepuis 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_jsonsurprogram_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.