Le widget ne passait pas minuit : personne ne lisait updatedAt
Signale par Sylvain le 2026-08-18 : passe minuit, l'hydratation, les calories et la seance de la veille restaient affichees comme celles du jour. Il fallait ouvrir l'app pour les voir retomber a zero. Le widget n'a AUCUN acces reseau : il rend ce que l'app lui a pousse la derniere fois qu'elle a tourne. Le snapshot portait deja `updatedAt` — rien ne le lisait. CoachQuickWidget programmait meme un reveil a minuit, avec le bon commentaire, mais relisait ensuite les memes chiffres sans regarder leur date : le rechargement avait lieu, la remise a zero non. - `CoachWidgetSnapshot.asOf(_:)` : vide ce qui decrit UNE journee (eau, cafes, kcal, routine, activite faite, score de forme) quand le snapshot date d'un autre jour. Conserve ce qui traverse les jours — objectifs et zones cardiaques. Les compteurs repartent a 0 et non a nil : au matin, « rien bu » est vrai, « je ne sais pas » ne l'est pas. - Les trois providers emettent une 2e entree datee de minuit. WidgetKit bascule seul, sans reveiller l'app. - `tomorrow` : la seance du lendemain, poussee par le pont web. Sans elle, vider la seance ferait annoncer JOUR OFF chaque nuit — l'agacement d'un incident precedent, repete tous les jours. ⚠️ Deuxieme occurrence trouvee en verifiant, et pire : CoachQuickSync.credit() ajoutait au total EXISTANT puis rehorodatait a maintenant. Sur un snapshot de la veille, 200 ml bus le matin donnaient « 2 200 ml » estampilles du jour — un total faux, que la peremption ne pouvait plus rattraper. ⚠️ NON COMPILE : pas de toolchain Swift ici. Build Xcode requis sur le Mac mini pour que le correctif arrive sur l'iPhone et la Watch.
This commit is contained in:
@@ -91,6 +91,15 @@ struct CoachWidgetSnapshot: Codable {
|
||||
/// côté natif (cf. l'en-tête de ce fichier).
|
||||
var zones: HeartRateZones? = nil
|
||||
|
||||
/// Séance planifiée de DEMAIN. N'est jamais affichée le jour même : elle
|
||||
/// sert au widget à passer minuit sans accès réseau.
|
||||
///
|
||||
/// ⚠️ Sans elle, `asOf(_:)` n'aurait d'autre choix que de vider la séance
|
||||
/// au changement de jour — et le widget annoncerait « JOUR OFF » chaque
|
||||
/// nuit jusqu'à ce que l'app soit rouverte, y compris la veille d'une
|
||||
/// sortie longue. Poussée par `widget-bridge.js` depuis `tomorrow_session`.
|
||||
var tomorrow: TodaySession? = nil
|
||||
|
||||
/// Séance planifiée du jour (nil = jour OFF / pas de séance).
|
||||
struct TodaySession: Codable {
|
||||
var sport: String // running, cycling, strength, mobility, hiking, rest…
|
||||
@@ -128,3 +137,64 @@ struct CoachWidgetSnapshot: Codable {
|
||||
var label: String? // "Prêt", "Récup", "Fatigue"…
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Péremption au changement de jour
|
||||
|
||||
extension CoachWidgetSnapshot {
|
||||
/// Le snapshot tel qu'il doit s'afficher le jour `day`.
|
||||
///
|
||||
/// ⚠️ **Le widget n'a aucun accès réseau.** Il rend ce que l'app lui a
|
||||
/// poussé la dernière fois qu'elle a tourné — et rien ne l'obligeait à
|
||||
/// vérifier de QUAND datait ce qu'il affichait. Passé minuit, l'hydratation,
|
||||
/// les calories, la routine et la séance de la veille restaient à l'écran,
|
||||
/// présentées comme celles du jour. Il fallait ouvrir l'app pour les voir
|
||||
/// retomber à zéro. Signalé le 2026-08-18.
|
||||
///
|
||||
/// `updatedAt` existait déjà : **personne ne le lisait**. C'est tout le
|
||||
/// correctif.
|
||||
///
|
||||
/// Ce qui est vidé est ce qui décrit UNE journée. Ce qui traverse les jours
|
||||
/// est conservé, et la distinction n'est pas cosmétique :
|
||||
///
|
||||
/// | conservé | pourquoi |
|
||||
/// |---|---|
|
||||
/// | `waterGoalMl`, `kcalGoal` | ce sont des objectifs, pas des mesures |
|
||||
/// | `zones` | version figée, valable des semaines |
|
||||
///
|
||||
/// Les compteurs repartent à **0 et non à `nil`** : au matin d'un jour
|
||||
/// neuf, « rien bu » est une information vraie, alors que `nil` signifierait
|
||||
/// « je ne sais pas ».
|
||||
func asOf(_ day: Date, calendar: Calendar = .current) -> CoachWidgetSnapshot {
|
||||
if calendar.isDate(updatedAt, inSameDayAs: day) { return self }
|
||||
|
||||
var s = self
|
||||
s.waterMlToday = 0
|
||||
s.coffeeCountToday = 0
|
||||
s.kcalToday = 0
|
||||
s.routine = nil
|
||||
// L'activité de la veille n'est pas celle du jour, et le score de forme
|
||||
// du jour n'est calculé qu'après la nuit : afficher celui d'hier comme
|
||||
// étant celui d'aujourd'hui serait faux, pas seulement périmé.
|
||||
s.doneToday = nil
|
||||
s.forme = nil
|
||||
|
||||
// La séance : c'est ici que `tomorrow` évite le « JOUR OFF » nocturne.
|
||||
// Elle ne vaut que pour le LENDEMAIN du snapshot — au-delà, on ne sait
|
||||
// plus, et un plan inventé serait pire qu'une case vide.
|
||||
let lendemain = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: updatedAt))
|
||||
if let lendemain, calendar.isDate(lendemain, inSameDayAs: day) {
|
||||
s.today = tomorrow
|
||||
} else {
|
||||
s.today = nil
|
||||
}
|
||||
s.tomorrow = nil
|
||||
return s
|
||||
}
|
||||
|
||||
/// Prochain minuit strictement après `date`. Sert aux trois widgets à
|
||||
/// programmer l'entrée qui fait basculer l'affichage sans réveiller l'app.
|
||||
static func nextMidnight(after date: Date, calendar: Calendar = .current) -> Date {
|
||||
calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: date))
|
||||
?? date.addingTimeInterval(86_400)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user