chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)

Regroupe l'état de travail en cours réalisé dans un même worktree sur
plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164),
mélangeant des tickets QA et inProgress. Ne constitue pas une feature
terminée : commit de sauvegarde avant triage/split par ticket en
branches feature/* dédiées. Exclut les dossiers d'environnement de
build locaux et le heap dump parasite (.gitignore mis à jour).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 16:48:54 +02:00
parent 58272e354a
commit 917777e18b
279 changed files with 13546 additions and 674 deletions

View File

@ -0,0 +1,530 @@
---
name: gametime-ux-watch-companion
description: memory note gametime-ux-watch-companion
metadata:
type: project
---
# GameTime — UX interface montre synchronisée (ticket #91, UX 2026-07-25)
Conception UX d'une app compagnon Wear OS synchronisée avec l'app téléphone pour piloter une séance en cours depuis le poignet.
Références à respecter :
- `gametime-session-execution-timer-refactor`
- `gametime-ux-step-chaining-override`
- `gametime-architecture-step-chaining-override`
- `gametime-ux-score-chrono`
- `gametime-online-layer-philosophy`
## Décision produit structurante
La montre est une **surface compagnon de pilotage**, pas une deuxième app autonome de séance.
- **Téléphone = source de vérité de l'exécution**.
- **Montre = télécommande + miroir d'état**.
- Toute action lancée sur la montre est une **intention utilisateur** envoyée au téléphone, puis confirmée par le retour d'état.
- La montre ne doit jamais exposer un modèle d'exécution différent de celui du téléphone.
Justification :
- la logique de séance, des chronos, des overrides d'étapes et du repos existe déjà côté téléphone ;
- cela évite les divergences de calcul entre deux appareils ;
- cela rend la cohérence UX compréhensible : un seul état réel, visible sur deux surfaces.
## Principe de surface
Sur montre, la hiérarchie doit être radicale :
1. **où j'en suis** : série, exercice, étape/passage si nécessaire ;
2. **ce qui se passe maintenant** : chrono dominant ou état dominant ;
3. **l'action unique la plus probable** ;
4. **les actions de contournement** dans une surface secondaire.
La montre ne doit pas tenter de reproduire toute l'interface téléphone. Pas de médias, pas d'édition, pas de paramètres, pas de détails historiques.
## Surfaces montre
## 1. État sans séance active
Écran affiché si aucune séance n'est en cours côté téléphone.
Contenu :
```text
Aucune séance en cours
Lance une séance sur le téléphone.
```
CTA optionnel si le système le permet :
```text
[Actualiser]
```
Règles :
- aucun contrôle d'exécution affiché ;
- pas de faux bouton `Démarrer` ;
- si la montre n'est pas connectée au téléphone, le message devient :
```text
Téléphone indisponible
Rouvre GameTime sur le téléphone.
```
## 2. Écran principal `Séance active`
Écran par défaut dès qu'une séance en cours existe. C'est la surface centrale de la feature.
Structure recommandée sur écran rond :
```text
SÉRIE 2 / 5
Pompes tempo
Passage 1 / 3 · Étape 2 / 4
00:18
Chrono étape
Série 01:42 · Score 00:51
[Pause]
```
### Hiérarchie d'information
L'ordre visuel doit être fixe :
1. `SÉRIE X / Y` toujours en haut.
2. Nom de l'exercice sur 1 à 2 lignes max.
3. Ligne contextuelle compacte :
- `Passage A / B` seulement si l'exercice en a ;
- `Étape C / D` seulement si l'exercice en a ;
- si les deux existent, les concaténer sur une seule ligne.
4. **Chrono dominant** en très grand.
5. Libellé du chrono dominant ou de l'état.
6. Ligne compacte des autres chronos simultanés, si utile.
7. Bouton principal pleine largeur.
### Règle du chrono dominant
La montre ne doit afficher qu'un seul chrono en grand. Ordre de priorité UX :
1. `Repos` si un repos est en cours.
2. `Étape` si une étape temps est active ou en état `Chrono suivant prêt`.
3. `Score chrono` s'il est actif et qu'aucune étape temps n'est prioritaire.
4. `Temps de série` si actif.
5. Sinon, l'état dominant remplace le chrono.
Les autres chronos actifs restent en secondaire sur une seule ligne compacte, par exemple :
```text
Série 03:12 · Score 01:08
```
But : rester lisible pendant l'effort tout en respectant le modèle multi-chronos existant.
### Bouton principal contextuel
Le bas de l'écran porte **une seule action primaire** dont le libellé dépend de l'état :
- `Démarrer l'exercice`
- `Pause`
- `Reprendre`
- `Démarrer le chrono`
- `Passer le repos`
Cette action unique est le raccourci du cas d'usage le plus probable à cet instant.
## 3. Écran `Actions`
Surface secondaire accessible depuis `Séance active` par swipe horizontal ou tap sur un affordance discret `Actions`.
Cette surface contient les actions moins fréquentes, sous forme de gros boutons verticaux scrollables.
Ordre des actions :
```text
[Passer l'étape] // seulement si applicable
[Passer le passage] // seulement si applicable
[Terminer la série]
[Passer la série]
[Passer le repos] // seulement pendant le repos
```
Règles :
- n'afficher que les actions réellement applicables à l'état courant ;
- ne pas afficher d'action impossible ou déjà satisfaite ;
- pendant un chrono en cours, `Passer la série` doit demander une confirmation ;
- pendant un repos, `Terminer la série` disparaît car la série est déjà terminée.
### Confirmations montre
Deux confirmations seulement, pour éviter les erreurs grossières pendant l'effort :
1. `Passer la série ?`
`Le chrono en cours sera ignoré.`
`[Annuler] [Passer]`
2. `Passer le passage ?`
`L'étape en cours sera ignorée.`
`[Annuler] [Passer]`
`Passer l'étape` peut être immédiat : coût faible, fréquence plus élevée.
## 4. Écran `Repos`
Quand le repos est en cours, l'écran principal change de nature et devient un écran repos, pas un simple bandeau.
Structure :
```text
REPOS
Après série 2 / 5
00:37
Repos en cours
Exercice suivant
Fentes sautées
[Pause]
```
Actions secondaires sur l'écran `Actions` :
```text
[Passer le repos]
```
Si le repos est en pause :
```text
REPOS
00:37
Repos en pause
[Reprendre]
```
## États à couvrir
## 1. Séance pas encore lancée / début de série
Cas : la série existe, mais aucun chrono qui démarre au début de série n'a encore été lancé.
Affichage :
```text
SÉRIE 1 / 4
Burpees
Étape 1 / 3
00:20
Prêt à démarrer
[Démarrer l'exercice]
```
Règle obligatoire :
- si l'exercice est sous chrono **et** que la première étape est une étape `Temps`, le bouton reste **unique** :
```text
[Démarrer l'exercice]
```
Cette action démarre à la fois :
- le timer de série si `timeEnabled` ;
- le chrono de la première étape ;
- le score chrono si `scoreInputMode == stopwatch`.
La montre ne doit jamais afficher deux boutons concurrents du type `Démarrer l'exercice` et `Démarrer l'étape`.
## 2. Chrono running
Cas nominal pendant l'effort.
Affichage :
- chrono dominant animé visuellement ;
- libellé `En cours` ou libellé du chrono (`Chrono étape`, `Score chrono`, `Temps de série`) ;
- bouton principal `Pause`.
Effet du bouton :
- met la séance en pause ;
- la pause suspend tous les chronos running, y compris le repos.
## 3. Séance en pause
Affichage :
```text
SÉRIE 2 / 5
Pompes tempo
00:18
Séance en pause
[Reprendre]
```
Règles :
- l'état doit être explicite ;
- aucun chrono ne doit sembler continuer ;
- si l'utilisateur reprend depuis le téléphone, la montre revient automatiquement à l'état running.
## 4. `Chrono suivant prêt`
Cas imposé par `autoStartNextTimedStep = false` après une étape temps suivie d'une autre étape temps.
Affichage :
```text
SÉRIE 2 / 5
Pompes tempo
Passage 1 / 3 · Étape 3 / 4
00:15
Chrono suivant prêt
[Démarrer le chrono]
```
Règles :
- ne pas réutiliser `Démarrer l'exercice` ;
- ne pas auto-démarrer ;
- conserver l'état après pause/reprise ;
- l'écran doit rendre évident qu'on est déjà avancé dans la séquence, mais en attente d'un lancement manuel.
## 5. Entre les séries
Deux cas distincts :
1. repos configuré et lancé ;
2. pas de repos en cours, prochaine série prête.
Si pas de repos :
```text
SÉRIE 3 / 5
Pompes tempo
Prêt pour la série suivante
[Démarrer l'exercice]
```
L'utilisateur comprend qu'il redémarre une nouvelle série, pas la séance depuis zéro.
## 6. Repos running / repos en pause
Voir surface `Repos`.
Le repos doit être traité comme un état de premier rang, car c'est souvent le seul moment où l'utilisateur regarde la montre entre deux séries.
## 7. Pas de séance active
Voir surface `État sans séance active`.
## 8. Téléphone temporairement indisponible
Cas spécifique à la cohabitation téléphone/montre.
La montre peut afficher le dernier état connu, mais il doit être clairement marqué comme potentiellement obsolète :
```text
Connexion perdue
Dernier état reçu il y a quelques secondes
[Réessayer]
```
Règles :
- désactiver les actions de pilotage tant que la connexion n'est pas rétablie ;
- ne pas laisser croire qu'un tap local a réellement modifié la séance sans confirmation ;
- au retour de connexion, la montre remplace entièrement son affichage par l'état confirmé du téléphone.
## Mapping des contrôles
## Gestes retenus
- **Tap** sur le bouton principal : action primaire contextuelle.
- **Swipe horizontal** : bascule entre `Séance active` et `Actions`.
- **Scroll vertical / couronne** : parcours des actions si la liste dépasse.
- **Bouton système / retour système** : navigation système uniquement.
Décision UX : ne pas attribuer de commande métier obligatoire à un bouton physique matériel. Les montres Wear OS n'offrent pas toutes la même ergonomie matérielle.
## Mapping détaillé
### Sur `Séance active`
- `Démarrer l'exercice`
- déclenche tous les chronos de début de série applicables ;
- couvre explicitement le cas exercice chrono + première étape chrono.
- `Pause`
- met la séance en pause ;
- suspend série, étape, score chrono, repos.
- `Reprendre`
- reprend exactement l'état suspendu.
- `Démarrer le chrono`
- démarre l'étape temps prête après un enchaînement manuel.
- `Passer le repos`
- raccourci contextuel possible si UX test montre que c'est plus utile que `Pause` pendant le repos ;
- sinon garder `Pause` en primaire et déplacer `Passer le repos` dans `Actions`.
Décision recommandée pour v1 :
- pendant le repos, **bouton principal = `Pause`**, pour garder la cohérence avec le reste de la séance ;
- `Passer le repos` reste en action secondaire.
### Sur `Actions`
- `Passer l'étape`
- disponible seulement si une étape courante existe.
- `Passer le passage`
- disponible seulement si l'exercice a plusieurs passages/séquences et si le passage courant n'est pas déjà terminé.
- `Terminer la série`
- finalise la série et arrête/enregistre les chronos actifs selon les règles existantes.
- `Passer la série`
- ignore la série courante avec confirmation si un chrono ou une séquence est en cours.
- `Passer le repos`
- disponible seulement si repos en cours.
## Cohérence téléphone ↔ montre
## Source de vérité
Décision UX ferme :
- l'état autoritaire de séance vit sur le téléphone ;
- la montre affiche une **projection compacte** de cet état ;
- la montre n'invente jamais un état final seule.
## Modèle d'interaction
Cycle UX attendu pour une action montre :
1. l'utilisateur tape sur la montre ;
2. la montre passe brièvement le bouton en état `Envoi...` ;
3. le téléphone applique la commande ;
4. la montre reçoit l'état confirmé et remplace l'affichage.
Si l'état confirmé diffère de l'intention initiale parce que le téléphone a déjà changé entre-temps, c'est **le dernier état confirmé** qui gagne visuellement.
Exemple :
- l'utilisateur tape `Pause` sur la montre ;
- presque au même moment, il a déjà repris sur le téléphone ;
- la montre ne doit pas essayer de "corriger" localement ; elle se recale sur le dernier snapshot confirmé.
## Règle de conflit UX
En cas d'actions quasi simultanées téléphone/montre :
- le système applique un ordre réel côté source de vérité ;
- la montre n'affiche jamais un dialogue de conflit ;
- elle se contente d'afficher le résultat réel le plus récent.
Autrement dit : **pas de résolution de conflit visible par l'utilisateur**, seulement un réalignement rapide de l'UI.
## Règle de latence
La montre doit distinguer trois cas UX :
1. **latence courte**
- simple état `Envoi...` sur le bouton.
2. **latence perceptible**
- texte discret `En attente du téléphone`.
3. **absence de réponse**
- état `Connexion perdue` et actions désactivées.
Les seuils temporels exacts sont laissés à Architect, mais la surface doit prévoir ces trois états distincts.
## Signaux sonores et haptiques
## Principe
Pour respecter #92, la montre doit privilégier **l'haptique**. L'audio montre n'est pas requis pour le MVP.
Décisions UX :
- par défaut, **pas de son obligatoire côté montre** ;
- retour principal = vibration ;
- si un son est ajouté plus tard, il doit être bref, non intrusif, et ne jamais prendre le focus audio au détriment de la musique de fond.
## Patterns recommandés
- démarrage/pause/reprise confirmé : **impulsion courte** ;
- fin d'un chrono ou fin de repos : **double impulsion** ;
- état `Chrono suivant prêt` : **double impulsion** au moment où l'état est atteint, puis silence ;
- perte de connexion après action utilisateur : **impulsion lourde unique** optionnelle.
Règles :
- pas de bip de décompte 3-2-1 imposé sur la montre au MVP ;
- pas de répétition haptique continue ;
- éviter la duplication agressive téléphone + montre sur le même événement.
## Exigences de donnée pour Architect
La montre a besoin d'un view model compact, orienté exécution, pas de tout le snapshot séance brut.
Minimum UX requis :
- présence ou absence d'une séance active ;
- statut de connexion téléphone↔montre ;
- `seriesIndex`, `seriesTotal` ;
- `exerciseName` ;
- contexte séquence :
- `sequenceIndex?`, `sequenceTotal?`
- `stepIndex?`, `stepTotal?`
- `stepName?`
- type et valeur du **chrono dominant** ;
- liste compacte des autres chronos actifs visibles ;
- état global :
- `ready`
- `running`
- `paused`
- `nextTimerReady`
- `restRunning`
- `restPaused`
- `noActiveSession`
- `phoneUnavailable`
- libellé de l'action primaire autorisée ;
- liste des actions secondaires autorisées ;
- indicateur `commandPending`.
## Hypothèses techniques laissées à Architect
Points explicitement non tranchés par UX, à valider techniquement :
1. faisabilité et coût d'une app Wear OS Flutter dédiée ou module compagnon séparé ;
2. capacité à exposer côté téléphone un flux d'état d'exécution suffisamment compact et fréquent pour la montre ;
3. stratégie de mise à jour du chrono affiché sur la montre :
- push fréquent depuis le téléphone ;
- ou interpolation locale à partir d'horodatages autoritaires ;
4. transport exact téléphone↔montre et garanties d'acknowledgement pour les commandes ;
5. comportement si le téléphone est verrouillé, en arrière-plan, ou si le process principal est suspendu ;
6. possibilité de déclencher des vibrations montre sans prise de focus audio ;
7. capacité à rendre idempotentes les commandes utilisateur (`pause`, `resume`, `skipStep`, `finishSet`, etc.) ;
8. découpage des use cases côté téléphone pour exposer uniquement les commandes compatibles montre ;
9. gestion exacte des timeouts et des seuils de latence visibles dans l'UI ;
10. politique de duplication ou non des signaux entre téléphone et montre sur un même événement.
## Synthèse décisionnelle pour la suite
La montre doit rester une surface extrêmement simple :
- un écran principal `Séance active` ;
- un écran secondaire `Actions` ;
- un écran dédié `Repos` quand le repos est l'état dominant ;
- des états explicites pour pause, `Chrono suivant prêt`, absence de séance et perte de connexion ;
- une seule action primaire contextuelle à la fois ;
- téléphone autoritaire, montre suiveuse interactive.
Cette forme couvre l'objectif utilisateur réel : piloter entièrement la séance sans sortir le téléphone, tout en conservant la logique d'exécution déjà stabilisée dans GameTime.