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>
This commit is contained in:
167
.ideai/memory/gametime-architecture-exercise-steps.md
Normal file
167
.ideai/memory/gametime-architecture-exercise-steps.md
Normal file
@ -0,0 +1,167 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user