Files
GameTime/.ideai/tickets/93/carnet.md
Blomios b1d1bd293f chore(tickets): met à jour le suivi du ticket #93
Métadonnées de suivi IdeA pour le ticket #93 (refonte UI écran de séance), validé GO par QA.

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

551 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
issueRef: "#93"
version: 5
updatedBy: {"kind":"agent","agent_id":"f3408f5d-469c-4f64-9485-d8b218f3ff26"}
updatedAt: 1784705305380
---
# UX — Revue écran dexécution séance
## Contexte lu
Ticket utilisateur #93 : retour direct sur `workout_execution_screen.dart` après lamélioration récente du sans-scroll (#90). Le principe **tout sans scroll** est validé par lutilisateur et doit être conservé.
Mémos pris en compte :
- `gametime-session-execution-timer-refactor` : lécran sorganise autour de lexercice actif ; `Démarrer lexercice` lance les chronos logiques de début de série.
- `gametime-ux-execution-nav-and-program-simplification` : exécution optimisée effort, navigation via `Voir le plan`, pas de surcharge.
- `gametime-ux-series-counter` : le compteur de série doit rester un repère principal.
- `gametime-ux-exercise-steps` : `Passage 1/10` ou `Passage en cours` est déjà linformation de progression de séquence.
Code observé dans `lib/presentation/workout_execution_screen.dart` :
- `_ExecutionContextHeader` affiche aujourdhui `Série X/Y`, `Programme X/Y · Exercice X/Y`, temps écoulé, nom dexercice, temps de série et bouton `Démarrer lexercice` dans un même panneau.
- `_StepSequencePanel` affiche déjà `Passage X / Y` ou `Passage en cours`.
- `_StepSetResultSummary` réaffiche ensuite `Passages réalisés : X / Y`, redondant.
- `_TimedStepBody` contient le compteur détape et le bouton de démarrage, dans un `FittedBox`, ce qui peut expliquer le rendu minuscule tant que létat initial na pas démarré.
- Les références de performance passent par `_formatReferenceTime(...)`; les valeurs affichées en milliers doivent être traitées comme bug dunité/source, pas comme choix UX.
## Décision générale
Conserver lécran actif **sans scroll** et réduire la hiérarchie à trois zones fixes :
```text
AppBar compacte
Bloc série + exercice actif
Séquence / mesures actives
Actions de série en bas
```
Lutilisateur doit lire immédiatement :
1. quelle séance/programme est en cours ;
2. quelle série il fait ;
3. quel exercice il fait ;
4. quoi lancer ou valider maintenant.
Tout le reste devient secondaire ou disparaît.
## Layout cible — écran actif
### AppBar
Titre principal : nom de la séance.
Sous-titre ou ligne compacte à côté/au-dessous selon composant disponible : nom du programme courant, tronqué à une ligne.
```text
Séance tirs du soir
Programme finition longue…
```
ou, si lAppBar ne permet pas de sous-titre proprement :
```text
Séance tirs du soir · Finition longue…
```
Règles :
- le nom de séance reste prioritaire ;
- le programme est secondaire, style `labelSmall` ou `bodySmall`, couleur texte secondaire ;
- max 1 ligne, `TextOverflow.ellipsis` ;
- ne pas afficher `Programme 1/2` dans le header principal ;
- conserver les actions `Voir le plan` et `Pause`.
### Bloc haut — série + exercice
Remplacer le header actuel par un panneau plus court, toujours Court Blazer, liseré crimson 2 px, rayon 6 px.
Structure :
```text
SÉRIE
2 / 4
Dribble combo [médias]
Temps de série : 00:45 [Démarrer]
```
Règles de hiérarchie :
- `SÉRIE` en label uppercase Archivo, texte secondaire.
- `2 / 4` en Anton, or, visible mais compact : environ 40-46 px, pas plus si lécran est contraint.
- Nom dexercice en `titleLarge`, max 1 ligne, ellipsis. Si le nom est vraiment long, ne pas prendre deux lignes par défaut : lécran est sans scroll, la hauteur est précieuse.
- Icône médias seule si disponible, tooltip `Voir les médias de lexercice`.
- `Temps de série` reste dans le bloc exercice actif, conformément au mémo timer, mais compact.
- Le temps écoulé global de séance peut rester discret dans lAppBar ou dans une petite pastille secondaire, mais ne doit plus concurrencer série/exercice.
Supprimer du bloc haut :
```text
Programme 1/2 · Exercice 3/8
```
Justification : ce niveau de position est utile au plan de séance, pas au geste immédiat. Lutilisateur demande explicitement série + exercice comme informations suffisantes.
## Décisions de conception
### Point 2 — Retirer `Passages réalisés`
À supprimer de lécran actif quand une séquence est affichée :
```text
Passages réalisés : 0 / 10
```
La source visible devient uniquement len-tête de séquence :
```text
Passage 1 / 10
```
ou :
```text
Passage en cours
```
Implication UI : `_StepSetResultSummary` ne doit plus afficher les passages. Si lexercice a un score manuel de série, conserver uniquement le champ score, en version compacte :
```text
Score (paniers)
[ ]
```
Si aucun score manuel de série nest actif, supprimer complètement cette zone pour gagner la hauteur.
Exception : quand la séquence est terminée, le panneau peut afficher :
```text
Séquence terminée
10 / 10 passages
```
mais uniquement dans le panneau de séquence, pas dans un bloc séparé.
### Point 3 — Simplifier la première partie de lécran
Nouvelle hiérarchie validée :
- AppBar : séance + programme courant tronqué.
- Bloc principal : série + exercice.
- Le détail `Programme X/Y · Exercice X/Y` disparaît du bloc principal.
- Le plan de séance reste lendroit pour voir la position complète.
Libellés conservés :
```text
Voir le plan
Pause
SÉRIE
Temps de série
```
Libellés retirés du header actif :
```text
Programme 1/2 · Exercice 3/8
```
### Point 4 — Raccourcir le bouton `Démarrer lexercice`
Libellé cible dans le bouton principal :
```text
Démarrer
```
Tooltip / accessibilité :
```text
Démarrer lexercice
```
Raison : laction est déjà située dans le bloc de lexercice actif ; le mot `exercice` est redondant et consomme trop de largeur.
Dans les confirmations existantes où lutilisateur na pas démarré le chrono, garder un libellé explicite :
```text
Démarrer
```
avec message :
```text
Tu nas pas démarré le chrono de cette série.
```
Action alternative inchangée :
```text
Terminer sans chrono
```
## Bugs purs à corriger sans débat de conception
### Point 5 — Unités de temps affichées en milliers
Bug. Toute valeur de temps affichée à lutilisateur doit être formatée, jamais rendue en entier brut.
Formats attendus :
- Durée de série / repos / temps global : `00:45`, `12:08`, `1:02:10` si heure nécessaire.
- Référence de temps courte hors chrono score : `45 s`, `1:12`.
- Score chrono : `00:42.8` ou `1:04.2`.
- Objectif détape : `Objectif : 10 s`, pas `10000`.
À vérifier côté implémentation :
- si une valeur sappelle `actualTimeMs`, `actualScoreTimeMs` ou `targetScoreTimeMs`, elle doit passer par un formatter en millisecondes ;
- si une valeur issue dun champ en secondes (`targetTimeSeconds`, `defaultTargetValue` détape temps) est transmise à un formatter millisecondes, elle doit être convertie avant affichage ;
- aucune référence de performance ne doit afficher directement une valeur brute `5000`, `12000`, etc.
Ce point peut nécessiter Architect si la source de donnée est ambiguë ou incohérente entre secondes et millisecondes. Si le problème est seulement un mauvais formatter dans lécran, DevFrontend peut corriger directement.
### Point 6 — État initial de létape chronométrée affiché trop petit
Bug UI. Le compteur et le bouton dune étape temps doivent avoir le même statut visuel avant et après démarrage.
État initial attendu :
```text
ÉTAPE 1 / 4
Dribble main droite
Objectif : 10 s
00:10
[Démarrer]
[Passer létape] [...]
```
Règles visuelles :
- `00:10` en Anton / style timer, or, taille lisible équivalente à létat running.
- Bouton `Démarrer` hauteur minimale 44-48 px, texte normal, jamais réduit à une taille miniature.
- Éviter que le `FittedBox` réduise tout le body à cause dune contrainte verticale trop basse. Réduire les espacements ou donner une hauteur stable au body, mais ne pas scaler le texte du timer et du bouton sous une taille lisible.
- Le libellé interne de létape temps devient aussi `Démarrer`, pas `Démarrer la séquence`, pour rester court. Le contexte `Passage 1/10` suffit.
- Si cest un chrono suivant prêt, afficher :
```text
Chrono prêt
[Démarrer]
```
au lieu de :
```text
Chrono suivant prêt
[Démarrer le chrono]
```
## Layout cible — avec séquence
État exercice avec étapes, avant démarrage :
```text
AppBar
Séance tirs du soir
Finition longue… [plan] [Pause]
SÉRIE
2 / 4
Dribble combo [médias]
Temps de série : 00:45 [Démarrer]
Passage 1 / 10 SÉQUENCE
[1] [2] [3] [4]
ÉTAPE 1 / 4
Dribble main droite
Objectif : 10 s
00:10
[Démarrer]
[Passer létape] [...]
[Terminer la série]
[Passer la série]
```
État sans temps de série mais avec première étape chrono :
```text
SÉRIE
2 / 4
Dribble combo [médias] [Démarrer]
```
État sans séquence :
```text
SÉRIE
2 / 4
Dribble combo [médias]
Temps de série : 00:45 [Démarrer]
Répétitions
[-] 10 [+]
Score (paniers)
[ ]
[Terminer la série]
[Passer la série]
```
## Priorités dimplémentation
1. Ne pas régresser le sans-scroll : tester avec écran mobile contraint, exercice long, programme long, étape longue, score manuel actif.
2. Supprimer `Passages réalisés` de `_StepSetResultSummary` et retirer le bloc si aucun score manuel de série nest à saisir.
3. Refaire `_ExecutionContextHeader` : série + exercice seulement ; programme déplacé dans lAppBar en texte secondaire tronqué.
4. Remplacer les labels de démarrage visibles par `Démarrer`, garder les tooltips/messages explicites.
5. Corriger les formats de temps bruts en milliers.
6. Corriger le style initial de `_TimedStepBody` pour que timer et bouton soient lisibles avant démarrage.
## Étape suivante
- **Architect** si le bug dunité temps vient dune ambiguïté de source (`seconds` vs `milliseconds`) dans les données de référence/performance.
- Sinon, **DevFrontend directement** : les décisions UX sont suffisamment cadrées pour modifier `workout_execution_screen.dart`.
# Architect — Diagnostic unité temps Point 5
## Verdict
Le problème est **(a) un bug daffichage local à `lib/presentation/workout_execution_screen.dart`**. Je nai pas identifié de rupture de contrat domain/application/infra.
Contrat confirmé côté #81 :
- `lib/application/ports.dart:713-735` et `lib/application/ports.dart:739-763` exposent explicitement `actualTimeMs` et `actualScoreTimeMs` en millisecondes pour `WorkoutHistorySetPerformance` et `WorkoutHistoryMetricPerformance`.
- `lib/infrastructure/local/drift_repositories.dart:1646-1657`, `1702-1712` lisent `actual_time_ms` / `actual_score_time_ms` depuis lhistorique.
- `lib/infrastructure/local/drift_repositories.dart:3511-3525` mappe ces colonnes vers les DTO sans conversion, ce qui est correct.
- Les objectifs de temps dexercice `targetTimeSeconds` et détape `defaultTargetValue` restent des secondes côté snapshot/exécution ; `lib/application/use_cases.dart:2873-2878` convertit `defaultTargetValue * 1000` uniquement pour les calculs chrono.
Donc DevFrontend peut corriger sans changement de DTO, port, repository ou migration.
## Localisations précises à corriger
### 1. Formatter des références de performance
Fichier : `lib/presentation/workout_execution_screen.dart`.
- `4083-4107` : `_formatSetPerformance(...)` appelle `_formatReferenceTime(performance.actualTimeMs!, stopwatch: false)`.
- `4110-4139` : `_formatMetricPerformance(...)` appelle `_formatReferenceTime(...)` pour `PerformanceMetric.time` et score chrono.
- `4156-4169` : `_formatReferenceScore(...)` appelle `_formatReferenceTime(actualScoreTimeMs, stopwatch: true)`.
- `4183-4199` : `_formatReferenceTime(int milliseconds, {required bool stopwatch})` est le point central à remplacer/renforcer.
Correction attendue : ne plus utiliser un seul formatter ambigu. Séparer explicitement temps de mesure et score chrono :
```dart
String _formatReferenceMeasureTime(int milliseconds) {
final totalSeconds = (milliseconds / 1000).round().clamp(0, 999999).toInt();
if (totalSeconds < 60) return '$totalSeconds s';
final minutes = totalSeconds ~/ 60;
final seconds = (totalSeconds % 60).toString().padLeft(2, '0');
return '$minutes:$seconds';
}
String _formatReferenceStopwatchScore(int milliseconds) {
return _formatScoreStopwatch(Duration(milliseconds: milliseconds));
}
```
Puis :
- `actualTimeMs` / `PerformanceMetric.time` -> `_formatReferenceMeasureTime(...)`.
- `actualScoreTimeMs` / score chrono -> `_formatReferenceStopwatchScore(...)`.
Raison : les deux valeurs sont bien en millisecondes, mais elles nont pas la même convention daffichage. Le score chrono doit afficher les dixièmes (`00:42.8`) ; le temps de mesure doit rester court (`45 s`, `1:12`).
### 2. Objectif de temps dans `SetMeasureInput`
Fichier : `lib/presentation/workout_execution_screen.dart:1749-1761`.
Aujourdhui :
```dart
exercise.targetTimeSeconds == null
? 'Chronométrer'
: '${exercise.targetTimeSeconds} s'
```
Ce nest pas une rupture de contrat : `targetTimeSeconds` est bien en secondes. Mais ce rendu contourne les formatters et devient mauvais dès que la cible dépasse une minute (`300 s` au lieu de `05:00`, visible avec les seeds à 300 secondes). Correction : passer par `_formatDuration(Duration(seconds: exercise.targetTimeSeconds!))` ou un formatter court secondes dédié.
### 3. Objectif de létape chrono
Fichier : `lib/presentation/workout_execution_screen.dart:2321-2325`.
Aujourdhui :
```dart
Text('Objectif : ${step.defaultTargetValue} s')
```
Contrat confirmé : `defaultTargetValue` dune étape `ExerciseStepType.time` est en secondes. Ce nest pas la source des millisecondes si les données sont correctes. Correction tout de même recommandée pour cohérence UX : `Objectif : ${_formatShortSeconds(step.defaultTargetValue)}` avec `10 s`, `1:30`, etc. Ne pas convertir comme si la source était déjà en millisecondes.
## Tests recommandés
Ajouter des tests widget ciblés dans `test/presentation/workout_execution_screen_test.dart` si possible via un fake `ExercisePerformanceReferenceUseCase` ou en rendant les helpers testables indirectement :
- référence `actualTimeMs = 45000` -> affiche `45 s`, jamais `45000`.
- référence `actualTimeMs = 72000` -> affiche `1:12`, jamais `72000` ni `72 s` si le format court minutes est retenu.
- score chrono `actualScoreTimeMs = 42800` -> affiche `00:42.8`.
- objectif exercice `targetTimeSeconds = 300` -> affiche `05:00` ou le format UX choisi, pas `300 s` si ce bloc reste visible après refonte.
- objectif étape temps `defaultTargetValue = 10` -> affiche `Objectif : 10 s`; avec `90` -> `Objectif : 1:30`.
Pas de test backend nécessaire.
## Décision pour la suite
DevFrontend corrige directement dans `workout_execution_screen.dart`. Ne pas modifier les contrats #81 : les unités y sont cohérentes et déjà nommées en millisecondes/secondes.
# DevFrontend — Implémentation UI séance
## Réalisé
- Point 1 : ajout d'un test widget de non-régression sans scroll avec écran contraint, exercice long, programme long, étape longue et score manuel actif.
- Point 2 : suppression de `Passages réalisés` dans `_StepSetResultSummary`; le bloc n'est plus affiché quand aucun score manuel de série n'est actif.
- Point 3 : simplification de `_ExecutionContextHeader` en `SÉRIE` + compteur compact + exercice uniquement ; le nom du programme courant est déplacé dans l'AppBar sous le nom de séance, tronqué sur une ligne.
- Point 4 : boutons visibles de démarrage raccourcis à `Démarrer`; le tooltip/accessibilité conserve `Démarrer lexercice` et les confirmations gardent le message explicite `Tu nas pas démarré le chrono de cette série.`
- Point 5 : séparation des formatters de référence : temps de mesure (`45 s`, `1:12`) et score chrono (`00:42.8`) ; objectifs `targetTimeSeconds` et `defaultTargetValue` passent désormais par des formatters.
- Point 6 : `_TimedStepBody` ne passe plus tout son contenu dans un `FittedBox`; timer en Anton/or lisible, bouton `Démarrer` en hauteur minimale 48 px, libellé `Chrono prêt`.
## Fichiers touchés
- `lib/presentation/workout_execution_screen.dart`
- `test/presentation/workout_execution_screen_test.dart`
- `.ideai/tickets/93/carnet.md`
## Vérifications DevFrontend
- `HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/workout_execution_screen.dart test/presentation/workout_execution_screen_test.dart` : exit code 0 ; seules 6 infos existantes dans `workout_execution_screen.dart` restent signalées.
- `HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze` : exit code 0 ; 24 infos existantes hors blocage.
- `HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/workout_execution_screen_test.dart` : non exécutable dans ce sandbox, le wrapper Flutter tente d'écrire dans `/opt/flutter/bin/cache` en lecture seule.
- `HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart test test/presentation/workout_execution_screen_test.dart` : non exécutable dans ce sandbox, les build hooks tentent un accès réseau à `pub.dev` (`flutter_launcher_icons`).
## Écarts / limites
- Commit non réalisé dans ce sandbox : `.git/index.lock` ne peut pas être créé car `.git` est en lecture seule.
- L'outil `idea_ticket_update_carnet` n'était pas exposé dans cette session ; résumé ajouté directement au fichier carnet du ticket.
## Correctif ambiguïté `Démarrer`
Suite au retour Main sur `le chrono score démarre et s'arrête avec la bonne durée` :
- le bouton principal de série/exercice garde le libellé `Démarrer`.
- les contrôles chrono inline passent à `Lancer` : chrono score, chrono d'étape, chrono score d'étape.
- les tests ciblent maintenant `Lancer` pour le chrono score et les étapes chronométrées, ce qui évite l'ambiguïté avec l'action principale visible simultanément.
Vérifications locales sandbox :
- `HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/workout_execution_screen.dart test/presentation/workout_execution_screen_test.dart` : exit code 0 ; mêmes 6 infos existantes dans `workout_execution_screen.dart`.
- `HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/workout_execution_screen_test.dart` : toujours non exécutable dans ce sandbox, le wrapper Flutter tente d'écrire dans `/opt/flutter/bin/cache` en lecture seule.
# UX — Décision finale libellés de démarrage
GO sur `Lancer` pour les chronos inline.
Décision finale :
- bouton principal du bloc série/exercice : `Démarrer` ;
- tooltip/accessibilité du bouton principal : `Démarrer lexercice` ;
- boutons inline de chronos détape et de score chrono : `Lancer` ;
- bouton darrêt des chronos inline : `Arrêter` ;
- état prêt dun chrono détape : `Chrono prêt` puis bouton `Lancer`.
Raison UX : `Démarrer` porte laction globale qui lance lexercice/la série. `Lancer` est suffisamment distinct, court, compatible avec le ton direct de GameTime, et convient bien à une action locale de chrono. Il évite deux boutons visibles avec le même libellé sans introduire un mot trop technique comme `Activer` ou trop long comme `Démarrer le chrono`.
À ne pas utiliser ici :
- `Start`, `Go`, `Play` : pas cohérents avec les libellés français de lapp ;
- `Activer` : trop système ;
- `Démarrer le chrono` : trop long pour lécran sans scroll.
# QA — Validation #93
## Périmètre relu
- Cadrage complet UX / Architect / DevFrontend lu via `idea_ticket_read("#93", includeCarnet=true)`.
- Fichiers relus : `lib/presentation/workout_execution_screen.dart`, `test/presentation/workout_execution_screen_test.dart`.
## Vérification des 6 points
1. Sans-scroll non régressé : test dédié présent `lécran actif avec séquence longue reste sans scroll`, avec surface contrainte, noms longs, séquence longue, score manuel actif ; il vérifie `find.byType(ListView), findsNothing`.
2. `Passages réalisés` retiré : plus de libellé dans le code de production relu ; tests `find.textContaining('Passages réalisés'), findsNothing`.
3. Header simplifié : `_ExecutionContextHeader` affiche `SÉRIE`, compteur `x / y`, nom dexercice, temps de série et actions ; `Programme X/Y · Exercice X/Y` nest plus dans le header. Le programme courant est affiché dans `_ExecutionAppBarTitle`, sous le nom de séance, `maxLines: 1` + `TextOverflow.ellipsis`.
4. Libellés : bouton principal visible `Démarrer` avec tooltip `Démarrer lexercice`; chronos inline `Lancer`, arrêt `Arrêter`, état prêt `Chrono prêt`.
5. Format temps : références temps passent par `_formatReferenceMeasureTime` (`45 s`, `1:12`) et scores chrono par `_formatReferenceStopwatchScore` (`00:42.8`). Objectifs exercice/étape passent par `_formatDuration` / `_formatShortSeconds`. Tests présents contre `72000` brut et pour `1:12`, `00:42.8`, `Objectif : 1:30`.
6. Étape chrono initiale lisible : `_TimedStepBody` nutilise pas de `FittedBox`, affiche le timer en `displayMedium` Anton/or, bouton `Lancer` avec `minimumSize: Size.fromHeight(48)`. Le `FittedBox` restant concerne uniquement `_RepsStepBody`, pas létape chronométrée.
Aucun oubli UX bloquant trouvé à la lecture.
## Commandes exécutées par QA
Commande :
```sh
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/workout_execution_screen.dart test/presentation/workout_execution_screen_test.dart
```
Sortie réelle pertinente :
```text
Analyzing workout_execution_screen.dart, workout_execution_screen_test.dart...
6 issues found.
```
Verdict commande : exit code 0 ; les 6 issues sont des infos déjà documentées dans `workout_execution_screen.dart`.
Commande :
```sh
HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/workout_execution_screen_test.dart
```
Sortie réelle :
```text
/opt/flutter/bin/internal/update_engine_version.sh: line 71: /opt/flutter/bin/cache/engine.stamp.tmp.24: Read-only file system
/opt/flutter/bin/internal/update_engine_version.sh: line 78: /opt/flutter/bin/cache/engine.realm: Read-only file system
```
Verdict commande : non exécutée, blocage environnement Flutter cache read-only avant lancement des tests.
Commande :
```sh
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart test test/presentation/workout_execution_screen_test.dart
```
Sortie réelle :
```text
Running build hooks...Running build hooks...Got socket error trying to find package flutter_launcher_icons at https://pub.dev.
```
Verdict commande : non exécutée jusqu'au vert, blocage réseau/build hooks.
## Verdict QA
**GO QA pour #93**, sous réserve explicite que cette session n'a pas pu relancer `flutter test` à cause du sandbox. La validation automatisée verte à retenir reste celle de Main : `flutter test --no-pub test/presentation/workout_execution_screen_test.dart` -> 31/31 verts. La lecture QA confirme que les 6 points utilisateur et la décision UX finale `Démarrer` / `Lancer` sont couverts.