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.
Le widget affichait un total sans jauge, et c'était une décision documentée
et juste : « aucun objectif de boisson n'est fixé, et en inventer un
donnerait un pourcentage qui n'a pas de sens ». Une cible existe maintenant
côté serveur (`/api/drinks` renvoie `water_target_ml`), donc la jauge peut
apparaître — sans cible, on retombe exactement sur l'ancien comportement.
⚠️ Cette cible n'a **aucune source** : c'est un objectif personnel, pas une
recommandation médicale. D'où le libellé « objectif » et non « besoin » ou
« recommandé ».
⚠️ Les deux côtés du pont bougent ensemble : `widget-bridge.js` pose
`waterGoalMl`, `CoachWidgetBridge` la lit sous le même nom. Le widget
« JOUR OFF » a menti pendant des jours parce que le pont lisait une clé
absente de la réponse serveur.
Ajoute aussi un `widgetURL` vers /hydratation : un tap hors bouton ouvrait
l'app sur sa dernière page consultée, au hasard.
⚠️ **NON COMPILÉ** — pas de Mac ici. À vérifier au prochain build Xcode,
avec les zones cardiaques Watch et `getSnapshot` déjà en attente.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Décision de l'utilisateur : pas d'objectif de boisson, juste la consommation du
jour. Le code qui attendait un objectif pour afficher une barre est retiré,
champ de snapshot compris — un conditionnel qui ne se déclenchera jamais est une
fausse promesse, et le prochain lecteur y verrait une fonctionnalité en attente.
Le widget affiche « 1,2 L bue aujourd'hui ». Seule l'énergie garde sa jauge :
elle est la seule à avoir une cible fixée (1950 kcal).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux barres sous le titre : eau bue sur objectif, énergie consommée sur objectif
calorique. La barre des calories vire à l'orange au-dessus de la cible.
La jauge n'apparaît QUE si l'objectif correspondant existe. Aucun objectif
d'hydratation n'est défini dans les objectifs nutritionnels : tant qu'il vaut
nil, le widget affiche le volume bu sans barre. Une jauge sur un objectif
inventé donnerait un pourcentage qui ne veut rien dire — et il aurait l'air
d'un réglage validé.
Les trois champs du snapshot sont optionnels : un instantané écrit par une
version antérieure reste lisible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un widget se lit hors contexte, au milieu d'autres tuiles : sans titre,
« 0,3 L · 2 cafés » ne dit ni de quoi il s'agit, ni à quoi servent les boutons.
Le nom affiché dans la galerie de widgets est aligné sur ce titre — deux libellés
différents pour la même chose obligeraient à les rapprocher de tête.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les deux liens utilisaient un schéma custom `coachapp://` que rien ne route :
le projet passe par les Universal Links (entitlement associated-domains), comme
les widgets Séance du jour et Score de forme, qui pointent tous deux sur des URL
https. L'app s'ouvrait donc sur sa dernière page consultée — le score de sommeil
en l'occurrence.
Les liens passent en https://coach.hypnotruck.ch/meals?photo=1 et ?scan=1, et le
schéma custom que j'avais ajouté à l'Info.plist est retiré : il ne servait à
rien et aurait laissé une fausse piste.
J'avais écrit ces URL sans lire comment les widgets voisins s'y prenaient — même
faute que pour `todayISO`, supposer un mécanisme au lieu de le vérifier.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Boutons d'App Intent (iOS 17+) pour l'eau et le café : ils s'exécutent dans
l'extension, écrivent dans l'App Group et rafraîchissent la timeline. Aucun
appel réseau — une extension n'a ni session authentifiée ni droit de tenir une
requête.
Photo et code-barres OUVRENT l'application, par `coachapp://meals?photo=1` et
`?scan=1` : la caméra exige le premier plan, aucun widget ne peut y échapper.
Le widget économise les navigations, pas le lancement.
`CoachQuickLog` : file d'attente partagée app ↔ widget ↔ montre. Les saisies
partent au serveur à la prochaine ouverture de l'app, et sont retirées PAR
IDENTIFIANT — vider la file perdrait une saisie faite pendant la
synchronisation, sans laisser de trace.
Le widget affiche son propre total du jour : les boissons déjà connues du
serveur (nouveaux champs `waterMlToday` / `coffeeCountToday` du snapshot, tous
deux optionnels pour rester lisibles depuis une version antérieure) PLUS ce qui
attend dans la file. Sans cela, il afficherait un total périmé juste après une
saisie et l'utilisateur douterait de ce qu'il vient de faire. Ce qui n'est pas
encore parti est signalé par une flèche, pas masqué.
Un échec d'écriture de l'App Group remonte en erreur d'intent : afficher
« ajouté » sur une saisie perdue est pire que ne rien afficher.
Runbook Mac complété — Target Membership, enregistrement dans le WidgetBundle,
schéma d'URL, iOS 17 minimum, et le câblage de synchronisation restant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>