---
issueRef: "#93"
version: 6
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
updatedAt: 1784705595811
---
# 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’exercice` 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à l’information de progression de séquence.

Code observé dans `lib/presentation/workout_execution_screen.dart` :

- `_ExecutionContextHeader` affiche aujourd’hui `Série X/Y`, `Programme X/Y · Exercice X/Y`, temps écoulé, nom d’exercice, temps de série et bouton `Démarrer l’exercice` 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 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 :

```text
AppBar compacte
Bloc série + exercice actif
Séquence / mesures actives
Actions de série en bas
```

L’utilisateur 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 l’AppBar 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 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é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 l’AppBar 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. 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 :

```text
Passages réalisés : 0 / 10
```

La source visible devient uniquement l’en-tête de séquence :

```text
Passage 1 / 10
```

ou :

```text
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 :

```text
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 :

```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 l’endroit 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 l’exercice`

Libellé cible dans le bouton principal :

```text
Démarrer
```

Tooltip / accessibilité :

```text
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 :

```text
Démarrer
```

avec message :

```text
Tu n’as 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 à 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: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 s’appelle `actualTimeMs`, `actualScoreTimeMs` ou `targetScoreTimeMs`, elle doit passer par un formatter en millisecondes ;
- si une valeur issue d’un 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 d’une é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 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`, pas `Démarrer la séquence`, pour rester court. Le contexte `Passage 1/10` suffit.
- Si c’est 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 d’implé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 n’est à saisir.
3. Refaire `_ExecutionContextHeader` : série + exercice seulement ; programme déplacé dans l’AppBar 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 d’unité temps vient d’une 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 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-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 l’historique.
- `lib/infrastructure/local/drift_repositories.dart:3511-3525` mappe ces colonnes vers les DTO sans conversion, ce qui est correct.
- Les objectifs de temps d’exercice `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 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 :

```dart
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 :

```dart
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` -> 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 l’exercice` et les confirmations gardent le message explicite `Tu 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`) ; 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 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êt` puis bouton `Lancer`.

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

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 d’exercice, temps de série et actions ; `Programme X/Y · Exercice X/Y` n’est 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 l’exercice`; 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` n’utilise 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.
