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:
52
.ideai/tickets/135/carnet.md
Normal file
52
.ideai/tickets/135/carnet.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
issueRef: "#135"
|
||||
version: 6
|
||||
updatedBy: {"kind":"user"}
|
||||
updatedAt: 1785151136170
|
||||
---
|
||||
## Cadrage UX (agent UX, 2026-07-27)
|
||||
|
||||
**Nature** : bug technique probable côté transport (latence/perte Wear Data Layer), avec un gap UX qui aggrave la perception du problème — à corriger aussi.
|
||||
|
||||
**Localisation** : `watch_session_view_model.dart::sendPrimaryAction` → `pauseSession`/`resumeSession` ; bouton `_TimerToggleButton` dans `watch_session_screen.dart`, activé seulement si `canToggleTimer` (`!connectionLost && !commandPending && primaryAction ∈ {pauseSession, resumeSession}`).
|
||||
|
||||
**Diagnostic technique à vérifier par Architect/DevBackend** :
|
||||
- « parfois inopérant » : le bouton est désactivé si `primaryAction` n'est pas exactement pause/resume au moment du tap (fenêtre de transition d'état) ou si `connectionLost`/`commandPending` est vrai suite à une commande précédente mal purgée. Vérifier la robustesse de la purge de `commandPending` après timeout/ack et l'exactitude de la projection `primaryAction` en continu.
|
||||
- « parfois lent » : `commandTimeout` est fixé à 2s, `waitingThreshold` à 500ms — un aller-retour Data Layer peut légitimement approcher ces bornes. Vérifier s'il y a des retries/pertes réseau anormales plutôt que juste la latence normale du canal.
|
||||
|
||||
**Décision UX à poser (indépendamment du bug)** : pas d'optimisme sur pause/play — c'est un choix assumé (la montre n'est jamais source de vérité, cf. [[gametime-watch-companion-implementation]]), donc le geste ne doit jamais changer l'icône avant confirmation. Mais le seul signal actuel pendant l'attente est un texte en bas d'écran (« Envoi... » / « En attente du téléphone »), facile à manquer sur un cadran de montre — c'est ce qui fait percevoir la lenteur comme un blocage plutôt qu'un traitement en cours.
|
||||
- Ajouter un signal directement sur le bouton pendant `commandPending` : réutiliser le pattern `_PendingDot` déjà utilisé pour le score (petit point/anneau discret superposé à l'icône pause/play), pas un nouveau composant.
|
||||
- Le bouton reste désactivé pendant l'attente (déjà correct, évite les doubles envois) — mais son état désactivé doit être visuellement identique qu'il soit désactivé pour attente de commande ou pour connexion perdue, ce qui est déjà le cas via `disabledColor`.
|
||||
- Pas de message d'erreur nouveau si le timeout expire : le comportement existant (repasse en `connectionLost`, la surface entière s'assombrit et affiche « Connexion au téléphone perdue ») est suffisant et cohérent — ne pas dupliquer un message par bouton.
|
||||
|
||||
**Résumé** : corriger la robustesse de la commande pause/resume (état + transport) est prioritaire ; ajouter un indicateur "en cours" sur le bouton lui-même est le seul ajustement UX nécessaire pour que la latence normale ne soit plus confondue avec une panne.
|
||||
|
||||
---
|
||||
|
||||
## Audit technique (agent Architect, 2026-07-27)
|
||||
|
||||
**Root cause confirmée dans le code, partagée avec #134** : même mécanisme de bump de revision inconditionnel côté téléphone.
|
||||
|
||||
- `WatchWearDataLayerAdapter._ensureProjectionRefreshLoop` (`lib/infrastructure/watch_bridge/wear_data_layer_adapter.dart:151-155`) appelle `emitCurrentProjection()` toutes les 2s.
|
||||
- `WatchCompanionProjectionUseCases.emitCurrentProjection` (`lib/application/use_cases.dart:3334-3342`) fait `_revision += 1` **inconditionnellement**, même sans changement métier réel.
|
||||
- `pauseSession`/`resumeSession` ne sont **pas** dans `_allowsStaleRevision` (`use_cases.dart:3467-3470` — seuls `incrementScore`/`decrementScore` en sont exemptés). Toute commande pause/resume envoyée avec un `expectedRevision` capté juste avant un tick de refresh (jusqu'à toutes les 2s, en continu, tant qu'une séance est active) risque `WatchCommandAck.rejectedStaleRevision` (`use_cases.dart:3397-3399`) même si rien n'a réellement changé côté téléphone entre-temps.
|
||||
- Ça explique directement les deux symptômes rapportés :
|
||||
- **« parfois inopérant »** : rejet silencieux par staleness de revision, sans lien avec un vrai désaccord d'état pause/resume — c'est un faux positif de la vérification de concurrence.
|
||||
- **« parfois lent »** : latence normale du round-trip Wear Data Layer (`waitingThreshold` 500ms / `commandTimeout` 2s) — comportement transport attendu, pas un bug ; c'est bien un pur besoin d'indicateur UI comme cadré par UX.
|
||||
- Point vérifié et **non buggé** : `canToggleTimer` (`watch_app/lib/presentation/watch_session_screen.dart:248-253`) exige `primaryAction` exactement pause/resume et `!commandPending` — c'est le comportement voulu (pas d'optimisme), le bouton se désactive/réactive correctement au fil des projections ; aucune correction nécessaire ici.
|
||||
|
||||
**Contrat technique — DevBackend** :
|
||||
- Même fix que #134 : ne bumper `_revision` dans `emitCurrentProjection()` que sur changement réel de projection (hors `revision`/`projectedAtEpochMs`, timers interpolables traités comme stables tant qu'ils tournent sans transition). Un seul correctif, bénéficie aux deux tickets.
|
||||
- Ne pas ajouter `pauseSession`/`resumeSession` à `_allowsStaleRevision` : contrairement aux actions terminales de #134, accepter une revision périmée sur pause/resume pourrait appliquer le mauvais toggle si le téléphone a un état réellement différent (ex. auto-pause côté téléphone pour une autre raison) — la vérification de revision est la bonne garde-fou ici, conforme à la décision UX "pas d'optimisme". Le vrai fix est d'arrêter le bump inutile, pas d'affaiblir le contrôle.
|
||||
|
||||
**Contrat technique — DevFrontend (watch_app)** :
|
||||
- Implémenter l'indicateur déjà cadré par UX : ajouter un prop `pending: bool` à `_TimerToggleButton` (`watch_session_screen.dart:624-649`), affichant un point/anneau discret superposé à l'icône (réutiliser `_PendingDot`, déjà utilisé dans `_ManualScoreContent` ligne 579-582), actif quand `state.commandPending` est vrai **et** que le bouton concerné est celui qui a déclenché la commande (`onTogglePause != null`, i.e. `canToggleTimer` était vrai au moment du tap) — ne pas afficher le point si `commandPending` provient d'une autre action (ex. `skipCurrentRest`) sur un bouton qui n'a pas été pressé pour pause/resume.
|
||||
- Wiring aux deux call sites existants : `_ActiveContent` (ligne 484-487) et `_RestContent` (ligne 686-690).
|
||||
- Ne rien changer au comportement de timeout existant (retour à `connectionLost`) — conforme à la décision UX de ne pas dupliquer de message par bouton.
|
||||
|
||||
**Contrat bridge** : aucune extension nécessaire — fix uniquement côté projection téléphone (partagé avec #134) + rendu local montre à partir de `commandPending`, déjà suivi dans `WatchSessionViewModel`.
|
||||
|
||||
**Testabilité** :
|
||||
- Fix revision (backend) : sandbox, test unique partagé avec #134 sur `emitCurrentProjection()`.
|
||||
- Indicateur `_PendingDot` sur le bouton : sandbox via `watch_app/test/presentation/watch_session_screen_test.dart` — simuler `commandPending=true` après tap pause/resume, vérifier l'affichage du point, puis sa disparition à l'ack.
|
||||
- Vérification terrain (on-device) recommandée après le fix backend partagé : confirmer que le taux d'échec perçu ("inopérant") chute significativement une fois le bump de revision corrigé ; la latence résiduelle ("lent") restera normale et sera couverte par l'indicateur UI, à valider visuellement sur montre réelle (difficile à juger en sandbox pur).
|
||||
16
.ideai/tickets/135/issue.md
Normal file
16
.ideai/tickets/135/issue.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
id: "9fddc019-342f-4063-a194-a5a77907a793"
|
||||
number: 135
|
||||
title: "[Bug] bouton pause/play de la montre qui ne fonctionne pas toujours"
|
||||
status: "closed"
|
||||
priority: "medium"
|
||||
sprint: null
|
||||
links: []
|
||||
agentRefs: [{"agentId":"57695b92-24d0-4876-837c-76116e70a6ae","role":"assigned"}]
|
||||
createdBy: {"kind":"user"}
|
||||
updatedBy: {"kind":"user"}
|
||||
createdAt: 1785138943933
|
||||
updatedAt: 1785151136170
|
||||
version: 6
|
||||
---
|
||||
Le bouton pause/play ne fonctionne pas toujours. De temps en temps il met du temps a répondre et de temps en temps il ne fonctionne pas du tout
|
||||
Reference in New Issue
Block a user