Le build de ce soir tient sur une page, et l'import ne peut plus surprendre
Sylvain build ce soir en rentrant : docs/watch-plan-origin-runbook-mac.md porte les gestes — les deux fichiers touchés sont déjà dans la target App, donc aucun Target Membership ni framework à ajouter, et WorkoutKit était déjà lié par CoachWorkoutKit.swift. Le runbook dit aussi quoi vérifier sans courir (le message d'envoi doit NOMMER la séance programmée, ou dire l'autorisation manquante) et comment lire l'origine de la séance après coup. `import WorkoutKit` passe sous `#if canImport`, comme dans CoachWorkoutKit, et reportPlanOrigin avec lui : ce code n'ayant jamais vu de compilateur, autant que l'absence du framework le rende inerte plutôt qu'incompilable. Les trois erreurs les plus probables sont listées avec leur cause dans le runbook, pour ne pas avoir à rouvrir la doc DocC devant Xcode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
docs/watch-plan-origin-runbook-mac.md
Normal file
83
docs/watch-plan-origin-runbook-mac.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Runbook Mac — le build qui rend l'envoi vérifiable
|
||||
|
||||
**Écrit le 03/09/2026, à suivre sur le Mac mini.** Compte 20 minutes, dont la
|
||||
moitié d'attente. Ce build ne change rien à ce que la montre reçoit : il rend
|
||||
l'app capable de **dire** ce qu'elle a fait, ce qu'elle ne savait pas faire.
|
||||
|
||||
Le dossier complet — deux épisodes de « la montre ne dit plus rien », leur
|
||||
chronologie et ce qu'elle élimine — est dans `coach_sportif`,
|
||||
[`docs/GUIDE-MONTRE.md`](../../coach_sportif/docs/GUIDE-MONTRE.md) §5quater et
|
||||
§5quinquies. Ici : les gestes.
|
||||
|
||||
## Ce qui change
|
||||
|
||||
Deux fichiers, **déjà déclarés dans la target App** — donc rien à ajouter dans
|
||||
Xcode, pas de Target Membership à cocher, pas de nouveau framework à lier
|
||||
(WorkoutKit était déjà importé par `CoachWorkoutKit.swift`) :
|
||||
|
||||
- `ios/App/App/CoachWorkoutKit.swift`
|
||||
- `ios/App/App/CoachWorkoutObserver.swift`
|
||||
|
||||
Branche : `feat/watch-outdoor`. Le côté serveur est **déjà en prod**, il attend
|
||||
juste que l'app l'appelle.
|
||||
|
||||
## 1. Récupérer et compiler
|
||||
|
||||
```bash
|
||||
cd ~/coach-ios # ou l'emplacement du clone sur le Mac
|
||||
git checkout feat/watch-outdoor
|
||||
git pull
|
||||
npx cap sync ios
|
||||
open ios/App/App.xcworkspace
|
||||
```
|
||||
|
||||
Dans Xcode : cible **App**, ton iPhone connecté, ▶︎. Rien d'autre à toucher.
|
||||
|
||||
⚠️ **Ce code n'a jamais été compilé** — WorkoutKit et HealthKit n'existent pas
|
||||
dans Swift pour Linux, et `tests-linux/` ne couvre que ce qui n'importe que
|
||||
Foundation. Les signatures Apple ont été vérifiées une par une dans la doc DocC
|
||||
avant d'être écrites, mais si quelque chose casse, c'est là que ça cassera :
|
||||
|
||||
| symptôme | où regarder |
|
||||
|---|---|
|
||||
| `value of type 'HKWorkout' has no member 'workoutPlan'` | l'extension vient de WorkoutKit : vérifier que `import WorkoutKit` est bien pris (il est sous `#if canImport`) |
|
||||
| erreur sur `case .custom(let custom)` | `WorkoutPlan.workout` est l'enum `WorkoutPlan.Workout` — cas `custom`, `goal`, `pacer`, `swimBikeRun` |
|
||||
| erreur de conversion sur `sw.date` | c'est un `DateComponents`, pas une `Date` — d'où le `Calendar.current.date(from:)` |
|
||||
|
||||
## 2. Vérifier sans courir (2 minutes, dans le canapé)
|
||||
|
||||
1. Ouvre **Programme**, la séance du jour, et appuie sur l'envoi Apple Watch.
|
||||
Le message doit maintenant **nommer la séance programmée** :
|
||||
« Programmée sur la montre : CDC · … ».
|
||||
- S'il dit « la montre ne la liste pas encore » : le scheduler n'a pas encore
|
||||
publié — regarde *Exercice → Programmées* au poignet avant de conclure.
|
||||
- S'il dit « Autorisation « Séances programmées » absente » : **c'est la
|
||||
réponse au silence**. Réglages iPhone → coach → autoriser, puis renvoyer.
|
||||
C'est exactement ce qu'une réinstallation de l'app remet à zéro, et
|
||||
l'ancienne version affichait « Envoyé » quand même.
|
||||
|
||||
2. Console Xcode (facultatif), filtre `CoachWorkoutKit` : `schedule OK`,
|
||||
`authorizationState`.
|
||||
|
||||
## 3. Après ta prochaine séance
|
||||
|
||||
Le plugin remonte tout seul, en fin de séance, si elle venait de la séance
|
||||
programmée ou d'un départ à la main. Pour le lire :
|
||||
|
||||
```bash
|
||||
# sur nexus, là où vivent les données (node n'y est pas installé)
|
||||
venv/bin/python3 tools/watch_alert_check.py <AAAA-MM-JJ> --dump /tmp/wac.json
|
||||
# puis en local, là où node existe
|
||||
python3 tools/watch_alert_check.py --from /tmp/wac.json
|
||||
```
|
||||
|
||||
Trois réponses possibles, et une seule était disponible jusqu'ici :
|
||||
|
||||
- **« Séance lancée depuis la séance programmée »** → les alertes comptées
|
||||
étaient bien dues. Si tu n'as rien entendu, le défaut est ailleurs que dans
|
||||
l'envoi et le lancement.
|
||||
- **« Séance lancée À LA MAIN »** → aucune alerte n'était possible, le silence
|
||||
est normal. C'est le cas que rien ne savait détecter, et qui devient plausible
|
||||
depuis que la Polar est la montre de sport.
|
||||
- **« Origine inconnue »** → séance antérieure au build, ou lecture en échec.
|
||||
Ce n'est **pas** un « non » : l'app préfère se taire qu'affirmer à tort.
|
||||
@@ -27,7 +27,11 @@ import HealthKit
|
||||
import UserNotifications
|
||||
// Pour l'extension `HKWorkout.workoutPlan` (iOS 17+), qui rend la composition
|
||||
// du plan dont une séance est issue — ou nil si elle a été lancée à la main.
|
||||
// Même garde que `CoachWorkoutKit.swift` : sur un SDK sans WorkoutKit, le
|
||||
// fichier doit continuer à compiler, quitte à ne rien remonter.
|
||||
#if canImport(WorkoutKit)
|
||||
import WorkoutKit
|
||||
#endif
|
||||
|
||||
@objc(CoachWorkoutObserverPlugin)
|
||||
public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
@@ -392,6 +396,7 @@ public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
/// Même parti pris que `reportToRoutineIfStrength` : on n'envoie que des
|
||||
/// faits bruts, l'interprétation vit côté serveur donc sans rebuild iOS.
|
||||
private func reportPlanOrigin(_ workout: HKWorkout) {
|
||||
#if canImport(WorkoutKit)
|
||||
guard #available(iOS 17.0, *) else { return }
|
||||
Task {
|
||||
var fromPlan = false
|
||||
@@ -431,6 +436,7 @@ public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func scheduleNotification(for workout: HKWorkout) {
|
||||
|
||||
Reference in New Issue
Block a user