§ 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>
L'audit du 22/09 marquait cinq constats **[rapporté]** : issus de revues
automatisées, jamais reconfrontés au code, et il le disait lui-même — « les
confirmer avant d'agir ». Sylvain a autorisé la lecture, pas la modification :
aucune ligne de Swift ne bouge ici.
**Les cinq sont confirmés, aucun faux positif.** Trois lectures s'écartent
toutefois de ce que l'audit décrivait :
§ 1.2 est **plus grave**. L'audit ne voyait que le `try?` ; `pushToWatch` a
quatre sorties silencieuses sur sept lignes — WCSession non supportée, session
non activée, encodage échoué, envoi refusé. Et l'aggravant se vérifie :
`HeartRateZones` ne porte aucune date, alors que le backend sert une version
figée et datée (règle 9 du CLAUDE.md web). La date existe côté serveur et se
perd au transport. `asOf()` périme l'eau, les kcal, la routine et le score de
forme, mais pas les zones — délibérément, et c'est défendable pour une
péremption journalière ; ça laisse ouvert le cas d'une révision des bornes.
§ 2.3 est **plus nuancé**. Le code sait déjà que `finishWorkout()` rend nil sans
erreur quand la montre est verrouillée, et que la séance est alors bien écrite —
d'où `recentlySavedWorkout()`. Le défaut n'est donc pas « la séance est perdue »
mais que le double `try?` rend indiscernables deux situations opposées : montre
verrouillée (séance sauvée) et `endCollection` qui a levé (rien de sauvé). Les
deux tombent dans le même `else`.
§ 2.4 est **plus grave** aussi : `self.builder = nil` détruit la seule référence
au builder. La perte devient définitive dans l'instruction même qui l'ignore, et
le GPS continue de tourner après la fin apparente de la séance.
§ 1.3 et § 2.6 sont exacts tels que décrits. Pour § 2.6, un fait qui manquait :
le même fichier utilise `NSLock` pour ce motif à quatre autres endroits — c'est
une omission ponctuelle, pas une convention absente.
⚠️ Les numéros de ligne de l'audit ne valent plus : les correctifs du 22/09 au
soir ont décalé `WorkoutManager.swift` et `CoachHealthRoute.swift`. Le document
porte les références à jour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>