Les widgets reçoivent la bande et les préréglages au lieu de les inventer

La couleur du score de forme était décidée ici, avec un barème périmé
(vert >= 75, jaune >= 50). Le serveur avait recalé le sien sur la
distribution réelle (65/55/40) parce que le score est borné à [25, 75] :
une bande haute à 75 est inatteignable. Mesuré sur 115 jours de
production, le vert n'est jamais sorti et le rouge couvrait 68 jours.
La correction n'avait pas atteint le binaire.

`FormeScore` porte désormais la bande servie par le serveur, et
`resolvedBand` ne sert que de repli — aligné sur les seuils serveur, pas
sur les anciens.

Le widget de saisie rapide enregistrait un café à 100 ml, volume qui ne
correspond à aucun préréglage : le serveur avait séparé l'expresso (60)
du mug (250) parce qu'un volume unique faussait le journal, et ces
préréglages sont personnalisables. Les boutons viennent maintenant du
snapshot ; `fallbackQuickDrinks` reprend les préréglages par défaut du
serveur.

15 tests ajoutés au banc d'essai Linux, qui compile réellement ces deux
fichiers : 25 tests verts. Le reste (SwiftUI, WidgetKit) n'est pas
compilable ici et n'a été que relu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-08-19 11:42:19 +00:00
parent a965c1ad2b
commit 4cff1d4603
7 changed files with 304 additions and 24 deletions

View File

@@ -249,11 +249,19 @@ struct CoachTodaySessionWidget: Widget {
struct CoachFormeScoreEntryView: View {
var entry: CoachSnapshotEntry
private func ringColor(_ score: Int) -> Color {
switch score {
case 75...: return Color(red: 0.20, green: 0.78, blue: 0.35)
case 50..<75: return Color(red: 1.00, green: 0.80, blue: 0.0)
default: return Color(red: 1.00, green: 0.27, blue: 0.23)
/// Couleur de la bande servie par le serveur.
///
/// Les seuils vivaient ici (75 / 50), périmés face à ceux du serveur
/// (65 / 55 / 40). Le score étant borné à [25, 75] par construction, le vert
/// était inatteignable 0 jour sur 115 en production. Voir
/// `CoachWidgetSnapshot.FormeScore.resolvedBand`, qui porte le repli.
private func ringColor(_ band: CoachWidgetSnapshot.FormeBand?) -> Color {
switch band {
case .great: return Color(red: 0.20, green: 0.78, blue: 0.35)
case .good: return Color(red: 0.30, green: 0.85, blue: 0.60)
case .medium: return Color(red: 1.00, green: 0.80, blue: 0.0)
case .low: return Color(red: 1.00, green: 0.27, blue: 0.23)
case nil: return Color.gray
}
}
@@ -270,7 +278,7 @@ struct CoachFormeScoreEntryView: View {
Circle().stroke(Color.secondary.opacity(0.18), lineWidth: 9)
Circle()
.trim(from: 0, to: min(max(Double(score) / 100.0, 0), 1))
.stroke(ringColor(score), style: StrokeStyle(lineWidth: 9, lineCap: .round))
.stroke(ringColor(forme?.resolvedBand), style: StrokeStyle(lineWidth: 9, lineCap: .round))
.rotationEffect(.degrees(-90))
VStack(spacing: 0) {
Text("\(score)").font(.system(size: 30, weight: .bold, design: .rounded))