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>
23 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #93 | 5 |
|
1784705305380 |
UX — Revue écran d’exécution séance
Contexte lu
Ticket utilisateur #93 : retour direct sur workout_execution_screen.dart après l’amélioration récente du sans-scroll (#90). Le principe tout sans scroll est validé par l’utilisateur et doit être conservé.
Mémos pris en compte :
gametime-session-execution-timer-refactor: l’écran s’organise autour de l’exercice actif ;Démarrer l’exercicelance les chronos logiques de début de série.gametime-ux-execution-nav-and-program-simplification: exécution optimisée effort, navigation viaVoir 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/10ouPassage en coursest déjà l’information de progression de séquence.
Code observé dans lib/presentation/workout_execution_screen.dart :
_ExecutionContextHeaderaffiche aujourd’huiSérie X/Y,Programme X/Y · Exercice X/Y, temps écoulé, nom d’exercice, temps de série et boutonDémarrer l’exercicedans un même panneau._StepSequencePanelaffiche déjàPassage X / YouPassage en cours._StepSetResultSummaryréaffiche ensuitePassages réalisés : X / Y, redondant._TimedStepBodycontient le compteur d’étape et le bouton de démarrage, dans unFittedBox, ce qui peut expliquer le rendu minuscule tant que l’état initial n’a pas démarré.- Les références de performance passent par
_formatReferenceTime(...); les valeurs affichées en milliers doivent être traitées comme bug d’unité/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 :
AppBar compacte
Bloc série + exercice actif
Séquence / mesures actives
Actions de série en bas
L’utilisateur doit lire immédiatement :
- quelle séance/programme est en cours ;
- quelle série il fait ;
- quel exercice il fait ;
- 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.
Séance tirs du soir
Programme finition longue…
ou, si l’AppBar ne permet pas de sous-titre proprement :
Séance tirs du soir · Finition longue…
Règles :
- le nom de séance reste prioritaire ;
- le programme est secondaire, style
labelSmalloubodySmall, couleur texte secondaire ; - max 1 ligne,
TextOverflow.ellipsis; - ne pas afficher
Programme 1/2dans le header principal ; - conserver les actions
Voir le planetPause.
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 :
SÉRIE
2 / 4
Dribble combo [médias]
Temps de série : 00:45 [Démarrer]
Règles de hiérarchie :
SÉRIEen label uppercase Archivo, texte secondaire.2 / 4en Anton, or, visible mais compact : environ 40-46 px, pas plus si l’écran est contraint.- Nom d’exercice 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 l’exercice. Temps de sériereste dans le bloc exercice actif, conformément au mémo timer, mais compact.- Le temps écoulé global de séance peut rester discret dans l’AppBar ou dans une petite pastille secondaire, mais ne doit plus concurrencer série/exercice.
Supprimer du bloc haut :
Programme 1/2 · Exercice 3/8
Justification : ce niveau de position est utile au plan de séance, pas au geste immédiat. L’utilisateur 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 :
Passages réalisés : 0 / 10
La source visible devient uniquement l’en-tête de séquence :
Passage 1 / 10
ou :
Passage en cours
Implication UI : _StepSetResultSummary ne doit plus afficher les passages. Si l’exercice a un score manuel de série, conserver uniquement le champ score, en version compacte :
Score (paniers)
[ ]
Si aucun score manuel de série n’est actif, supprimer complètement cette zone pour gagner la hauteur.
Exception : quand la séquence est terminée, le panneau peut afficher :
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/Ydisparaît du bloc principal. - Le plan de séance reste l’endroit pour voir la position complète.
Libellés conservés :
Voir le plan
Pause
SÉRIE
Temps de série
Libellés retirés du header actif :
Programme 1/2 · Exercice 3/8
Point 4 — Raccourcir le bouton Démarrer l’exercice
Libellé cible dans le bouton principal :
Démarrer
Tooltip / accessibilité :
Démarrer l’exercice
Raison : l’action est déjà située dans le bloc de l’exercice actif ; le mot exercice est redondant et consomme trop de largeur.
Dans les confirmations existantes où l’utilisateur n’a pas démarré le chrono, garder un libellé explicite :
Démarrer
avec message :
Tu n’as pas démarré le chrono de cette série.
Action alternative inchangée :
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 à l’utilisateur 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:10si heure nécessaire. - Référence de temps courte hors chrono score :
45 s,1:12. - Score chrono :
00:42.8ou1:04.2. - Objectif d’étape :
Objectif : 10 s, pas10000.
À vérifier côté implémentation :
- si une valeur s’appelle
actualTimeMs,actualScoreTimeMsoutargetScoreTimeMs, elle doit passer par un formatter en millisecondes ; - si une valeur issue d’un champ en secondes (
targetTimeSeconds,defaultTargetValued’é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 d’une étape temps doivent avoir le même statut visuel avant et après démarrage.
État initial attendu :
ÉTAPE 1 / 4
Dribble main droite
Objectif : 10 s
00:10
[Démarrer]
[Passer l’étape] [...]
Règles visuelles :
00:10en Anton / style timer, or, taille lisible équivalente à l’état running.- Bouton
Démarrerhauteur minimale 44-48 px, texte normal, jamais réduit à une taille miniature. - Éviter que le
FittedBoxréduise tout le body à cause d’une 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, pasDémarrer la séquence, pour rester court. Le contextePassage 1/10suffit. - Si c’est un chrono suivant prêt, afficher :
Chrono prêt
[Démarrer]
au lieu de :
Chrono suivant prêt
[Démarrer le chrono]
Layout cible — avec séquence
État exercice avec étapes, avant démarrage :
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 :
SÉRIE
2 / 4
Dribble combo [médias] [Démarrer]
État sans séquence :
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 d’implémentation
- Ne pas régresser le sans-scroll : tester avec écran mobile contraint, exercice long, programme long, étape longue, score manuel actif.
- Supprimer
Passages réalisésde_StepSetResultSummaryet retirer le bloc si aucun score manuel de série n’est à saisir. - Refaire
_ExecutionContextHeader: série + exercice seulement ; programme déplacé dans l’AppBar en texte secondaire tronqué. - Remplacer les labels de démarrage visibles par
Démarrer, garder les tooltips/messages explicites. - Corriger les formats de temps bruts en milliers.
- Corriger le style initial de
_TimedStepBodypour que timer et bouton soient lisibles avant démarrage.
Étape suivante
- Architect si le bug d’unité temps vient d’une ambiguïté de source (
secondsvsmilliseconds) 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 d’affichage local à lib/presentation/workout_execution_screen.dart. Je n’ai pas identifié de rupture de contrat domain/application/infra.
Contrat confirmé côté #81 :
lib/application/ports.dart:713-735etlib/application/ports.dart:739-763exposent explicitementactualTimeMsetactualScoreTimeMsen millisecondes pourWorkoutHistorySetPerformanceetWorkoutHistoryMetricPerformance.lib/infrastructure/local/drift_repositories.dart:1646-1657,1702-1712lisentactual_time_ms/actual_score_time_msdepuis l’historique.lib/infrastructure/local/drift_repositories.dart:3511-3525mappe ces colonnes vers les DTO sans conversion, ce qui est correct.- Les objectifs de temps d’exercice
targetTimeSecondset d’étapedefaultTargetValuerestent des secondes côté snapshot/exécution ;lib/application/use_cases.dart:2873-2878convertitdefaultTargetValue * 1000uniquement 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(...)pourPerformanceMetric.timeet 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 :
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 n’ont pas la même convention d’affichage. 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.
Aujourd’hui :
exercise.targetTimeSeconds == null
? 'Chronométrer'
: '${exercise.targetTimeSeconds} s'
Ce n’est 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.
Aujourd’hui :
Text('Objectif : ${step.defaultTargetValue} s')
Contrat confirmé : defaultTargetValue d’une étape ExerciseStepType.time est en secondes. Ce n’est 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-> affiche45 s, jamais45000. - référence
actualTimeMs = 72000-> affiche1:12, jamais72000ni72 ssi le format court minutes est retenu. - score chrono
actualScoreTimeMs = 42800-> affiche00:42.8. - objectif exercice
targetTimeSeconds = 300-> affiche05:00ou le format UX choisi, pas300 ssi ce bloc reste visible après refonte. - objectif étape temps
defaultTargetValue = 10-> afficheObjectif : 10 s; avec90->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ésdans_StepSetResultSummary; le bloc n'est plus affiché quand aucun score manuel de série n'est actif. - Point 3 : simplification de
_ExecutionContextHeaderenSÉ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é conserveDémarrer l’exerciceet les confirmations gardent le message expliciteTu n’as 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) ; objectifstargetTimeSecondsetdefaultTargetValuepassent désormais par des formatters. - Point 6 :
_TimedStepBodyne passe plus tout son contenu dans unFittedBox; timer en Anton/or lisible, boutonDémarreren hauteur minimale 48 px, libelléChrono prêt.
Fichiers touchés
lib/presentation/workout_execution_screen.darttest/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 dansworkout_execution_screen.dartrestent 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/cacheen 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.lockne peut pas être créé car.gitest en lecture seule. - L'outil
idea_ticket_update_carnetn'é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
Lancerpour 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 dansworkout_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/cacheen 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 l’exercice; - boutons inline de chronos d’étape et de score chrono :
Lancer; - bouton d’arrêt des chronos inline :
Arrêter; - état prêt d’un chrono d’étape :
Chrono prêtpuis boutonLancer.
Raison UX : Démarrer porte l’action globale qui lance l’exercice/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 l’app ;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
- 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érifiefind.byType(ListView), findsNothing. Passages réalisésretiré : plus de libellé dans le code de production relu ; testsfind.textContaining('Passages réalisés'), findsNothing.- Header simplifié :
_ExecutionContextHeaderafficheSÉRIE, compteurx / y, nom d’exercice, temps de série et actions ;Programme X/Y · Exercice X/Yn’est plus dans le header. Le programme courant est affiché dans_ExecutionAppBarTitle, sous le nom de séance,maxLines: 1+TextOverflow.ellipsis. - Libellés : bouton principal visible
Démarreravec tooltipDémarrer l’exercice; chronos inlineLancer, arrêtArrêter, état prêtChrono prêt. - 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 contre72000brut et pour1:12,00:42.8,Objectif : 1:30. - Étape chrono initiale lisible :
_TimedStepBodyn’utilise pas deFittedBox, affiche le timer endisplayMediumAnton/or, boutonLanceravecminimumSize: Size.fromHeight(48). LeFittedBoxrestant concerne uniquement_RepsStepBody, pas l’étape chronométrée.
Aucun oubli UX bloquant trouvé à la lecture.
Commandes exécutées par QA
Commande :
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 :
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 :
HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/workout_execution_screen_test.dart
Sortie réelle :
/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 :
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart test test/presentation/workout_execution_screen_test.dart
Sortie réelle :
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.