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

23 KiB
Raw Blame History

issueRef, version, updatedBy, updatedAt
issueRef version updatedBy updatedAt
#93 5
kind agent_id
agent f3408f5d-469c-4f64-9485-d8b218f3ff26
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 :

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.

Séance tirs du soir
Programme finition longue…

ou, si lAppBar 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 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 :

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 :

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 :

Passages réalisés : 0 / 10

La source visible devient uniquement len-tête de séquence :

Passage 1 / 10

ou :

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 :

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 :

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 :

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 lexercice

Libellé cible dans le bouton principal :

Démarrer

Tooltip / accessibilité :

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 :

Démarrer

avec message :

Tu nas 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 à 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 :

É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 :
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 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 :

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 :

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 :

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 :

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.