7.9 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #157 | 8 |
|
1785321673274 |
Cadrage UX — agent UX (2026-07-28)
Statut : cadrage exploitable, aucune nouvelle surface à créer. Ce ticket branche une donnée (distance live) sur des emplacements déjà spécifiés et déjà construits par gametime-ux-online-client-adjacent (#155 cadrage transverse) et #159 (surface montre, fermé). Ne pas concevoir de nouvel écran ni de nouveau composant : la distance vient simplement remplir un slot déjà réservé, aujourd'hui silencieusement absent.
Rappel des deux emplacements concernés (déjà cadrés par #155, à ne pas rouvrir)
Téléphone, barre stats live compacte sous le bloc d'exécution :
FC 142 bpm · 0,84 km · 186 kcal
- ordre fixe FC → Distance → Calories, la distance est toujours au milieu ;
- aujourd'hui la barre affiche seulement
FC 142 bpm(distance et calories absentes) — c'est le comportement silencieux attendu, pas un bug ; - dès que la distance devient disponible, elle s'insère à sa place dans l'ordre, sans changement de layout, sans réanimation d'apparition particulière au-delà du refresh discret déjà en place pour la FC.
Montre, écran secondaire Stats (glissement inverse d'Actions, cf. #159) :
- une ligne
Distancevient s'ajouter aux côtés deFC(icône cœur, cf. #161) etCalories, toujours dans l'ordre FC / Distance / Calories ; - pas d'icône dédiée à inventer pour la distance : libellé texte
Distanceen Archivo au-dessus de la valeur, même traitement typographique queCalories(aucune des deux n'est la donnée dominante de l'écran montre, contrairement à la FC sur l'écran principal) ; - écran principal montre : la distance n'y apparaît jamais, seule la FC y a droit (règle #155/#161 déjà actée, ce ticket ne la touche pas).
Format d'affichage
- Unité
km, séparateur décimal virgule (locale FR), deux décimales :0,84 km(cohérent avec l'exemple historique2,84 kmdéjà utilisé dans le cadrage #155/carte historique). - Pas de conversion m/km dynamique à ce stade (pas de "840 m" sous 1km) — rester simple, un seul format, cohérent partout où la distance est affichée (téléphone, montre, futur après-séance/historique #158/#160).
Garde-fous
- Silence total si la donnée est absente (pas de capteur GPS/distance sur la montre, pas de fix GPS encore acquis, montre non compatible) : la ligne/le segment disparaît, jamais de
--ni de placeholder. Le comportement actuel (barre réduite àFCseule) est déjà la bonne référence. - Perte temporaire de mesure live : garder la dernière valeur connue plutôt que de faire disparaître puis réapparaître le segment en boucle (cf. règle #155 "perte temporaire → dernière valeur connue si la session l'utilise déjà").
- Ne pas transformer la distance en donnée dominante nulle part sur ce lot : elle reste secondaire au téléphone (barre compacte) et secondaire à la montre (écran Stats uniquement, jamais écran principal).
- Aucun message bloquant, aucune demande de permission spécifique visible à l'utilisateur au-delà de ce qui est déjà géré par les tickets permissions capteurs montre déjà livrés (cf. commits f71a1551/58272e35).
Compatibilité avec #155/#159/#161 déjà traités
Ce ticket ne modifie aucune décision de #155 (surfaces), #159 (navigation montre par glissement inverse) ni #161 (icône cœur FC, dédoublonnage écran principal). Il alimente uniquement les emplacements distance déjà prévus et actuellement vides faute de donnée.
Cadrage Architect (2026-07-28)
Statut : surprise majeure — ce ticket n'a rien de "Frontend" restant à faire. Vérifié en lecture directe :
lib/presentation/workout_execution_screen.dart(_LiveSensorBar, ~L2221) affiche déjà FC + Distance + Calories en pills conditionnelles (if (sensorState.latestHeartRateBpm case final heartRate?),if (_distanceLabel(sensorState) case final distanceLabel?),if (_caloriesLabel(sensorState) case final caloriesLabel?)) — silence total déjà implémenté exactement comme cadré par UX.watch_app/lib/presentation/watch_session_screen.dart(~L1187-1196) affiche déjàDistance/Caloriesdans l'écran secondaireStats, via_distanceLabel/_caloriesLabelsurWatchSensorSample.- Le contrat
packages/watch_bridge_contract/.../watch_bridge_contract.dartporte déjàdistanceMetersetcaloriesKcalsurWatchSensorSample/WatchSensorSummary(schemaVersion 4), sérialisation JSON incluse. - Donc l'intégralité de la chaîne UI téléphone + UI montre + contrat de transport est déjà livrée. Le titre "[DevFrontend] Distance live montre" ne correspond plus à l'état réel du code.
Root cause probable du "distance toujours vide" — risque architecture réel côté watch natif
WatchHeartRateCollector.kt:106-107 enregistre DataType.DISTANCE et DataType.CALORIES via MeasureClient.registerMeasureCallback, le même client que pour la FC. Or Health Services MeasureClient est documenté par Google comme ne supportant en pratique de façon fiable que quelques types passifs (FC, et sur certains devices SpO2) : la distance et les calories nécessitent normalement un ExerciseClient (session d'exercice Health Services explicite, avec ExerciseType, capacités GPS le cas échéant), pas le mode passif "measure" utilisé ici.
Conséquence probable : sur device réel, registerMeasureCallback(DataType.DISTANCE, ...) échoue ou ne remonte simplement jamais de données — et comme onRegistrationFailed n'est que loggé (jamais remonté), ce échec est indiscernable du fallback silencieux voulu par l'UX ("pas de donnée = rien ne s'affiche"). C'est très probablement pour cela que le ticket reste ouvert malgré une UI déjà terminée : la donnée source ne remonte jamais côté device réel, mais aucun signal ne le révèle sans instrumentation ciblée.
Plan exécutable
- Vérification (watch bridge natif, DevBackend/watch) : appeler
measureClient.getCapabilities()sur un device Wear OS réel compatible et confirmer siDataType.DISTANCE/DataType.CALORIESsont listés comme supportés en modeMeasureClient. C'est un test de quelques lignes, pas un chantier. - Si non supporté (cas attendu) : cadrer un sous-lot dédié
ExerciseClientpour distance/calories, distinct du pipeline FC actuel — cycle de vie propre (start/pause/stop de session d'exercice aligné sur la séance GameTime), permissions supplémentaires possibles (ACCESS_FINE_LOCATIONselon device pour la distance GPS-based). Ne pas le faire rentrer de force dansWatchHeartRateCollector: il mérite son propre collecteur (WatchExerciseMetricsCollectorou équivalent) branché sur le même pipeline de sample/summary existant (mêmes champsWatchSensorSample.distanceMeters/caloriesKcal, pas de nouveau contrat nécessaire). - Si supporté mais silencieux pour une autre raison (permission refusée, capability disponible mais jamais déclenchée) : corriger l'enregistrement/l'octroi de permission ciblé, toujours sans toucher au contrat ni à l'UI.
- Aucun travail Frontend Dart/Flutter requis dans les deux cas — l'UI est prête et attend juste que le sample porte une valeur non nulle.
- Reclassement recommandé : ce ticket devrait passer de
[DevFrontend]à[DevBackend]/watch bridge natif dans son titre, pour éviter qu'il soit repris à tort comme un chantier Flutter.
Risque transverse
Si le sous-lot ExerciseClient s'avère nécessaire, il ajoute une complexité (permission localisation, gestion double session Health Services FC+Exercise en parallèle) non anticipée par le cadrage initial #156, qui supposait implicitement une seule source de collecte. À valider avec Main si le produit veut assumer cette complexité maintenant ou accepter un dégradé silencieux permanent (distance/calories jamais peuplées) sur les devices où seul MeasureClient HR fonctionne.