Le ticket #82 était resté au statut open malgré le travail terminé (édition manuelle précédente insuffisante) ; clôturé proprement via l'API IdeA. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #82 | 5 |
|
1784705972459 |
UX — Statistiques de progression simples
Intention
La surface doit donner une raison de revenir sans transformer GameTime en dashboard analytique. Elle reste une lecture courte de l'historique existant : combien l'utilisateur s'est entraîné, s'il garde un rythme, et si un exercice progresse dans le temps.
Principe de conception : une vue Progression, trois blocs, aucun classement global complexe.
Surface et navigation
Nouvelle entrée depuis l'accueil
Ajouter une entrée dans la liste d'accueil, après Historique :
Progression
Volume, régularité et évolution par exercice
Icône recommandée : Icons.show_chart ou équivalent Flutter Material.
Raison : les stats doivent être découvrables au même niveau que l'historique, sans densifier la liste historique ni introduire de bottom nav. L'écran reste autonome et lisible.
Lien depuis l'historique
Dans Historique, ajouter une action secondaire en AppBar ou en haut de liste :
Progression
Ce lien ouvre le même écran. L'historique reste la liste chronologique des séances ; Progression est sa lecture synthétique.
Écran Progression
AppBar :
Progression
Sous l'AppBar, un sélecteur de période simple :
4 semaines | 3 mois | Tout
Valeur par défaut : 4 semaines.
Règle : la période filtre tous les blocs de l'écran. Tout permet de ne pas perdre les utilisateurs qui s'entraînent peu.
Structure verticale :
Progression
[4 semaines] [3 mois] [Tout]
VOLUME
[ Séances ] [ Temps total ]
RÉGULARITÉ
Rythme récent
...
PAR EXERCICE
[Choisir un exercice]
[Choisir une mesure si nécessaire]
Graphique simple
Résumé de tendance
Style Court Blazer : fond existant, cartes plates rayon 6 px, bordure discrète, liseré supérieur crimson uniquement sur les blocs clés (Volume, Par exercice). Chiffres principaux en Anton couleur or, labels en Archivo. Ne pas utiliser de couleurs d'alerte pour l'absence d'activité.
Bloc 1 — Volume
Titre de section :
Volume
Deux tuiles compactes :
Séances
8
Temps total
5 h 40
Sous les tuiles, une ligne de contexte facultative si pertinente :
Depuis le 24 juin
ou en période Tout :
Depuis ta première séance
MVP : ne pas afficher de comparaison en pourcentage avec la période précédente. C'est tentant mais trop analytique et fragile avec peu de données.
Bloc 2 — Régularité non culpabilisante
Titre de section :
Régularité
Libellé principal :
Rythme récent
Métrique MVP recommandée : semaines actives sur la période, pas streak quotidien.
Affichage :
3 / 4 semaines actives
Texte secondaire :
Une semaine active contient au moins une séance terminée.
Pourquoi : le basket et l'entraînement ne sont pas forcément quotidiens. Une logique de streak journalier culpabilise vite ; les semaines actives valorisent la reprise et le rythme réel.
Si la période est 3 mois :
8 / 12 semaines actives
Si la période est Tout, afficher une formulation sans ratio trop long :
12 semaines actives au total
Ne pas afficher 0 jour de série, streak perdu, tu as raté... ou du rouge.
Bloc 3 — Progression par exercice
Titre de section :
Par exercice
Choix de l'exercice
Contrôle : menu/selecteur compact.
Placeholder :
Choisir un exercice
Liste : exercices présents dans l'historique filtré, y compris exercices archivés avec badge :
Exercice archivé
Option de tri MVP : derniers exercices joués en premier. Pas besoin de recherche si la liste reste raisonnable ; ajouter recherche seulement si le composant existe déjà ailleurs et se réutilise simplement.
Choix de la mesure
Si l'exercice n'a qu'une mesure exploitable dans l'historique, ne pas afficher de second contrôle.
Si plusieurs mesures existent, afficher un segmented control :
Score | Répétitions | Temps
Libellés selon le type exact :
- Score libre numérique :
Score - Score chrono :
Temps réalisé - Répétitions :
Répétitions - Temps de série :
Temps
Si le score a un label utilisateur, l'afficher dans le résumé, pas forcément dans l'onglet :
Réussites · paniers
Graphique MVP
Graphique simple en ligne ou points reliés, sans multi-séries.
Un point = une séance où cet exercice a un résultat exploitable pour la mesure choisie.
Agrégation par séance :
- Score libre numérique : meilleur score de la séance pour cet exercice, avec unité utilisateur.
- Score chrono : meilleur temps réalisé de la séance pour cet exercice, format
mm:ss.d, avec aide courtePlus bas = mieux. - Répétitions : total des répétitions réalisées sur l'exercice pendant la séance.
- Temps : total du temps réalisé/saisi sur l'exercice pendant la séance.
Sous le graphique, résumé court :
Meilleur : 14 paniers
Dernier : 12 paniers
Pour score chrono :
Meilleur : 00:42.8
Dernier : 00:45.1
Pour répétitions :
Total récent : 180 répétitions
Dernière séance : 45 répétitions
Éviter les promesses de tendance sophistiquée (+12%, en hausse) au MVP, sauf si Architect confirme une agrégation fiable plus tard.
Interaction avec un point
Tap sur un point : afficher un petit détail non modal ou bottom sheet légère :
12 juillet
Séance : Prépa match
Meilleur score : 14 paniers
Action secondaire :
Voir la séance
qui ouvre le détail historique existant.
États vides et limites
Aucun historique
Écran Progression :
Aucune progression à afficher
Termine une séance pour voir ton volume, ton rythme et tes résultats évoluer ici.
[Voir les séances]
Pas de carte vide multiple. Une seule surface d'état vide suffit.
Une seule séance terminée
Afficher Volume normalement.
Dans Régularité :
1 semaine active
Dans Par exercice :
Encore un point de départ
Termine cet exercice dans une autre séance pour voir son évolution.
Si un exercice est sélectionnable, afficher le point unique mais ne pas tracer de tendance.
Historique existant mais aucun résultat exploitable
Exemple : seulement des séries passées, scores texte non numériques, ou mesures absentes.
Pas encore de mesure exploitable
Les graphiques apparaissent quand un exercice contient des répétitions, du temps ou un score numérique enregistré.
Conserver Volume et Régularité si des séances terminées existent.
Score libre non numérique
Ne pas tenter de le grapher.
Message dans Par exercice si seule cette mesure existe :
Score non graphique
Ce score est enregistré comme texte. Tu peux le retrouver dans le détail de l'historique.
Si d'autres mesures existent, masquer cette mesure du segmented control MVP ou la désactiver avec aide courte.
Peu de données sur la période
Si la période sélectionnée n'a pas assez de points mais Tout en a :
Peu de données sur cette période
Passe sur "Tout" pour voir plus d'historique.
Ne pas changer automatiquement la période.
Libellés à utiliser
ProgressionVolumeSéancesTemps totalRégularitéRythme récentsemaines activesPar exerciceChoisir un exerciceTemps réalisépour score chronoPlus bas = mieuxpour score chronoVoir la séance
Libellés à éviter
DashboardAnalyticsPerformance scoreObjectif ratéStreak perdu0 jour consécutifÉchec de progression
Exigences de données pour Architect
L'écran exige une agrégation locale à partir de l'historique existant :
- séances terminées filtrables par date ;
- durée totale par séance ;
- nombre de séances terminées par période ;
- semaines actives par période, avec règle
au moins une séance terminée dans la semaine; - liste des exercices présents dans l'historique, y compris snapshots d'exercices archivés ;
- résultats par exercice, séance, série et mesure ;
- identification du type de score : libre numérique, libre texte, chrono ;
- unité/label utilisateur du score libre ;
- agrégats par séance pour un exercice : meilleur score numérique, meilleur score chrono, total répétitions, total temps.
Point à cadrer : définir précisément quelles séries comptent dans les agrégats (terminée oui, passée/skipped non ; résultat absent non) et comment normaliser les dates de semaine côté local/timezone.
Architect — Cadrage architecture #82
Décision de frontière
La logique de progression appartient à application, exposée par use case et DTO, avec un port de lecture implémenté par Drift. La présentation ne doit pas recalculer les agrégats à partir de WorkoutHistoryUseCases.listActive() : elle choisit une période, un exercice, une mesure, puis affiche les DTO déjà normalisés.
Le domaine conserve les invariants existants (WorkoutHistory, WorkoutHistorySetResult, WorkoutHistoryStepResult, SetResultStatus, ScoreInputMode). Aucun nouveau concept métier persistant n'est nécessaire pour le MVP : les statistiques sont une projection de lecture locale sur l'historique snapshoté.
Le port #81 ExercisePerformanceReferenceRepository reste dédié à l'aide pendant l'exécution (dernière performance / record). Il ne doit pas devenir un port de dashboard. #82 crée donc un port séparé, étroit, orienté progression.
Contrats application à créer
Dans lib/application/ports.dart :
enum ProgressionPeriod { fourWeeks, threeMonths, all }
enum ProgressionMeasure { manualScore, stopwatchScore, reps, time }
final class ProgressionDateRange {
const ProgressionDateRange({required this.startedAt, required this.endedAt});
final DateTime? startedAt; // null pour Tout
final DateTime endedAt; // clock.now(), inclusif côté use case
}
abstract interface class ProgressionStatsRepository {
Future<ProgressionGlobalStatsData> readGlobalStats(ProgressionDateRange range);
Future<List<ProgressionExerciseOptionData>> listExerciseOptions(ProgressionDateRange range);
Future<List<ProgressionMeasureOptionData>> listMeasureOptions({
required ProgressionDateRange range,
required String exerciseKey,
});
Future<ProgressionExerciseSeriesData> readExerciseSeries({
required ProgressionDateRange range,
required String exerciseKey,
required ProgressionMeasure measure,
});
}
exerciseKey est la clé stable de regroupement exposée par le repository : utiliser sourceExerciseIdSnapshot quand présent, sinon exerciseSnapshotId. Le DTO doit aussi exposer cette clé ; l'UI ne reconstruit pas la clé.
Dans lib/application/use_cases.dart :
final class ProgressionStatsUseCase {
const ProgressionStatsUseCase({required this.repository, required this.clock});
Future<ProgressionOverview> getOverview(ProgressionPeriod period);
Future<ProgressionExerciseSeries> getExerciseSeries({
required ProgressionPeriod period,
required String exerciseKey,
required ProgressionMeasure measure,
});
}
Responsabilités du use case : résoudre la période à partir de clock.now(), calculer weeksInPeriod pour 4 semaines et 3 mois, porter la règle Tout, et retourner des DTO de présentation prêts à afficher. Le calcul de début de semaine se fait en temps local de l'app : lundi 00:00 local. Les timestamps stockés restent UTC/Drift ; l'adapter convertit en local pour les buckets de semaines si le calcul n'est pas fait directement par SQLite.
DTO à exposer à la présentation
DTO globaux :
final class ProgressionOverview {
const ProgressionOverview({
required this.period,
required this.rangeLabelKind,
required this.completedSessionCount,
required this.totalActiveMs,
required this.activeWeekCount,
required this.totalWeekCount,
required this.hasAnyCompletedHistory,
required this.exerciseOptions,
});
final ProgressionPeriod period;
final ProgressionRangeLabelKind rangeLabelKind; // sinceDate | sinceFirstSession | none
final int completedSessionCount;
final int totalActiveMs;
final int activeWeekCount;
final int? totalWeekCount; // null pour Tout
final bool hasAnyCompletedHistory;
final List<ProgressionExerciseOption> exerciseOptions;
}
final class ProgressionExerciseOption {
const ProgressionExerciseOption({
required this.exerciseKey,
required this.nameSnapshot,
required this.isArchived,
required this.lastPerformedAt,
required this.measures,
});
final String exerciseKey;
final String nameSnapshot;
final bool isArchived;
final DateTime lastPerformedAt;
final List<ProgressionMeasureOption> measures;
}
DTO mesures et graphique :
final class ProgressionMeasureOption {
const ProgressionMeasureOption({
required this.measure,
required this.label,
this.scoreLabel,
this.scoreUnit,
required this.lowerIsBetter,
});
final ProgressionMeasure measure;
final String label; // Score, Temps réalisé, Répétitions, Temps
final String? scoreLabel;
final String? scoreUnit;
final bool lowerIsBetter; // true seulement pour stopwatchScore
}
final class ProgressionExerciseSeries {
const ProgressionExerciseSeries({
required this.exerciseKey,
required this.exerciseNameSnapshot,
required this.measure,
required this.points,
required this.summary,
required this.state,
});
final String exerciseKey;
final String exerciseNameSnapshot;
final ProgressionMeasureOption measure;
final List<ProgressionPoint> points;
final ProgressionSeriesSummary summary;
final ProgressionSeriesState state;
}
final class ProgressionPoint {
const ProgressionPoint({
required this.workoutHistoryId,
required this.workoutNameSnapshot,
required this.startedAt,
required this.value,
required this.rawValue,
});
final String workoutHistoryId;
final String workoutNameSnapshot;
final DateTime startedAt;
final double value; // valeur numérique pour l'axe Y
final Object rawValue; // double pour score, int ms/reps/time selon mesure
}
ProgressionSeriesState doit couvrir au minimum : ready, singlePoint, emptyForPeriod, emptyButHasAllTimeData, noGraphableMeasure, textScoreOnly. Le MVP ne graphe pas les scores texte. À ce stade le modèle existant stocke actualScore en double?, donc un score libre enregistré ici est déjà numérique ; l'état textScoreOnly sert à rester compatible avec l'UX si des scores texte existent via snapshot JSON ou évolution future, mais DevBackend ne doit pas parser historySnapshotJson pour fabriquer une série.
ProgressionSeriesSummary porte seulement les valeurs fiables demandées par UX : best, last, recentTotal, lastSessionTotal selon la mesure. Pas de pourcentage, pas de tendance calculée.
Règles d'agrégation
Séances incluses : workout_history.deleted_at IS NULL, completed = 1, période sur started_at. ended_at ne pilote pas le filtre MVP.
Volume : COUNT(*) des séances incluses, SUM(total_active_ms).
Régularité : une semaine active = au moins une séance incluse dont started_at tombe dans la semaine locale. Pour 4 semaines, totalWeekCount = 4; pour 3 mois, totalWeekCount = nombre de semaines locales intersectant la période (12 ou 13 selon dates réelles, le libellé UX peut rester approximatif si Front le souhaite, mais le ratio doit venir du DTO). Pour Tout, totalWeekCount = null et activeWeekCount est le total distinct.
Exercices listés : résultats d'historique inclus, status = completed, deleted_at IS NULL, avec au moins une mesure exploitable (actual_score, actual_score_time_ms, actual_reps, actual_time_ms). Inclure workout_history_set_results et workout_history_step_results pour ne pas perdre les exercices à étapes. Trier par MAX(history.started_at) DESC.
Archivage : si sourceExerciseIdSnapshot pointe vers un exercice actif non archivé, isArchived = false; si l'exercice source est archivé, soft-deleted, absent, ou si seule la clé snapshot existe, isArchived = true. Le nom affiché vient toujours du snapshot historique le plus récent, pas de l'exercice courant.
Mesures disponibles :
manualScoresiscore_input_mode_snapshot = manualetactual_score IS NOT NULL.stopwatchScoresiscore_input_mode_snapshot = stopwatchetactual_score_time_ms IS NOT NULL.repssiactual_reps IS NOT NULL.timesiactual_time_ms IS NOT NULL.
Agrégation par séance, pour un exercice et une mesure :
manualScore: meilleurMAX(actual_score)sur la séance.stopwatchScore: meilleurMIN(actual_score_time_ms)sur la séance,lowerIsBetter = true.reps:SUM(actual_reps)sur la séance.time:SUM(actual_time_ms)sur la séance.
Les résultats skipped ne comptent jamais. Les valeurs absentes ne comptent pas. Les séries et step-results peuvent coexister ; l'adapter doit les unir en projection homogène avant agrégation, pas additionner deux fois une même ligne.
Impact repository et Drift
Créer DriftProgressionStatsRepository dans lib/infrastructure/local/drift_repositories.dart et le câbler dans AppBootstrap via ProgressionStatsUseCase + AppDependencies.
Les requêtes Drift recommandées peuvent être en customSelect, comme #81, car elles agrègent sur deux tables de résultats et des buckets temporels. Elles doivent retourner des projections primitives, mappées ensuite vers les DTO application.
Pas de nouvelle table métier. Migration uniquement si ajout d'index. Index recommandés si les tests de requêtes montrent un coût significatif, et acceptables dès B1 car les stats liront l'historique souvent :
CREATE INDEX IF NOT EXISTS idx_workout_history_completed_started
ON workout_history (completed, started_at)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_history_set_progression_exercise
ON workout_history_set_results (source_exercise_id_snapshot, exercise_snapshot_id, workout_history_id, status)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_history_step_progression_exercise
ON workout_history_step_results (source_exercise_id_snapshot, exercise_snapshot_id, workout_history_id, status)
WHERE deleted_at IS NULL;
Ces index imposent un bump de schemaVersion Drift et une migration idempotente. Ils ne changent pas les entités synchronisées et n'impactent pas le serveur.
Découpage de lots
B1 — Contrats application et agrégats globaux
- Ajouter enums/DTO
Progression*dansapplication. - Ajouter
ProgressionStatsRepositoryetProgressionStatsUseCase. - Implémenter
readGlobalStatsdansDriftProgressionStatsRepository. - Câbler dans
AppDependencies/AppBootstrap. - Tests application : résolution période, semaines actives, cas
Tout, historique vide. - Tests infra : volume et semaines actives sur plusieurs séances, soft delete ignoré, séances non terminées ignorées.
Livrable testable seul : le frontend peut déjà afficher Volume, Régularité, état vide global.
B2 — Exercices et mesures disponibles
- Implémenter
listExerciseOptionsetlistMeasureOptions. - Unifier set-results et step-results en projection de lecture.
- Gérer
isArchived, tri par dernière séance, snapshot name. - Tests infra : exercice archivé, exercice source absent, score chrono vs score manuel, résultats skipped/absents exclus.
Livrable testable seul : le frontend peut afficher le selecteur d'exercice et le contrôle de mesure sans graphique.
B3 — Série temporelle par exercice
- Implémenter
readExerciseSeriesavec agrégation par séance. - Calculer
best,last,recentTotal,lastSessionTotalselon mesure. - Gérer
singlePoint,emptyForPeriod,emptyButHasAllTimeData,noGraphableMeasure. - Tests application/infra : max score manuel, min chrono, somme reps, somme temps, ordre chronologique, drill-down
workoutHistoryId.
Livrable testable seul : DTO complet pour graphique et bottom sheet Voir la séance.
F1 — Navigation et écran Progression global
- Ajouter entrée Accueil après
Historique. - Ajouter lien depuis
Historiquevers le même écran. - Créer
ProgressionScreenavec période,Volume,Régularité, états vides globaux. - Tests widget : entrée accueil, navigation depuis historique, période par défaut
4 semaines, empty state.
F2 — Bloc Par exercice
- Connecter les options d'exercice/mesure.
- Afficher exercice archivé, états
pas de mesure exploitable,score non graphique,peu de données. - Afficher graphique simple mono-série et détail de point avec action
Voir la séance. - Tests widget : sélection exercice, mesure unique sans second contrôle, score chrono avec
Plus bas = mieux, point unique sans tendance.
Points hors MVP
Pas de progression par programme dans #82 MVP : le titre commercial mentionne programme, mais la surface UX retenue ne l'expose pas. Ne pas créer de contrat programme préventif. Si demandé plus tard, ajouter un port/DTO séparé basé sur sourceWorkoutTemplateId ou programSnapshotId, après cadrage dédié.
Pas de comparaison période précédente, pas de tendance pourcentage, pas de streak quotidien, pas de parsing de historySnapshotJson pour récupérer des mesures non déjà matérialisées en colonnes.
DevBackend — Implémentation B1/B2/B3
Réalisé
- B1 : ajout des contrats application
ProgressionPeriod,ProgressionMeasure,ProgressionStatsRepository, DTOProgressionOverview/ options / série / points / résumé, etProgressionStatsUseCase. - B1 : implémentation Drift
readGlobalStatsavec filtredeleted_at IS NULL,completed = 1, période surstarted_at, volume total et semaines actives calculées en semaine locale lundi 00:00. - B1 : câblage
ProgressionStatsUseCasedansAppDependencies/AppBootstrap. - B1 : bump Drift
schemaVersionà 18 et ajout idempotent des index recommandés. - B2 : implémentation
listExerciseOptionsetlistMeasureOptions, avec projection homogèneset-results+step-results, clé stablesourceExerciseIdSnapshot ?? exerciseSnapshotId, tri par dernière séance, nom snapshot le plus récent etisArchived. - B3 : implémentation
readExerciseSeriesavec agrégation par séance :MAX(actual_score),MIN(actual_score_time_ms),SUM(actual_reps),SUM(actual_time_ms), ordre chronologique etworkoutHistoryIdpour drill-down. - B3 : calcul application des états
ready,singlePoint,emptyForPeriod,emptyButHasAllTimeData,noGraphableMeasureet des résumésbest/lastourecentTotal/lastSessionTotal.
Fichiers touchés
lib/application/ports.dartlib/application/use_cases.dartlib/application/app_bootstrap.dartlib/infrastructure/local/app_database.dartlib/infrastructure/local/drift_repositories.darttest/application/use_cases_test.darttest/infrastructure/drift_repositories_test.darttest/presentation/home_screen_test.dart(fakeAppDependenciesmis à jour)
Vérifications backend
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/application/ports.dart lib/application/use_cases.dart lib/application/app_bootstrap.dart lib/infrastructure/local/app_database.dart lib/infrastructure/local/drift_repositories.dart test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart test/presentation/home_screen_test.dart: OK, aucune issue.HOME=/tmp /opt/flutter/bin/flutter test test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart: non exécutable dans ce sandbox, le wrapper Flutter tente d'écrire dans/opt/flutter/bin/cacheen lecture seule.HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart test test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart: non exécutable dans ce sandbox, les build hooks tentent un accès réseau àpub.dev(flutter_launcher_icons).
Écarts / limites
- Aucun parsing de
historySnapshotJson, conformément au hors-MVP. - Aucun contrat programme ajouté.
- Le nom d'exercice pour les
step-resultsest récupéré depuis le set-result parent quand présent ; à défaut, la projection retombe surstep_name_snapshot, car la tableworkout_history_step_resultsne porte pas deexercise_name_snapshot.
Correctif régressions (commit a8baa31, DevBackend)
Suite au rapport de test réel de Main (2 échecs directement liés à #82) :
- décodage des timestamps entiers issus de
customSelecten secondes Unix, ce qui corrige le bucket de semaines actives (était1970-01-19). - tri déterministe des options d'exercice : actifs avant archivés, puis dernière séance, nom, clé.
Vérification Main après correctif (commit a8baa31)
flutter test --no-pub test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart test/presentation/home_screen_test.dart exécuté en environnement fonctionnel (hors sandbox Dev) : 102 tests, 1 seul échec — exercise repository round-trips exercise option combinations stopwatch score with stopwatch scored steps (FK media, dette préexistante déjà documentée sur #80/#81, sans rapport avec #82).
Les deux régressions #82 précédemment identifiées (semaines actives mal bucketées, tri des options d'exercice) sont corrigées et confirmées vertes.
Lot backend B1/B2/B3 : GO.
Étape suivante : DevFrontend enchaîne F1 (navigation + écran Progression global : Volume/Régularité/état vide) puis F2 (bloc Par exercice avec graphique).
DevFrontend — Implémentation F1/F2
Réalisé
- F1 : ajout de l'entrée
Progressionsur l'accueil aprèsHistorique, avec l'icôneshow_chartet le sous-titre validé. - F1 : ajout d'une action secondaire
Progressiondans l'AppBar de l'écranHistorique, ouvrant la même surface. - F1 : création de
ProgressionScreenavec sélecteur4 semaines/3 mois/Tout, défaut4 semaines, blocsVolumeetRégularité, et état vide global. - F2 : ajout du bloc
Par exerciceconsommant les options/mesures duProgressionStatsUseCase, avec badgeExercice archivé, segmented control masqué quand une seule mesure est disponible, graphique mono-série, résumé et états dédiés. - F2 : tap sur un point du graphique ouvrant un bottom sheet avec détail et action
Voir la séancevers le détail d'historique existant.
Fichiers touchés
lib/presentation/progression_screen.dartlib/presentation/home_screen.dartlib/presentation/history_screen.dartlib/presentation/presentation.darttest/presentation/progression_screen_test.darttest/presentation/home_screen_test.darttest/presentation/history_screen_test.dart
Vérifications DevFrontend
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/progression_screen.dart lib/presentation/home_screen.dart lib/presentation/history_screen.dart lib/presentation/presentation.dart test/presentation/progression_screen_test.dart test/presentation/home_screen_test.dart test/presentation/history_screen_test.dart: OK, aucune issue.HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze: OK au sens exit code 0 ; 26 infos préexistantes hors périmètre touché restent signalées.HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/progression_screen_test.dart test/presentation/home_screen_test.dart test/presentation/history_screen_test.dart: non exécutable dans ce sandbox, le wrapper Flutter tente d'écrire dans/opt/flutter/bin/cacheen lecture seule.HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart test test/presentation/progression_screen_test.dart test/presentation/home_screen_test.dart test/presentation/history_screen_test.dart: non exécutable dans ce sandbox, les build hooks tentent un accès réseau àpub.dev(flutter_launcher_icons).
Écarts / limites
- L'outil
idea_ticket_update_carnetdemandé n'était pas exposé dans cette session ; le résumé a été ajouté directement au fichier carnet du ticket. - La ligne de contexte
Depuis le ...pour les périodes bornées n'est pas affichée, car le DTO frontend disponible n'expose pas la date de début formatable ; la ligne étant facultative côté UX, l'écran évite une approximation.
Correctif tests widget DevFrontend
Suite au retour Main sur test/presentation/progression_screen_test.dart :
- le cas mesure unique affiche désormais le libellé de mesure (
Temps réalisé,Répétitions, etc.) sans afficher de segmented control. - le test de tap sur point rend explicitement le point visible avant interaction, conformément au comportement scrollable de l'écran.
Vérifications locales sandbox :
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/progression_screen.dart test/presentation/progression_screen_test.dart: OK, aucune issue.HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/progression_screen_test.dart test/presentation/home_screen_test.dart test/presentation/history_screen_test.dart: toujours non exécutable dans ce sandbox, le wrapper Flutter tente d'écrire dans/opt/flutter/bin/cacheen lecture seule.- Commit non réalisé dans ce sandbox :
.git/index.lockne peut pas être créé car.gitest en lecture seule.
QA — Validation fonctionnelle #82
Périmètre relu
- Cadrage complet UX / Architect / DevBackend / DevFrontend lu via
idea_ticket_read("#82", includeCarnet=true). - Fichiers relus :
lib/presentation/progression_screen.dart,lib/presentation/home_screen.dart,lib/presentation/history_screen.dart,test/presentation/progression_screen_test.dart,test/application/use_cases_test.dart,test/infrastructure/drift_repositories_test.dart.
Cohérence UX / frontend
- Accueil : entrée
Progressionprésente aprèsHistorique, icôneIcons.show_chart, sous-titreVolume, régularité et évolution par exercice. - Historique : action secondaire
Progressionen AppBar vers la même surface. - Écran : AppBar
Progression, sélecteur4 semaines/3 mois/Tout, défaut4 semaines, blocsVolume,Régularité,Par exercice. - Libellés attendus présents :
Séances,Temps total,Rythme récent,semaines actives,Choisir un exercice,Temps réalisé,Plus bas = mieux,Voir la séance,Exercice archivé. - Libellés interdits non trouvés dans les fichiers relus :
Dashboard,Analytics,Performance score,Objectif raté,Streak perdu,0 jour consécutif,Échec de progression. - États UX implémentés : vide global, absence de mesure exploitable, point unique, peu de données sur la période avec incitation à passer sur
Tout, score non graphique, mesure chrono avec aidePlus bas = mieux. - Écart relevé non bloquant : la ligne de contexte
Depuis le ...pour période bornée n'est pas affichée ; elle est facultative côté UX et l'écart était déjà documenté par DevFrontend.
Couverture de test relue
test/presentation/progression_screen_test.dartcouvre : période par défaut + état vide global, affichageVolume/Régularité, exercice archivé, point unique, mesure chronoTemps réalisé+Plus bas = mieux, tap sur point + bottom sheet +Voir la séance.test/presentation/home_screen_test.dartcouvre l'entréeProgressionaprèsHistorique.test/presentation/history_screen_test.dartcouvre le lien secondaireProgression.test/application/use_cases_test.dartcouvre : résolution période 4 semaines,Tout, compteur de semaines actives, options exercice + mesures, états de sérieready,emptyButHasAllTimeData,noGraphableMeasure, résumés totals.test/infrastructure/drift_repositories_test.dartcouvre : semaines actives et exclusion séances incomplètes / supprimées, tri actifs avant archivés, mesures score manuel / chrono / reps, exclusion skipped / valeurs absentes, signal mesure non graphique, agrégations par séanceMAX(actual_score),MIN(actual_score_time_ms),SUM(actual_reps),SUM(actual_time_ms), ordre chronologique etworkoutHistoryId, données all-time hors période.
Limite de couverture non bloquante : pas de test widget dédié à textScoreOnly, mais l'état est mappé dans ProgressionScreen et le modèle existant ne matérialise pas encore les scores texte graphiques selon le cadrage Architect.
Commandes exécutées par QA
Commande :
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze
Sortie réelle pertinente :
Analyzing GameTime...
26 issues found.
Verdict commande : exit code 0 ; les 26 issues sont des infos existantes hors périmètre #82.
Commande :
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart analyze lib/presentation/progression_screen.dart lib/presentation/home_screen.dart lib/presentation/history_screen.dart test/presentation/progression_screen_test.dart test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart
Sortie réelle pertinente :
Analyzing progression_screen.dart, home_screen.dart, history_screen.dart, progression_screen_test.dart, use_cases_test.dart, drift_repositories_test.dart...
No issues found!
Verdict commande : VERT.
Commande :
HOME=/tmp /opt/flutter/bin/flutter test --no-pub test/presentation/progression_screen_test.dart test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart
Sortie réelle :
/opt/flutter/bin/internal/update_engine_version.sh: line 71: /opt/flutter/bin/cache/engine.stamp.tmp.24: Read-only file system
/opt/flutter/bin/internal/update_engine_version.sh: line 78: /opt/flutter/bin/cache/engine.realm: Read-only file system
Verdict commande : non exécutée, blocage environnement Flutter cache read-only avant lancement des tests.
Commande :
HOME=/tmp /opt/flutter/bin/cache/dart-sdk/bin/dart test test/presentation/progression_screen_test.dart test/application/use_cases_test.dart test/infrastructure/drift_repositories_test.dart
Sortie réelle :
Running build hooks...Running build hooks...Got socket error trying to find package flutter_launcher_icons at https://pub.dev.
Verdict commande : non exécutée jusqu'au vert, blocage réseau/build hooks.
Verdict QA
GO fonctionnel et cohérence #82, sous réserve explicite que cette session QA n'a pas pu relancer flutter test à cause du sandbox. Aucun écart bloquant trouvé à la lecture du cadrage, de l'implémentation frontend et de la couverture de tests. Les preuves exécutées localement sont : dart analyze complet exit 0 et dart analyze ciblé vert sans issue.