Deux épisodes de « la montre ne dit plus rien » ont buté sur la même absence : rien, côté app, ne distinguait « iOS a accepté l'appel » de « la séance est au poignet ». Détail du dossier et chronologie dans coach_sportif, docs/GUIDE-MONTRE.md §5quater et §5quinquies. CoachWorkoutKit : - requestAuthorization() n'avale plus son échec. Il était consigné dans un NSLog et l'envoi continuait : une réinstallation de l'app remet l'autorisation à notDetermined — celle du 31/08, chantier CoachPolarBLE, tombe dans la fenêtre du silence — et l'écran affichait « Envoyé » alors que rien ne partait. - authorizationState est lu et rendu au JS, par isAvailable() comme par la nouvelle méthode scheduledWorkouts(). isAvailable() ne répondait jusqu'ici qu'« iOS 17+ » : une autorisation révoquée rendait la même réponse. - schedule() est suivi d'une relecture de WorkoutScheduler.scheduledWorkouts, et sendInterval rend ce qu'elle liste (nom, date, blocs, itérations). Une liste vide n'est PAS traitée comme un échec : le scheduler publie de façon asynchrone et Apple ne garantit aucun délai — inventer une erreur à chaque envoi coûterait plus cher que le silence qu'on cherche. CoachWorkoutObserver : à chaque fin de séance, HKWorkout.workoutPlan dit si elle vient d'une séance programmée. Le fait part vers /api/workout/plan-origin, sur le modèle de reportToRoutineIfStrength — faits bruts ici, interprétation côté serveur, donc ajustable sans rebuild. Une lecture en échec n'envoie rien plutôt qu'un faux « lancée à la main » : côté serveur, l'absence s'affiche « inconnu ». ⚠️ Rien de tout cela ne se compile ici : WorkoutKit et HealthKit sont absents de Swift pour Linux, et tests-linux ne couvre que ce qui n'importe que Foundation. À builder sur le Mac mini. Les signatures ont été vérifiées une par une dans la doc DocC — authorizationState (get async, 4 cas), scheduledWorkouts, ScheduledWorkoutPlan.date en DateComponents, WorkoutPlan.workout, CustomWorkout.displayName optionnel, HKWorkout.workoutPlan (get async throws). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Coach Hypnotruck — Mobile (iOS + Android)
Wrapper natif mobile de coach.hypnotruck.ch basé sur Capacitor 8.
Le repo s'appelle historiquement
coach-ios; renommécoach-mobileà partir de l'ajout de la plateforme Android (2026-05-07). Le filenamecoach-ios.local.jsonest conservé pour ne pas casser les références dans le code Swift natif.
Architecture
- Backend : FastAPI + Jinja2 hébergé sur le VPS, accessible via
https://coach.hypnotruck.ch - Coque iOS : Capacitor 8 charge l'URL distante dans un WKWebView
- Coque Android : Capacitor 8 charge l'URL distante dans une WebView (Android 8.0+, API 26+ — requis par Health Connect)
- Plugins natifs : push notifications, local notifications, géolocalisation, HealthKit (iOS) / Health Connect (Android) via
@capgo/capacitor-health, splash, status bar, haptics
Le code web n'est pas embarqué — l'app pointe vers la prod via server.url dans capacitor.config.ts. Avantage : déploiement web = mise à jour instantanée de l'app sans review Apple/Google.
Bundle ID & nom
- Bundle ID :
ch.hypnotruck.coach - App Store name :
Coach Hypnotruck - Display name :
Coach Hypnotruck
Prérequis (sur le Mac mini)
- macOS 13+ (Ventura ou plus récent)
- Xcode 15+ (App Store, ~10 Go)
- Command Line Tools :
xcode-select --install - Homebrew +
brew install node cocoapods - Node 20 LTS (compatible avec Cap 8)
- Compte Apple Developer Program actif (99 USD/an)
- Apple ID
dev@hypnotruck.chconnecté dans Xcode → Settings → Accounts
Workflow build
1. Cloner le repo sur le Mac mini
git clone https://git.nexus.hypnotruck.ch/sylvain/coach-ios.git
cd coach-ios
npm install
1bis. Configurer le token d'auth (auto-login)
Le repo ne contient pas le token COACH_WEB_TOKEN (gitignored). Crée le fichier sur le Mac :
cp coach-ios.local.json.example coach-ios.local.json
# Édite coach-ios.local.json et remplace par la vraie valeur
# (visible dans ~/.config/infomaniak.env sur le VPS, ligne export COACH_WEB_TOKEN=)
L'app utilisera ce token pour se connecter automatiquement à coach.hypnotruck.ch/login?token=... au lancement.
2. Générer les icônes & splash
# iOS uniquement
npx @capacitor/assets generate --ios
# Android uniquement
npx @capacitor/assets generate --android
# Les deux
npx @capacitor/assets generate
Génère toutes les tailles requises depuis resources/icon.png (1024×1024) et resources/splash.png (2732×2732).
3. Ajouter la plateforme iOS (à faire UNE SEULE FOIS sur le Mac)
npx cap add ios
Cette commande crée le dossier ios/ avec le projet Xcode, qui sera ensuite committé.
4. Sync (à chaque changement de plugins ou config)
npx cap sync ios
5. Ouvrir dans Xcode
npx cap open ios
Dans Xcode :
- Sélectionner le projet App dans la navigation gauche
- Onglet Signing & Capabilities :
- Team : ton équipe Apple Developer
- Bundle Identifier :
ch.hypnotruck.coach - Cocher "Automatically manage signing"
- Ajouter les capabilities requises :
- HealthKit (lecture FC, pas, calories, VO2max)
- Push Notifications
- Background Modes → Remote notifications
- Onglet Info : ajouter les clés Privacy obligatoires (voir section ci-dessous)
6. Build sur simulateur / device
- Simulateur : choisir un iPhone dans la barre du haut → ⌘R
- Device physique : connecter iPhone par USB, sélectionner, ⌘R (la 1ère fois iOS demande de faire confiance au certificat dev dans Réglages → Général → VPN et gestion des appareils)
7. Upload TestFlight
- Xcode → Product → Archive
- Fenêtre Organizer s'ouvre → sélectionner l'archive → Distribute App → App Store Connect → Upload
- Sur App Store Connect : créer une nouvelle app (1ère fois), section TestFlight → ajouter testeurs (jusqu'à 100 internes sans review, 10 000 externes avec review légère)
Privacy keys requises (Info.plist)
Sans ces clés, iOS crashe à l'usage du plugin. À ajouter dans Xcode → onglet Info → Custom iOS Target Properties :
| Clé | Valeur (description affichée à l'utilisateur) |
|---|---|
NSHealthShareUsageDescription |
"Coach Hypnotruck lit vos données de santé (FC, pas, calories) pour adapter vos séances." |
NSHealthUpdateUsageDescription |
"Coach Hypnotruck enregistre vos séances dans Santé pour suivre votre progression." |
NSLocationWhenInUseUsageDescription |
"Coach Hypnotruck utilise votre position pour tracer vos sorties course et VTT." |
NSLocationAlwaysAndWhenInUseUsageDescription |
"Permet le suivi GPS pendant les sorties même app en arrière-plan." |
NSMotionUsageDescription |
"Coach Hypnotruck utilise les capteurs de mouvement pour mesurer votre activité." |
NSCameraUsageDescription |
"Coach Hypnotruck peut utiliser l'appareil photo pour scanner des codes-barres alimentaires." |
Anti-rejet App Store (Guideline 4.2)
Apple rejette les apps qui sont juste un wrapper webview sans valeur native. Pour passer review :
- ✅ Push notifications natives (rappel séances) — implémenté côté iOS via
@capacitor/push-notifications - ✅ HealthKit (lecture FC, pas, calories) — implémenté via
@capgo/capacitor-health - ✅ Géolocalisation native pour tracking sorties
- ✅ Local notifications (rappels hors-ligne)
- ✅ Haptics sur actions importantes
- ⏳ À implémenter côté web : appeler ces plugins via JS bridge depuis coach.hypnotruck.ch
Structure repo
coach-ios/
├── capacitor.config.ts # Config Capacitor (bundle ID, URL, plugins)
├── package.json # Deps Capacitor + plugins
├── www/ # Page de bootstrap (chargement, fallback)
│ └── index.html
├── resources/ # Source icônes & splash (1024×1024, 2732×2732)
│ ├── icon.png
│ └── splash.png
├── ios/ # Projet Xcode (généré par `cap add ios` sur Mac)
└── README.md
Mise à jour de l'app
| Type de modif | Action requise |
|---|---|
| Changement web (templates, CSS, routes) | Aucune — l'app charge la prod en direct |
| Ajout plugin Capacitor | npm install + npx cap sync + nouveau build TestFlight / Play Store |
| Changement icône / splash | npx @capacitor/assets generate + sync + nouveau build |
| Changement bundle ID, version, capabilities | Build + soumission Apple review / Play Console review |
TODO Phase 0
- Sur le Mac :
npx cap add ioset commit du dossierios/ - Configurer signing dans Xcode avec Apple ID
dev@hypnotruck.ch - Ajouter les Privacy keys dans Info.plist
- 1er build simulateur pour vérifier que la PWA charge bien
- 1er build device physique (iPhone perso)
- Upload TestFlight pour beta interne
- Préparer screenshots App Store (6.7" iPhone, min 3)
- Privacy policy URL (obligatoire avec HealthKit) — à héberger sur coach.hypnotruck.ch/privacy