Commit Graph

3 Commits

Author SHA1 Message Date
Sylvain Bettinelli
6aa7265147 Les macros arrivent au poignet — affichage, pas saisie
Sylvain, après avoir reçu le widget Hydratation : « il manque juste le suivi
des repas calories et lipides etc ».

⚠️ Sa demande est plus fine que ce que j'avais écarté. J'avais dit non à la
SAISIE des repas sur la montre — pas de caméra, pas de scanner, pas de saisie
de texte : un bouton n'aurait fait que renvoyer vers l'iPhone. Mais VOIR où
l'on en est n'a aucune de ces limites.

Ce qui manquait, et où
-----------------------
Le snapshot portait `kcalToday`/`kcalGoal` et rien d'autre. Les macros
existaient pourtant côté serveur, agrégées et servies :

  /api/meals            -> kcal, protein_g, carbs_g, fat_g, fiber_g
  /api/nutrition/goals  -> cibles {target, min, max} pour chacune

Il manquait uniquement le transport. Trois maillons ajoutés : `widget-bridge.js`
les pousse, `CoachWidgetSnapshot` les porte, `CoachWatchNutritionWidget` les
affiche.

⛔ Rien n'est calculé côté natif. Les totaux viennent du journal, les cibles de
`nutrition_goals.py`. Le seul calcul est un reste (cible − consommé), la même
soustraction que /meals affiche déjà.

Le piège évité, parce qu'il est documenté dans ce fichier
---------------------------------------------------------
`asOf(_:)` remet les totaux du jour à zéro quand le snapshot date d'hier. Les
macros y sont ajoutées : les oublier aurait affiché les grammes de la veille
sous la date du jour — le défaut même que cette fonction existe pour empêcher,
et qui avait survécu un temps sur les totaux de boisson.

⚠️ Les CIBLES ne se périment pas, elles : une cible n'est pas une mesure. Sans
ça, la jauge disparaîtrait chaque nuit jusqu'à la prochaine ouverture de l'app.
Deux tests Linux le verrouillent (`testMacrosResetLikeEnergy`,
`testMacroGoalsSurviveTheNight`) — 93 au vert.

⚠️ Une cible de type « min » (les protéines) se lit comme un plancher atteint,
pas comme un plafond à ne pas dépasser. `widget-bridge.js` ne pousse que
`target` ; le widget n'en sait pas plus et ne prétend pas le contraire.

Le widget compile-t-il ? Inconnu, comme les précédents : WidgetKit n'existe pas
sur Linux. Vérifié en revanche que chaque champ appelé existe, et qu'aucun
symbole n'est déclaré deux fois.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 07:46:32 +00:00
Sylvain Bettinelli
7e7be4a1d1 Les bornes cardiaques partaient vers la montre sans que rien ne dise si elles arrivaient
§ 1.2 de l'audit, vérifié le matin même puis corrigé sur demande explicite de
Sylvain — le gel du 22/09 est levé pour ce point seul, les quatre autres
constats restent intouchés.

Pourquoi celui-là d'abord : c'est le seul dont la conséquence est un chiffre
faux affiché PENDANT l'effort, chez quelqu'un qui règle son intensité dessus.
Les autres perdent de la donnée ou de la batterie, ça se rattrape. Une borne de
zone périmée au poignet a déjà servi à décider.

**L'envoi ne se tait plus.** `pushToWatch` avait quatre sorties silencieuses sur
sept lignes ; il en a maintenant six, toutes nommées et journalisées —
WatchConnectivity non supportée, session non activée, aucune montre appairée,
app Watch non installée, encodage impossible, envoi refusé. Les deux gardes du
milieu viennent de `CoachRoutineBridge`, qui les avait déjà : sans elles,
l'absence de montre se confond avec un échec d'envoi. Le log de succès nomme ce
qui compte — `zones=oui depuis=2026-08-12` — pour savoir quelles bornes sont au
poignet sans ouvrir la montre.

⚠️ Un échec ne fait PAS échouer `setSnapshot`, contrairement à
`CoachRoutineBridge` où l'envoi EST l'opération. Ici l'App Group est déjà écrit
et les widgets iPhone rafraîchis : une montre verrouillée n'invalide rien. D'où
un `resolve` enrichi (`watchPushed`, `watchReason`) plutôt qu'un `reject`, et
`ok` conservé — aucun appelant JS ne casse.

**La date de version voyage enfin.** `HeartRateZones` ne portait aucune date,
alors que le backend sert une version figée et datée (règle 9 du CLAUDE.md web) :
elle se perdait au transport, et après une révision rien au poignet ne
distinguait les nouvelles bornes des anciennes. `zonesSince` et `zonesFrom`
rejoignent le modèle en optionnels. Le serveur les servait déjà — c'est le JS du
dépôt web qui ne les faisait pas monter.

Trois tests au banc Linux : la date survit à `asOf()`, elle survit à
l'aller-retour JSON (ce qui n'est pas encodé n'arrive jamais), et un snapshot
antérieur à ces champs se décode toujours — un décodage qui lèverait laisserait
la montre sans zones du tout, pire que des zones sans date. 91 verts (88 avant).

⛔ NON COMPILÉ. Syntaxe vérifiée (`swiftc -parse`), mais Capacitor,
WatchConnectivity et SwiftUI n'existent pas sur Linux. Au prochain build :
ouvrir la Console sur la catégorie `widget` et vérifier qu'une ligne
« snapshot poussé vers la Watch · zones=oui depuis=… » apparaît au lancement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 08:08:02 +00:00
Sylvain Bettinelli
1d2d7a0e18 Une toolchain Swift sur Linux, pour ne plus ecrire de dates a l'aveugle
J'ai ecrit `asOf(_:)` hier sans pouvoir le compiler. Swift 6.3 est desormais
installe sur la machine de dev (Debian 12 x86_64) et `tests-linux/` compile
et teste la logique PURE des widgets.

10 tests, dont 5 verifies rouges en neutralisant la peremption. Ils couvrent
la remise a zero, la conservation des objectifs et des zones, le report de la
seance du lendemain, et le passage a l'heure d'hiver du 25 octobre — ou un
`+86400` naif ferait basculer le widget une heure trop tot, la veille au soir.

Les sources du paquet sont des liens symboliques vers les vrais fichiers : le
test porte sur le code livre, pas sur une copie.

⚠️ Ne remplace PAS le build Xcode et ne le remplacera jamais : WidgetKit,
SwiftUI, HealthKit et WatchKit sont absents de Swift pour Linux. Ni l'app, ni
les widgets, ni les vues ne se compilent ici.
2026-08-18 07:42:59 +00:00