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>
This commit is contained in:
@@ -1,6 +1,18 @@
|
||||
# Diagnostic natif — les cinq constats ouverts de l'audit, vérifiés
|
||||
|
||||
**24/09/2026. Lecture seule : aucune ligne de Swift n'a été modifiée.**
|
||||
> ## ⚠️ § 1.2 A ÉTÉ CORRIGÉ le 24/09, après ce diagnostic
|
||||
>
|
||||
> Sylvain a levé le gel sur ce point précis (« tu attends quoi ? »). Le correctif
|
||||
> est décrit en fin de document, § « Ce qui a été corrigé ». **Les quatre autres
|
||||
> constats restent ouverts, et le gel tient pour eux.**
|
||||
>
|
||||
> ⛔ **Rien n'est réel tant que le build Mac n'a pas tourné.** Syntaxe vérifiée
|
||||
> (`swiftc -parse`) et 91 tests du banc Linux verts — mais ni `CoachWidgetBridge`
|
||||
> ni `HeartRateZones` ne se compilent hors Xcode : Capacitor, WatchConnectivity
|
||||
> et SwiftUI sont absents de Swift pour Linux.
|
||||
|
||||
**24/09/2026. Diagnostic mené en lecture seule** — aucune ligne de Swift n'avait
|
||||
été modifiée à sa rédaction.
|
||||
Demandé par Sylvain après l'audit du 22/09, dont cinq constats étaient marqués
|
||||
**[rapporté]** — issus de revues automatisées, jamais reconfrontés au code.
|
||||
L'audit lui-même le disait : « les confirmer avant d'agir ».
|
||||
@@ -237,3 +249,67 @@ dont la conséquence est un **chiffre faux affiché pendant l'effort**, chez
|
||||
quelqu'un qui règle son intensité dessus. Les quatre autres perdent de la donnée
|
||||
ou de la batterie — c'est réparable après coup. Une borne de zone périmée au
|
||||
poignet ne se rattrape pas : elle a déjà servi à décider.
|
||||
|
||||
---
|
||||
|
||||
## Ce qui a été corrigé (§ 1.2, le 24/09 après-midi)
|
||||
|
||||
Trois couches, parce que le défaut en traversait trois.
|
||||
|
||||
### 1. L'envoi ne se tait plus — `CoachWidgetBridge.swift`
|
||||
|
||||
`pushToWatch` rend désormais la **raison** de son échec, ou `nil` s'il a réussi,
|
||||
et journalise dans `Logger(subsystem: "ch.hypnotruck.coach", category: "widget")`.
|
||||
Les quatre sorties muettes deviennent **six sorties nommé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 en gras viennent de `CoachRoutineBridge` : sans elles, l'absence
|
||||
de montre se confondait avec un échec d'envoi.
|
||||
|
||||
Le log de succès nomme ce qui compte : `zones=oui depuis=2026-08-12`. Savoir
|
||||
quelles bornes sont au poignet, et depuis quand, 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`) et non un `reject` — `ok`
|
||||
est conservé, aucun appelant JS existant ne casse.
|
||||
|
||||
### 2. La date de version voyage — `HeartRateZones.swift` + `widget-bridge.js`
|
||||
|
||||
`zonesSince` (date de la version figée) et `zonesFrom` (`"frozen"` ou `"live"`)
|
||||
rejoignent le modèle, en optionnels — un snapshot écrit avant reste décodable.
|
||||
|
||||
**Le serveur les servait déjà** : `/api/cardiac-zones` rend `zones_since` et
|
||||
`zones_from` depuis `hr_zones_history.effective()`. Rien à changer côté backend ;
|
||||
c'est `widget-bridge.js` qui ne les faisait pas monter dans le snapshot. Deux
|
||||
lignes.
|
||||
|
||||
La montre peut donc maintenant distinguer des bornes **validées** de bornes
|
||||
**provisoires**, et dire leur âge. Ce qu'elle en affiche reste à décider — c'est
|
||||
de l'UI watchOS, hors de ce correctif.
|
||||
|
||||
### 3. Le garde-fou — `tests-linux/`
|
||||
|
||||
Trois tests dans `DayRolloverTests` : la date survit au passage de minuit
|
||||
(`asOf`), elle survit à l'aller-retour JSON (ce qui n'est pas encodé n'arrive
|
||||
pas au poignet), 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. La doublure `HeartRateZonesStub.swift` est tenue à l'identique.
|
||||
|
||||
91 tests verts (88 avant).
|
||||
|
||||
### Ce que ce correctif ne fait pas
|
||||
|
||||
- **Il ne prouve pas que le push fonctionne.** Il rend son échec *visible*. Si
|
||||
l'envoi échouait pour une raison qu'on ignore, on le saura au prochain build —
|
||||
c'était impossible jusqu'ici.
|
||||
- **Il ne périme pas les zones.** Après une révision, la montre affichera
|
||||
toujours les anciennes bornes tant qu'un push n'a pas réussi ; elle saura
|
||||
seulement les dater. Décider qu'une borne de plus de N jours n'est plus
|
||||
affichable est un choix produit, pas une correction de défaut.
|
||||
- **Il ne touche pas au flux course à pied** (WorkoutKit, live workout FC, app
|
||||
Exercice), conformément à la consigne du 22/09.
|
||||
- **Il n'a pas été compilé.** Le geste qui vaut preuve, 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.
|
||||
|
||||
Reference in New Issue
Block a user