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:
142
.ideai/tickets/94/carnet.md
Normal file
142
.ideai/tickets/94/carnet.md
Normal file
@ -0,0 +1,142 @@
|
||||
---
|
||||
issueRef: "#94"
|
||||
version: 8
|
||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||
updatedAt: 1784936913063
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Architect — Cadrage point 2 : regroupement des étapes sans chrono (ticket #94)
|
||||
|
||||
Périmètre : uniquement le point 2 de l'analyse UX ci-dessus (regroupement/auto-complétion silencieuse des étapes sans chrono). Les points 1/3/4/5 restent des ajustements UI purs, DevFrontend les traite directement sans ce cadrage.
|
||||
|
||||
## Ce qui existe déjà et se réutilise tel quel
|
||||
|
||||
Code lu : `lib/application/use_cases.dart` (`ActiveExerciseStepUseCases`, ~L3087-3653) et `lib/domain/entities.dart` (`ActiveExerciseStepProgressState`, `ActiveExerciseStepResult`, `ExerciseStep`, `ActiveExerciseStepProgressStatus`).
|
||||
|
||||
- `_StepSequenceContext` porte déjà `steps`, `expectedPassages`, `autoStartNextTimedStep` (résolu depuis #73/#76) — tout ce qu'il faut pour scanner la timeline à venir sans lecture DB supplémentaire.
|
||||
- `_advanceState(context, state, now, {completedStep})` fait déjà avancer d'une étape en traversant les frontières de passage (`nextStep >= steps.length → nextPassage++`) et sait déjà produire `sequenceComplete` en fin de dernier passage. C'est exactement la mécanique de traversée qu'il faut réutiliser en boucle pour le regroupement — **pas besoin de réécrire la logique de frontière de passage**, juste de l'appeler plusieurs fois d'affilée avant l'action déclenchante, comme `_autoAdvanceElapsedTimers` le fait déjà pour les chronos qui s'enchaînent.
|
||||
- `_stepResult(...)` sait déjà construire un `ActiveExerciseStepResult` `completed` avec `actualReps ?? step.defaultTargetValue` (cf. `completeCurrentStep`, L3301-3303) — c'est la convention à réutiliser pour l'auto-complétion silencieuse (valeur cible enregistrée comme valeur réalisée, jamais `null`, cohérent avec `gametime-architecture-exercise-steps` qui laissait ce choix à l'implémentation).
|
||||
- `ActiveExerciseStepProgressStatus.waitingManual` reste le statut correct pour une étape reps individuelle ; on ne touche pas à l'enum.
|
||||
|
||||
## Ce qui doit changer : nouvelle logique de calcul, pas de nouveau champ persisté
|
||||
|
||||
**Pas de changement de modèle de données.** `ActiveExerciseStepProgressState` et `ActiveExerciseStepResult` restent inchangés dans leur forme. La notion de « suite silencieuse » est **dérivée à la volée** depuis `context.steps` + position courante, exactement comme `_isNextTimedStepReady` (présentation, ~L2354-2368) dérive déjà `Chrono suivant prêt` sans champ dédié. Raison : la suite n'est qu'une lecture en avance sur une liste déjà chargée en mémoire (max 8 étapes, cf. `gametime-architecture-exercise-steps`), aucune raison de la persister.
|
||||
|
||||
### Règle d'éligibilité au regroupement silencieux
|
||||
|
||||
Une étape est éligible au regroupement silencieux si et seulement si :
|
||||
|
||||
```dart
|
||||
bool _isSilentStep(ExerciseStep step) {
|
||||
if (step.type == ExerciseStepType.time) return false;
|
||||
if (step.hasScore && step.scoreInputMode == ScoreInputMode.stopwatch) return false;
|
||||
return true; // reps sans score, ou reps avec score manuel
|
||||
}
|
||||
```
|
||||
|
||||
C'est la traduction directe du cas limite déjà identifié par UX : une étape reps à score chrono a son propre chrono actif et reste un point d'interaction à part entière.
|
||||
|
||||
### Calcul de la suite courante (nouvelle fonction application, pure, sans I/O)
|
||||
|
||||
```dart
|
||||
_SilentStepGroup _resolveSilentGroup(
|
||||
_StepSequenceContext context,
|
||||
ActiveExerciseStepProgressState state,
|
||||
) {
|
||||
final steps = <ExerciseStep>[];
|
||||
var passage = state.currentPassageIndex;
|
||||
var index = state.currentStepIndex;
|
||||
while (true) {
|
||||
final step = context.steps[index];
|
||||
if (!_isSilentStep(step)) {
|
||||
return _SilentStepGroup(steps: steps, blockingStep: step, blockingPassageIndex: passage, reachesSequenceEnd: false);
|
||||
}
|
||||
steps.add(step);
|
||||
index++;
|
||||
if (index >= context.steps.length) {
|
||||
index = 0;
|
||||
passage++;
|
||||
if (passage >= context.expectedPassages) {
|
||||
return _SilentStepGroup(steps: steps, blockingStep: null, blockingPassageIndex: passage, reachesSequenceEnd: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Appelée uniquement quand l'étape courante (`_currentStep(context, state)`) est elle-même silencieuse — sinon la vue reste le mode `_CurrentStepPane` classique existant (étape chronométrée, ou étape reps isolée). En pratique une étape reps isolée forme un groupe à 1 élément : le layout groupé et le layout actuel convergent visuellement pour ce cas trivial. DevFrontend affiche le layout groupé dès qu'il y a ≥1 étape silencieuse en tête, pas seulement ≥2, pour ne pas avoir deux présentations différentes selon la longueur de la suite.
|
||||
|
||||
### Port/contrat exposé à la présentation
|
||||
|
||||
Étendre `ActiveExerciseStepProgressView` (pas de nouveau port séparé — c'est le même port `ActiveExerciseStepUseCases`/vue déjà consommé par l'écran) avec :
|
||||
|
||||
```dart
|
||||
final class ActiveExerciseStepProgressView {
|
||||
// ... champs existants inchangés (state, steps, expectedPassages, results)
|
||||
final List<ExerciseStep> silentPendingSteps; // vide si l'étape courante n'est pas silencieuse
|
||||
final ExerciseStep? silentGroupBlockingStep; // étape chronométrée qui suit la suite, null si fin de séquence
|
||||
final bool silentGroupReachesSequenceEnd; // true si la suite va jusqu'à sequenceComplete
|
||||
}
|
||||
```
|
||||
|
||||
Calculée dans `startOrResumeProgress`/`readProgress` à partir de `_resolveSilentGroup`, en lecture seule — aucune écriture tant que l'utilisateur n'a pas déclenché l'action de clôture. `silentPendingSteps` est la liste à afficher (« Pompes 10 reps / Squats 15 reps / Fentes 10 reps ») ; `silentGroupBlockingStep` alimente la ligne « Chrono suivant : Gainage · 20 s » ; `silentGroupReachesSequenceEnd` sert à afficher le message de fin de série au lieu du bouton `Lancer`.
|
||||
|
||||
### Nouvelle méthode d'auto-complétion silencieuse (écriture)
|
||||
|
||||
Un seul point d'entrée, appelé par les deux actions déclenchantes :
|
||||
|
||||
```dart
|
||||
Future<ActiveExerciseStepProgressState> completePendingSilentSteps({
|
||||
required String sessionId,
|
||||
required int programIndex,
|
||||
required int exerciseIndex,
|
||||
required int setIndex,
|
||||
}) async {
|
||||
final context = await _stepContext(...);
|
||||
var state = await _requiredStepProgressState(...);
|
||||
final now = clock.now();
|
||||
while (_isSilentStep(_currentStep(context, state))) {
|
||||
final step = _currentStep(context, state);
|
||||
final result = _stepResult(
|
||||
context: context, state: state, step: step,
|
||||
status: SetResultStatus.completed, now: now,
|
||||
actualReps: step.type == ExerciseStepType.reps ? step.defaultTargetValue : null,
|
||||
);
|
||||
await sessionRepository.saveExerciseStepResult(result);
|
||||
state = _advanceState(context, state, now, completedStep: step);
|
||||
}
|
||||
await sessionRepository.saveExerciseStepProgressState(state);
|
||||
return state;
|
||||
}
|
||||
```
|
||||
|
||||
Réutilise `_advanceState` tel quel pour la traversée multi-passages (aucune modification de cette méthode nécessaire : elle gère déjà `nextPassage++` et `sequenceComplete`).
|
||||
|
||||
**Intégration côté « Lancer » (chrono suivant)** : `startTimer(...)` doit appeler `completePendingSilentSteps(...)` en tout premier (si l'étape courante est silencieuse) avant sa logique actuelle — après quoi l'étape courante est bien l'étape chronométrée bloquante, et le reste de `startTimer` fonctionne sans modification.
|
||||
|
||||
**Intégration côté « Terminer la série »** : le use case derrière `_finishSet` côté présentation (`recordCurrentSetResult`/`upsertSetResultAtPosition`, `lib/application/use_cases.dart` ~L2435-2487) doit, quand une séquence d'étapes existe pour la série (`context` résoluble sans exception) et que l'état n'est pas déjà `sequenceComplete`, appeler `completePendingSilentSteps(...)` avant d'enregistrer le résultat de série — silencieusement, sans passer par un dialogue supplémentaire (contrairement au dialogue existant `Chrono non lancé` qui gère un autre cas). Point d'attention DevBackend : ne déclencher cet appel que si l'étape courante est silencieuse ; si elle est déjà chronométrée/bloquante, ne rien faire (comportement actuel inchangé, l'utilisateur clôture une série avec une étape chrono en cours comme aujourd'hui).
|
||||
|
||||
### Cas limites couverts par ce calcul
|
||||
|
||||
- **Traversée de plusieurs passages** : couverte nativement par la boucle `while` de `_resolveSilentGroup`/`completePendingSilentSteps`, qui réutilise `_advanceState` — aucune règle spéciale à écrire, c'est la même mécanique que `_autoAdvanceElapsedTimers` applique déjà pour les chronos.
|
||||
- **Étape reps à score chrono** : exclue par `_isSilentStep`, casse le regroupement comme demandé — elle redevient un point d'arrêt classique (`waitingManual`), sans changement de comportement pour elle.
|
||||
- **Séquence entièrement sans chrono** : `silentGroupReachesSequenceEnd == true` dès `startOrResumeProgress`, dès la première étape ; `completePendingSilentSteps` sera appelée uniquement à `Terminer la série`, donc aucune écriture avant cette action (conforme à « l'utilisateur ne touche le téléphone qu'une fois, à la fin »).
|
||||
- **Reprise après kill/pause** : rien à faire de spécial, `silentPendingSteps` est recalculé à chaque `startOrResumeProgress`/`readProgress` depuis l'état persistant réel (pas de champ volatile en mémoire à perdre).
|
||||
|
||||
### Pas de réglage configurable
|
||||
|
||||
Confirmé avec la recommandation UX : comportement toujours actif, pas d'override façon `autoStartNextTimedStep` (#73). Contrairement à #73, il n'existe ici aucune fenêtre de temps réel entre étapes silencieuses (pas de chrono qui tourne), donc aucun cas d'usage produit ne justifie une confirmation étape par étape. Ne pas ajouter de champ domaine `Exercise`/`ProgramExercise`/`WorkoutTemplateExerciseOverride` pour ce point. Si un besoin réel émerge après usage, rouvrir un ticket dédié plutôt que d'anticiper.
|
||||
|
||||
## Découpage en lots
|
||||
|
||||
- **B1 — DevBackend** : `_isSilentStep`, `_resolveSilentGroup`, extension de `ActiveExerciseStepProgressView` (3 nouveaux champs), `completePendingSilentSteps`, intégration dans `startTimer` et dans le use case de clôture de série (`recordCurrentSetResult`/équivalent derrière `_finishSet`). Tests ciblés : suite silencieuse simple, suite traversant 2+ passages, suite bloquée par étape à score chrono, suite qui va jusqu'à `sequenceComplete`, reprise après kill au milieu d'une suite silencieuse.
|
||||
- **F — DevFrontend** (en plus des points 1/3/4/5 déjà cadrés UI par UX) : nouveau layout du panneau séquence quand `silentPendingSteps.isNotEmpty` (liste « À enchaîner sans interruption » + ligne `Chrono suivant : nom · cible` + bouton `Lancer`, ou message de fin de série sans bouton dédié si `silentGroupReachesSequenceEnd`), réutilisant le format déjà défini par UX dans son cadrage ci-dessus. Dépend de B1.
|
||||
|
||||
## Étape suivante
|
||||
|
||||
- **Git** : créer la branche de travail pour #94.
|
||||
- **DevBackend** : lot B1 ci-dessus (point 2 uniquement).
|
||||
- **DevFrontend** : points 1, 2 (lot F, dépend de B1), 3, 4, 5 — cadrage UX complet disponible plus haut dans ce carnet pour 1/3/4/5, cadrage Architect ci-dessus pour le point 2.
|
||||
24
.ideai/tickets/94/issue.md
Normal file
24
.ideai/tickets/94/issue.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
id: "2dd7f167-7478-49f0-a069-7420cc1464f4"
|
||||
number: 94
|
||||
title: "Refonte systeme etapes passage et serie d'un exercice"
|
||||
status: "closed"
|
||||
priority: "medium"
|
||||
sprint: null
|
||||
links: []
|
||||
agentRefs: []
|
||||
createdBy: {"kind":"user"}
|
||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||
createdAt: 1784732039058
|
||||
updatedAt: 1784936913063
|
||||
version: 8
|
||||
---
|
||||
Dans un exercice il y a un systeme de serie, de répétitions, d'étapes et de passage. Ca fait beaucoup, et ce n'est pas forcemment très clair autant a utiliser qu'a configurer. Il faut, sans perdre de fonctionnalité, épurer tout ça ou le rendre plus clair en ayant un affichage moins chargé. Par ne pas perdre de fonctionnalité, j'entends qu'il faut que l'ont puisse créer un maximum d'exercices en les organisant correctement. Il est important qu'on puisse enchainer différent mouvement au sein d'un même exercice et plusieurs fois ou pendant un certain temps.
|
||||
|
||||
J'entends par la que si je veux m'échauffer au niveau du dribble par exemple, il est important que je puisse faire 15sec mains gauche, puis 15sec mains droite par exemple sans avoir a toucher mon téléphone qui me dit quand changer de main (c'était l'interet des étapes avec chrono). je pense aussi qu'il est important de pouvoir consulter les différentes étapes d'une série avant de lancer la série pour savoir quoi faire lorsque le temps de chaque étape est écoulé.
|
||||
J'aimerais aussi qu'on trouve un meilleur affichage pour les étapes qui n'ont pas de chrono mais un nombre de répétition. Le but des étapes et de pouvoir les enchainer sans avoir a toucher le téléphone, si j'ai beaucoup d'étapes avec très peu de répétitions je me retrouve a toujours avoir le téléphone dans les mains, on pourrait par exemple, s'il y a une succession d'étapes sans chrono, les afficher avec le nombre de répétition d'étapes à faire. ça éviterait de devoir, pour chaque étape, passer à l'étape suivante, on aurait à le faire que pour terminer la série si c'était la derniere étape du dernier passage ou pour lancer le chrono de l'étape suivant si l'étape qui suit la dernière étape sans chrono est finie.
|
||||
|
||||
La notion de passage doit permettre de boucler sur une séquence d'étapes sans temps de repos alors que la notion de série doit permettre de boucler sur une séquence de passage avec du temps de repos. J'aimerais que ça soit un peu plus clair. Je pense qu'a coté des étapes, on pourrait se contenter d'afficher notre nombre de passage uniquement s'il y en a plusieurs en marquant par exemple uniquement 1/5. On pourrait aussi enlever "Etape 1/4" étant donné qu'on a déjà l'information avec les carrés qui représentent les étapes.
|
||||
|
||||
Quand l'exercice n'est pas encore démarré, le bouton Démarrer l'exercice vient écraser tout le texte sur le côté de l'écran et le chrono ainsi que le texte de la première étape (si l'étape possède un chrono) est écrasé entre le nom de l'étape et le bouton "Passer l'étape". Il faut trouver quelque chose pour corriger ça. Enlever "etape 1/4" qui est une répétition du nombre d'étapes et enlever "Passage réalisés" qui est actuelleemnt juste au dessus du bouton Terminer la série en mettant par exemple l'informaation à la place du titre SEQUENCE qui je pense ne sert pas à grand chose pourrait aussi permettre de gagner de la place et de la lisibilité.
|
||||
On pourrait aussi enlever le bouton "Pause" étant donné qu'il fait la même chose que la fleche de retour, et pourquoi pas mettre le temps de la séance sur cette ligne la
|
||||
Reference in New Issue
Block a user