Nouvelle méthode CoachHealthRoutePlugin.getCharacteristics qui lit
les caractéristiques read-only depuis HealthKit :
- dateOfBirth (ISO8601 date)
- biologicalSex (male/female/other)
- bloodType (A+/A-/B+/B-/AB+/AB-/O+/O-)
Auth étendue : on demande aussi read sur dateOfBirth, biologicalSex
et bloodType en plus de workoutType + workoutRoute.
Utilisé côté web par la page Profil pour auto-remplir les champs
sans saisie manuelle (cf. /api/profile/healthkit-suggestions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plugin accepte maintenant un champ optionnel `label` sur chaque step
(work/rest ou blockSteps[]). Si fourni, override l'affichage natif
Apple "Travail"/"Récupération" sur l'Apple Watch par le label custom
(ex. "Course"/"Marche").
API étendue :
- work: { duration_min, hr_zone, label? }
- rest: { duration_min, hr_zone, label? }
- blockSteps: [{ type, duration_min, hr_zone, label? }]
Aucune régression : si label absent ou vide, comportement inchangé.
Nécessite rebuild iOS pour prendre effet (Mac mini en attente macOS 26).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Capacitor 8 n'auto-découvre PAS les plugins in-app (définis dans le
target App, pas en npm package). Faut les passer explicitement à
bridge.registerPluginInstance() dans MainViewController.capacitorDidLoad().
Le commentaire du fichier expliquait déjà la règle, mais on l'avait
oubliée pour CoachHealthRoute → window.Capacitor.Plugins.CoachHealthRoute
restait undefined.
Hypothèse : HKHealthStore() à l'init de classe peut causer un échec
silencieux qui empêche Capacitor d'enregistrer le plugin. Lazy init.
Aussi : public override func load() avec NSLog pour qu'on voie dans
les logs natifs si Capacitor charge bien la classe au boot.
@capgo/capacitor-health n'expose pas les routes GPS des workouts. Ce
plugin Swift custom appelle HKSampleQuery + HKWorkoutRouteQuery pour
récupérer la liste des CLLocation associées à un workout (par UUID).
API JS :
CoachHealthRoute.getRoute({workoutUUID})
→ {available: bool, route: [{latitude, longitude, altitude, timestamp, speed}], count}
Format payload aligné sur HAE pour réutiliser le parser backend
existant (tools/health_parser.py).
Ajoute le plugin natif capacitor-native-settings@8.1.0 qui permet
d'ouvrir directement la page Localisation dans Réglages iPhone via
UIApplication.shared.open() (API officielle Apple).
Cas d'usage : depuis /settings → Météo → bouton 'Gérer la permission
iOS' → ouvre Réglages → Confidentialité → Localisation → Coach
Hypnotruck. C'est ce qu'utilisent Strava, Komoot, Uber, etc.
Sync ios/App/CapApp-SPM/Package.swift via npx cap sync ios — le
plugin est maintenant inclus dans le build iOS.
⚠️ Requiert rebuild TestFlight v1.2 :
cd /Users/sylvainbettinelli/Documents/coach-ios/ios/App
pod install (ou via Xcode si SPM)
Xcode → Product → Archive → upload TestFlight
Côté webapp, le bouton tombera en fallback (instructions texte) tant
que la nouvelle build TestFlight n'est pas installée. Le code détecte
window.Capacitor.Plugins.NativeSettings et bascule selon dispo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Préparation upload TestFlight v1.1 avec :
- 12 phases d'évolution depuis v1.0 du 7 mai (bilan IA hebdo, /calendar
refonte vue semaine, /workouts page, /profile, plan additions custom,
WorkoutKit Apple Watch 6 commits, builder chips blocs interactif)
- MARKETING_VERSION 1.0 → 1.1 (visible côté user TestFlight)
- CURRENT_PROJECT_VERSION 1 → 2 (build number, doit être strictement
croissant côté Apple)
- ITSAppUsesNonExemptEncryption=false : évite le questionnaire crypto
au moment de l'upload (l'app n'utilise pas d'algorithmes hors crypto
iOS standard)
Avant : work + rest fixes (2 steps) avec repeats sur le couple.
Maintenant : payload optionnel blockSteps = [{type, duration_min, hr_zone},
...] qui permet N steps custom dans le bloc à répéter. Backward compat :
si pas de blockSteps, lit work + rest comme avant.
Refactor de sendCustomWorkout : prend stepsSpec: [(min, hrZone, isWork)]
au lieu de work*/rest* params séparés. Génère N IntervalSteps depuis le
spec, met dans 1 IntervalBlock avec iterations=repeats.
Le compile error venait de let scheduled = ... .id ... .date qui suppose un
ScheduledWorkoutPlan avec ces propriétés. Probablement le type retourné par
schedule() dans le SDK actuel a une signature différente (ou retourne Void).
Simplification : ne pas capturer le résultat, juste try await + log statique.
preview() ne compile pas dans le SDK actuel (iOS 17.4+ pas dispo).
On revient sur schedule() qui marche, avec 2 améliorations :
- Schedule à Date()+60s au lieu de Date() pour éviter qu'iOS ignore
un workout dont la date passe au moment du traitement (race condition).
- NSLog du résultat (id + date) pour diagnostic Console.app si l'user
ne voit toujours rien sur la Watch malgré schedule succès.
Limitation : avec schedule(), le workout apparaît dans la liste 'Programmées'
au TOP de l'app Workout sur l'Apple Watch (avec icône ⏰), PAS dans la
sous-section 'Custom Workouts'. C'est l'API qu'Apple expose actuellement
pour pousser des CustomWorkout via WorkoutKit — la sous-section Custom
Workouts ne semble pas writable depuis 3rd-party apps directement.
schedule() (iOS 17.0+) que j'utilisais met le workout dans la Schedule (le
calendrier Fitness iOS) → utile pour 'demain à 14h je fais cette séance' mais
pas pour 'mets cette séance dans ma bibliothèque, je la lancerai quand'.
preview() (iOS 17.4+) ouvre une sheet système 'Ajouter à mes workouts' qui
ajoute la séance dans **Custom Workouts** de l'Apple Watch — accessible via
Workout app → Course → Custom Workouts → [séance].
Ajouts :
- requestAuthorization() en amont (idempotent, déclenche dialog iOS la 1re fois)
- if #available(iOS 17.4) → preview(plan)
else → schedule(plan, at: now) en fallback (rare car iOS 17.0-17.3)
- NSLog('[CoachWorkoutKit] ...') pour visibilité Console.app au cas où
L'erreur compile précédente sur preview() venait probablement des autres
problèmes API (WorkoutAlertEnumeration, IntervalStep init), maintenant tous
réglés. preview() devrait compiler proprement avec ce build.
Capacitor 8 requiert que les plugins implémentent CAPBridgedPlugin pour
déclarer explicitement identifier + jsName + pluginMethods. Sans ça,
registerPluginInstance() registre l'instance mais le bridge ne sait pas
quelles méthodes exposer → Capacitor.Plugins.CoachWorkoutKit reste
undefined côté WebView.
Avec ce fix : Capacitor.Plugins.CoachWorkoutKit dispo en JS, avec
méthodes isAvailable() et sendInterval() callable.
Capacitor 8 n'auto-découvre pas les plugins in-app Swift (qui ne sont pas
des npm packages). Les plugins définis dans le target App doivent être
enregistrés manuellement via une sous-classe de CAPBridgeViewController
qui override capacitorDidLoad().
Ajouts :
- ios/App/App/MainViewController.swift : sous-classe qui registers
CoachWorkoutKitPlugin() dans capacitorDidLoad()
- Main.storyboard : customClass pointe sur MainViewController (module
'App' target) au lieu de CAPBridgeViewController (module Capacitor)
- project.pbxproj : 4 entrées pour MainViewController.swift (BuildFile,
FileReference, group, Sources phase)
Après rebuild, Capacitor.Plugins.CoachWorkoutKit sera dispo en JS.
L'API WorkoutScheduler.shared.preview(plan) n'existe pas (ou pas avec cette
signature) dans le SDK iOS de l'utilisateur — compile error 'Value of type
WorkoutScheduler has no...'.
On garde uniquement WorkoutScheduler.shared.schedule(plan, at: now), qui
est l'API stable depuis iOS 17.0. Effet : le workout devient immédiatement
disponible dans Apple Watch → Exercice → Workouts personnalisés.
Le 1er schedule déclenche automatiquement la dialog d'autorisation iOS
'Coach Hypnotruck souhaite ajouter des workouts à votre Apple Watch' si pas
encore accordée. Plus besoin du authorizationState query manuel.
Trade-off : pas de sheet de prévisualisation système avant ajout (l'user ne
voit pas la structure dans une UI native avant que le workout soit ajouté).
Mais c'est le bon compromis pour avoir un build qui marche. On pourra
réintroduire preview() plus tard si on bump le deployment target.
Compile errors :
- WorkoutAlertEnumeration n'existe pas → c'est WorkoutAlert (et il faut
passer l'alert via WorkoutStep.alert, pas en param d'IntervalStep)
- IntervalStep init est .init(_ purpose:, step: WorkoutStep) — pas de goal
ni alert direct, faut wrapper dans WorkoutStep d'abord
- WorkoutScheduler.shared.preview(plan) ajouté en iOS 17.4+ → wrap dans
if #available(17.4) avec fallback schedule() pour 17.0-17.3
Ajouts :
- requestAuthorization() avant preview/schedule (idempotent si déjà accordée)
- HeartRateZoneAlert appliqué sur work + rest steps via .alert (style mutable)
Plugin Capacitor Swift in-app (ios/App/App/CoachWorkoutKit.swift) qui utilise
le framework Apple WorkoutKit (iOS 17+ / watchOS 10+) pour construire un
CustomWorkout structuré (warmup + IntervalBlock work/rest répété + cooldown)
et présenter la sheet système iOS qui propose à l'user d'ajouter le workout
à ses workouts personnalisés Apple Watch.
API JS (côté WebView) :
Capacitor.Plugins.CoachWorkoutKit.isAvailable()
Capacitor.Plugins.CoachWorkoutKit.sendInterval({
activity, displayName, warmupMin, work, rest, repeats, cooldownMin
})
Activity supportées : running / cycling / walking / hiking
HR zones (1-5) optionnelles via HeartRateZoneAlert sur work + rest steps.
Workflow attendu user :
1. coach.hypnotruck.ch → /settings ou bouton 'Envoyer Apple Watch' depuis /calendar
2. JS appelle WK.sendInterval(...)
3. Plugin Swift construit CustomWorkout + IntervalBlock + alerts
4. WorkoutScheduler.shared.preview(plan) ouvre la sheet iOS native
5. User tape 'Ajouter aux workouts' → workout dispo dans Apple Watch
→ Exercice → Course → Workouts personnalisés → [displayName]
Build à faire sur le Mac (cap sync ios + Xcode build → install iPhone).
Phase 1 = MVP plugin + bouton test sur /settings de coach.hypnotruck.ch.
Phase 2 = bouton 'Envoyer Apple Watch' sur card du jour /calendar (convertit
la séance prévue en CustomWorkout).
Phase 3 = bibliothèque templates intervalles (10x400m, fartlek, etc.).
Project.pbxproj : ajouté 4 entrées (PBXBuildFile + PBXFileReference + group +
Sources phase) avec IDs D1BD5B1990CF3F2B9C5D000{1,2}.
@capacitor/assets v3.0.5 → 87 assets Android générés à partir des sources
1024x1024 (icon) et 2732x2732 (splash) déjà présentes :
- mipmap-{m,h,xh,xxh,xxxh}dpi : ic_launcher.png + _round + _foreground
(foreground/background layers pour adaptive icons Android 8+)
- drawable-{land,port}-{l,m,h,xh,xxh,xxxh}dpi : splash.png
- mipmap-anydpi-v26 : ic_launcher.xml et ic_launcher_round.xml
(XML adaptive icon refs vers les layers PNG)
iOS pbxproj : strip leading zero `0920` → `920` (effet de bord du tool, pas
de comportement Xcode modifié — juste format normalisé).
Reste assets marketing pour Play Console (Feature Graphic 1024x500,
screenshots phone) — à générer au moment du upload, pas dans le repo.
Évite le redirect 303 du backend /login qui casse WKWebView (WebKitErrorDomain
code 102, frame load interrupted by policy change). Le cookie est posé
directement dans HTTPCookieStorage.shared avec les bons attributs (domain
coach.hypnotruck.ch, path /, secure, expire 1 an).
Token lu depuis CoachAuth.kCoachWebToken défini dans CoachAuth.swift
(gitignored). Template fourni dans CoachAuth.swift.example.
Setup côté Mac mini :
1. cp ios/App/App/CoachAuth.swift.example ios/App/App/CoachAuth.swift
2. Éditer pour mettre la vraie valeur du token
3. Dans Xcode, Right-click App folder → Add Files → sélectionner CoachAuth.swift