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:
44
.ideai/tickets/126/carnet.md
Normal file
44
.ideai/tickets/126/carnet.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
issueRef: "#126"
|
||||
version: 6
|
||||
updatedBy: {"kind":"user"}
|
||||
updatedAt: 1785216995347
|
||||
---
|
||||
|
||||
## #126 — Cadrage technique Architect (2026-07-27)
|
||||
|
||||
**Root cause identifiée dans le code réel** — bug de calcul, pas une question de conception.
|
||||
|
||||
`_stepTimerProjection` (`lib/application/use_cases.dart:4032-4055`) construit la projection du "chrono étape" envoyée à la montre avec :
|
||||
```dart
|
||||
referenceEpochMs: _epochMs(now),
|
||||
accumulatedMs: state?.accumulatedMs ?? 0, // BUG : valeur brute persistée, pas l'écoulé réel
|
||||
startedAtEpochMs: state?.status == runningTimer ? _epochMs(state!.startedAt!) : null,
|
||||
```
|
||||
Comparer avec `_restTimerProjection` (ligne 4018-4030), qui fait ça correctement :
|
||||
```dart
|
||||
referenceEpochMs: _epochMs(now),
|
||||
accumulatedMs: rest.elapsedMillisecondsAt(now), // écoulé réel jusqu'à `now`
|
||||
startedAtEpochMs: paused ? null : _epochMs(now), // "reparti" depuis `now`, cohérent avec accumulatedMs déjà à jour
|
||||
```
|
||||
`ActiveExerciseStepProgressState` possède déjà `elapsedMillisecondsAt(now)` (`lib/domain/entities.dart:1329-1334`, `accumulatedMs + now.difference(startedAt).inMilliseconds`) — exactement la méthode que `_restTimerProjection` utilise et que `_stepTimerProjection` **n'appelle pas**.
|
||||
|
||||
**Pourquoi ça boucle visuellement** : `WatchWearDataLayerAdapter._ensureProjectionRefreshLoop` (`lib/infrastructure/watch_bridge/wear_data_layer_adapter.dart:139-143`) republie la projection complète toutes les **2 secondes** (`projectionRefreshInterval`, keepalive/résilience), indépendamment de tout changement d'état. À chaque republication, le chrono étape reçoit un nouveau `referenceEpochMs = now` mais un `accumulatedMs` resté à sa valeur brute persistée (proche de 0 tant que l'étape n'a pas de checkpoint) — la montre recalcule alors un écoulé quasi nul et le countdown "resaute" près de sa valeur cible (`targetMs`), avant de redécompter localement pendant ~2s jusqu'à la prochaine republication. D'où le motif observé "1:00 → 59s → 1:00" en boucle, calé sur le cycle de 2s.
|
||||
|
||||
### Correctif (contrat clair pour DevFrontend/DevBackend)
|
||||
Aligner `_stepTimerProjection` sur le pattern déjà utilisé par `_restTimerProjection` :
|
||||
```dart
|
||||
accumulatedMs: state?.elapsedMillisecondsAt(now) ?? 0,
|
||||
startedAtEpochMs: state?.status == ActiveExerciseStepProgressStatus.runningTimer
|
||||
? _epochMs(now)
|
||||
: null,
|
||||
```
|
||||
Aucun changement de contrat (`WatchTimerProjection` inchangé), aucun impact sur la montre (l'interprétation `_interpolatedElapsedMs` côté watch_app est déjà correcte — c'est uniquement la donnée source côté téléphone qui est fausse).
|
||||
|
||||
### Périmètre de vérification
|
||||
Vérifier s'il existe un bug symétrique ailleurs — `_setTimerProjection` (ligne 4079) et `_scoreStopwatchTimerProjection` (ligne 4057) utilisent déjà `state.accumulatedMs` brut, **mais** ce n'est pas un bug pour eux : ce sont des chronos `displayMode: elapsed` (pas `countdown`) dont `startedAtEpochMs` pointe vers le vrai `state.startedAt` d'origine (pas `now`) — cohérent, l'écoulé est recalculé correctement côté montre à partir du vrai point de départ. Seul `_stepTimerProjection` mélange les deux styles (accumulatedMs brut + referenceEpochMs à `now`), ce qui est l'incohérence à corriger.
|
||||
|
||||
### Tests
|
||||
Test unitaire ciblé sur `_stepTimerProjection`/`WatchSessionProjectionProjector` : un chrono étape `runningTimer` avec `startedAt` antérieur à `now` doit produire un `accumulatedMs` qui reflète l'écoulé réel (pas 0), republié à l'identique après un second appel à `now+2s` sans avoir régressé. Peut s'ajouter à `test/application/watch_companion_projection_test.dart` (fichier déjà existant pour ce projecteur, cf. [[gametime-watch-companion-implementation]]).
|
||||
|
||||
**Prêt pour implémentation directe, aucun arbitrage supplémentaire nécessaire.**
|
||||
Reference in New Issue
Block a user