diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..0caf1e3
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,86 @@
+# CLAUDE.md — coach-ios (wrapper mobile Capacitor)
+
+Instructions pour Claude Code sur ce projet. Lis ce fichier en début de session.
+
+## 🎯 Contexte projet
+
+- **Nature** : wrapper Capacitor 8 (iOS + Android) qui charge `https://coach.hypnotruck.ch` dans une WebView
+- **Pas de code web embarqué** — `server.url` dans `capacitor.config.ts` pointe vers la prod → déploiement web = mise à jour app sans review Apple/Google
+- **Bundle ID** : `ch.hypnotruck.coach` (iOS + Android)
+- **Repo Gitea** : encore nommé `coach-ios` (renommage `coach-mobile` à faire) ; fichier `coach-ios.local.json` conservé pour ne pas casser les refs Swift
+- **Plateformes** : iOS 13+ et Android 8.0+ (API 26+, requis par Health Connect)
+- **Build iOS** : nécessite le Mac mini (macOS 13+, Xcode 15+). Pas de Mac cloud nécessaire.
+- **État** : iOS prêt (HealthKit + cookie inject AppDelegate). Android prêt. Bloqué externe : upgrade macOS 26 pour build iOS + Play Console $25 ~3-5j.
+
+## 🔒 Règles non négociables
+
+1. **Le code web n'est PAS ici** — pour modifier l'UI/UX, c'est `coach_sportif/web/`. Ce repo ne touche QUE le natif (Swift, Kotlin, plugins Capacitor, config).
+2. **Cookie inject AppDelegate** (iOS) : mécanisme critique pour l'auth WKWebView. Toute modif `AppDelegate.swift` doit préserver ce flux.
+3. **Health Connect Android** : nécessite la privacy policy URL dans le manifest (validation Play Console).
+4. **Pas d'extrapolation** : root cause vérifiable avant tout fix natif. iOS+Android = doubles surfaces de bug.
+5. **Default yes** : décider et exécuter (sauf actions destructives).
+6. **Pousser après commit** : `git push` systématique.
+7. **Descriptions Info.plist en FR** (NSHealthShareUsageDescription etc.) — App Store FR.
+
+## 🛠️ Workflow par phase
+
+### Avant toute feature native
+- `brainstorming` — explorer l'intent (souvent : "ce truc marche sur web, comment le porter en natif ?")
+- `grill-with-docs` — challenger : est-ce vraiment du natif ou du web qui marcherait via WebView ?
+
+### Code Swift / iOS
+- `swiftui-pro` — quand le chantier **watchOS live workout** démarrera (HKWorkoutPlan + WatchConnectivity)
+- Vérifier `CoachHealthRoute.swift`, `CoachWorkoutKit.swift`, `CoachWorkoutObserver.swift` avant de toucher au flow HealthKit
+
+### Code Kotlin / Android
+- `compose-state-hoisting` — si plugin Capacitor custom avec UI Compose
+- `kotlin-coroutines-structured-concurrency` — appels Health Connect async
+- `kotlin-flow-state-event-modeling` — observations Health Connect
+
+### Bug / régression natif
+- `investigate` — root cause discipline (cassures fréquentes : permissions, signatures, entitlements)
+- `diagnose` — reproduire sur device réel, pas juste simulator
+
+### QA / ship
+- `qa` (gstack) — l'app pointant vers la prod web, tester le web couvre 80%
+- `webapp-testing` — Playwright sur `coach.hypnotruck.ch` (le contenu réel de la WebView)
+- `cso` — audit signatures, entitlements, secrets dans `coach-ios.local.json`
+- `ship` — workflow PR (mais les releases App Store/Play Store restent manuelles depuis Mac mini)
+
+## 📂 Conventions code
+
+- **iOS natif** : `ios/App/App/*.swift`
+ - `AppDelegate.swift` — bootstrap + cookie inject (NE PAS CASSER)
+ - `MainViewController.swift` — WKWebView config
+ - `CoachAuth.swift.example` → copier en `CoachAuth.swift` (gitignored, contient secret partagé)
+ - `CoachHealthRoute.swift` / `CoachWorkoutKit.swift` / `CoachWorkoutObserver.swift` — pont HealthKit ↔ web
+- **Android natif** : `android/app/src/main/java/ch/...`
+- **Config Capacitor** : `capacitor.config.ts` (TS, source de vérité) → généré vers `ios/App/App/capacitor.config.json`
+- **Resources** : `resources/` → assets (icons, splash) générés par `@capacitor/assets`
+
+## 🔗 Plugins Capacitor utilisés
+
+- `@capacitor/push-notifications`
+- `@capacitor/local-notifications`
+- `@capacitor/geolocation`
+- `@capgo/capacitor-health` — HealthKit (iOS) / Health Connect (Android)
+- `@capacitor/splash-screen`
+- `@capacitor/status-bar`
+- `@capacitor/haptics`
+
+## 🚧 Backlog notable
+
+- Renommer le repo Gitea `coach-ios` → `coach-mobile`
+- Upgrade macOS 26 sur Mac mini pour reprendre les builds iOS
+- Play Console validation (~3-5j, $25 one-shot) pour upload Android
+- **watchOS live workout compagnon** (chantier ACTIF depuis 2026-05-24) — plan
+ technique complet dans `docs/watchos-live-workout-plan.md`. Handoff pour
+ Claude Code Mac dans `HANDOFF-WATCHOS.md` (à lire en 1er si tu reprends ce
+ chantier).
+- Push workouts vers Apple Watch (HKWorkoutPlan) / Polar (Training Targets API) / Garmin — backlog 2026-05-12
+
+## ⚠️ Limites connues sync
+
+- **Apple Watch sync** : impossible via API tierce — le wrapper HealthKit dans cette app est obligatoire pour récupérer les données Watch.
+- **Polar AccessLink** : pas d'historique pré-enregistrement (limitation API).
+- **Push training-targets vers Polar** : nécessiterait migration AccessLink v4 (backlog).
diff --git a/HANDOFF-WATCHOS.md b/HANDOFF-WATCHOS.md
new file mode 100644
index 0000000..a049349
--- /dev/null
+++ b/HANDOFF-WATCHOS.md
@@ -0,0 +1,181 @@
+# Handoff — Chantier watchOS Live Workout
+
+**Pour** : Claude Code lancé sur le Mac mini Sylvain (`cd ~/coach-ios && claude`).
+**De** : Claude Code session Linux du 2026-05-24 (sandbox sans Xcode).
+**Pourquoi le handoff** : la phase native Swift/watchOS nécessite Xcode +
+Apple Watch device, indispo sur le sandbox Linux.
+
+## Première chose à faire avant de coder
+
+1. **Lis** `coach-ios/CLAUDE.md` (règles projet).
+2. **Lis** `coach-ios/docs/watchos-live-workout-plan.md` (plan technique complet).
+3. **Vérifie** que tu es bien sur le Mac mini avec Xcode 16+ : `xcodebuild -version`.
+4. **Vérifie** que `coach-ios` est à jour : `git pull` (les commits préparatoires
+ sont déjà poussés sur `main`).
+
+## Contexte session Linux (2026-05-24)
+
+Sylvain a confirmé vouloir attaquer le chantier watchOS Live Workout
+(memory `project_coach_watchos_live.md`). On a estimé ensemble qu'écrire
+du Swift à l'aveugle depuis le sandbox Linux était risqué (training data
+peut diverger des dernières APIs Apple, pas de compile Xcode pour valider).
+
+D'où ce handoff : toute la phase native (Phases 1-3 du plan) doit être
+exécutée par toi sur le Mac avec lecture des docs Apple en direct.
+
+Memories à charger pour contexte complet (probablement déjà dans `~/.claude/projects/-home-coder/memory/` synchronisé via Gitea ou autre, sinon
+demande à Sylvain de te briefer) :
+
+- `project_coach_watchos_live.md` — chantier original
+- `project_coach_ios.md` — état wrapper Capacitor
+- `project_coach_app_commercial.md` — pivot commercial (le watchOS live est un
+ différenciateur clé pour vendre à 39 ou 99 CHF/mois)
+- `user_sante_cardio.md` — FCmax Sylvain 145-155 bpm (à respecter dans tests)
+- `feedback_inline_display_none_toggle.md` — règle apprise hier : grep JS avant
+ de migrer du `style="display:none"` initial vers du CSS
+
+## État actuel à ton arrivée
+
+```
+coach-ios/
+├── CLAUDE.md ............. Règles projet (à jour)
+├── HANDOFF-WATCHOS.md .... Ce fichier
+├── docs/
+│ └── watchos-live-workout-plan.md ... Plan technique complet
+├── ios/App/App/
+│ ├── AppDelegate.swift .......... Bootstrap + cookie inject (NE PAS CASSER)
+│ ├── MainViewController.swift ... WKWebView config
+│ ├── CoachHealthRoute.swift ..... HealthKit web bridge existant
+│ ├── CoachWorkoutKit.swift ...... HKWorkoutPlan push (iOS 17+)
+│ └── CoachWorkoutObserver.swift . Observer fin de séance
+└── ios/App/App.xcodeproj ......... Projet Xcode (PAS de target watchOS encore)
+```
+
+**Aucun fichier Swift watchOS écrit pour l'instant** — décision conjointe :
+tu écris depuis zéro après avoir lu les docs Apple à jour.
+
+## Workflow recommandé sur ton Mac
+
+### Step 1 — Setup environnement (30 min)
+
+```bash
+cd ~/coach-ios
+git pull origin main
+xcodebuild -version # Xcode 16+ requis (watchOS 11+ SDK)
+xcrun simctl list devicetypes | grep Watch # voir simulateurs watchOS dispo
+```
+
+Vérifie aussi que ton Apple Watch est jumelée à l'iPhone et tournée vers
+watchOS 11+ (cf. About sur la Watch).
+
+### Step 2 — Création de la target watchOS (1-2h)
+
+**2 voies possibles** :
+
+**Voie A — Manuelle dans Xcode UI** (recommandée pour la 1ère fois) :
+1. Ouvre `ios/App/App.xcworkspace`
+2. `File > New > Target...` → watchOS → App
+3. Suis le plan section "Phase 1.1" du fichier `docs/watchos-live-workout-plan.md`
+4. Commit le diff sur `project.pbxproj`
+
+**Voie B — Édition pbxproj directe** (si tu veux être 100% reproducible) :
+- Édite `ios/App/App.xcodeproj/project.pbxproj` à la main
+- Risque : format propriétaire Xcode, fragile. À éviter sauf si Voie A
+ bloquée.
+
+### Step 3 — Lecture des docs Apple à jour (45 min)
+
+Avant de coder, lis ces 4 pages doc Apple **dans ton browser** ou via
+`mcp__claude_ai_*` si tu as un MCP fetch dispo :
+
+1. `developer.apple.com/documentation/healthkit/hkliveworkoutbuilder`
+2. `developer.apple.com/documentation/watchconnectivity/wcsession`
+3. `developer.apple.com/documentation/healthkit/hkworkoutsession`
+4. `capacitorjs.com/docs/plugins/creating-plugins/ios-guide`
+
+Note les **signatures exactes** qui peuvent avoir changé. Le plan technique
+du fichier `docs/watchos-live-workout-plan.md` contient du pseudocode qui
+peut être périmé sur des détails (par exemple async/await sur certaines
+méthodes en watchOS 11+).
+
+### Step 4 — Implémentation Swift (6-8h sur 1-2 jours)
+
+Suis les Phases 2 et 3 du plan technique. Commit petit par petit :
+
+- Commit 1 : `CoachWatchApp.swift` + `ContentView.swift` minimal
+ (juste affichage statique, vérifie que la target builde sur simulator).
+- Commit 2 : `WorkoutManager.swift` (HKLiveWorkoutBuilder seul, sans
+ Connectivity). Vérifie sur Apple Watch device que les samples HR arrivent.
+- Commit 3 : `ConnectivityManager.swift` (WCSession). Test envoi simple.
+- Commit 4 : `CoachLiveBridge.swift` côté iPhone (Capacitor plugin).
+- Commit 5 : Tests + fixes itératifs.
+
+Règle clé : **chaque commit doit compiler** (Watch + iPhone targets).
+
+### Step 5 — Route web `/live` + template
+
+Cette partie peut être faite par toi OU déléguée à une session Claude Code
+sur le repo `coach_sportif/` (Linux Linux sandbox d'origine). Le code FastAPI
++ Jinja est trivial une fois le plugin Capacitor publie l'event JS
+`liveSample`.
+
+Si tu veux le faire toi-même sur Mac : Phase 4 du plan technique a le
+squelette prêt à coller.
+
+### Step 6 — Tests device + TestFlight internal (4-8h)
+
+- Test E2E : lance app CoachWatch sur Apple Watch → choisis activité
+ Course → vérifie /live sur iPhone affiche le BPM en moins de 10 sec.
+- Test fallback : Bluetooth off pendant 30 sec, vérifie que les samples
+ arrivent quand BLE revient.
+- Test batterie : 1h de workout, vérifier % drop sur Watch (cible <15%).
+- Upload TestFlight internal (juste pour Sylvain) avant la 1ère review
+ publique.
+
+## Garde-fous communs à respecter (depuis les memories)
+
+1. **Pas d'extrapolation** : si une API Apple n'est pas vérifiée dans la doc
+ live, **ne pas l'inventer**. Préfère commenter "TODO: vérifier signature"
+ et demander à Sylvain.
+2. **Default yes** : décide et exécute sauf actions destructives (push force,
+ delete entitlements, etc.).
+3. **Git push après chaque commit** atomique qui compile.
+4. **descriptions Info.plist en FR** (App Store FR).
+5. **`raise X from e`** ou `from None` côté Python (B904 fixé ce matin,
+ préserver la norme).
+6. **Tests à chaque feature** ; pas de merge sans test minimum.
+7. **CLAUDE.md à jour** : si tu décides quelque chose qui change l'archi
+ (ex : passer à Live Activity au lieu de webview /live), document-le.
+
+## Quand revenir vers la session Linux
+
+Le repo `coach_sportif/` (Linux sandbox source) reste mon terrain :
+
+- Backend FastAPI (route `/live`, helpers)
+- Template Jinja (`live_workout.html`)
+- Tests pytest backend
+- Refactor CSS / scope global
+
+Quand tu finis le plugin Capacitor `CoachLiveBridge` et que tu as besoin
+de la route `/live` côté serveur, soit tu la code toi-même (Phase 4), soit
+tu demandes à Sylvain de relancer la session Linux pour que je l'écrive.
+
+## Référence "qui possède quoi" pendant le chantier
+
+| Composant | Owner | Notes |
+|---|---|---|
+| `coach-ios/ios/App/App/Coach*Watch.swift` | Toi (Mac) | Swift natif iOS |
+| `coach-ios/watch/CoachWatch/*.swift` | Toi (Mac) | Swift natif watchOS |
+| `coach-ios/ios/App/App/CoachLiveBridge.swift` | Toi (Mac) | Plugin Capacitor iOS |
+| `coach-ios/project.pbxproj` | Toi (Mac) | Édition prudente |
+| `coach_sportif/web/routers/misc.py` route /live | Moi (Linux) ou toi | Trivial |
+| `coach_sportif/web/templates/live_workout.html` | Moi (Linux) ou toi | Squelette dans plan |
+| `coach_sportif/web/static/style.css` ajouts .live-* | Moi (Linux) | Si je fais le template |
+| Tests pytest backend | Moi (Linux) | |
+| Tests Xcode XCTest | Toi (Mac) | |
+
+## Bon courage
+
+Quand tu auras fini : commit final qui résume l'état + ferme ce chantier
+dans la memory (`project_coach_watchos_live.md` à update). Et appelle
+Sylvain pour test device. 🚀
diff --git a/docs/watchos-live-workout-plan.md b/docs/watchos-live-workout-plan.md
new file mode 100644
index 0000000..af26794
--- /dev/null
+++ b/docs/watchos-live-workout-plan.md
@@ -0,0 +1,444 @@
+# Chantier watchOS — Live Workout sur iPhone
+
+**Statut** : plan validé, prêt à exécuter sur Mac mini avec Xcode.
+**Owner exécution** : Claude Code lancé sur le Mac (cd `~/coach-ios && claude`).
+**Origine** : memory `project_coach_watchos_live.md` + session Linux 2026-05-24.
+
+## Objectif fonctionnel
+
+Pendant que Sylvain fait une séance de sport sur son Apple Watch,
+l'iPhone (et la web app `/live`) affiche en temps réel : FC bpm, calories,
+distance parcourue, durée écoulée, allure (si applicable).
+
+Comportement de référence : app **Apple Workout / Vélo native** qui le
+fait déjà côté iPhone via APIs privées. On reproduit avec APIs publiques.
+
+## Architecture cible (5 couches)
+
+```
+┌─────────────────────────────────────────────┐
+│ Apple Watch │
+│ ┌─────────────────────────────────────┐ │
+│ │ CoachWatch (watchOS app native) │ │
+│ │ ┌─────────────────────────────┐ │ │
+│ │ │ WorkoutManager (HKLiveWB) │←──┼───┼── L1
+│ │ └─────────────────────────────┘ │ │
+│ │ ↓ heart rate / │ │
+│ │ ↓ calories / │ │
+│ │ ↓ distance │ │
+│ │ ┌─────────────────────────────┐ │ │
+│ │ │ ConnectivityManager (WCSession) ←──┼── L2
+│ │ └─────────────────────────────┘ │ │
+│ └─────────────────────────────────────┘ │
+└──────────────────────┬──────────────────────┘
+ │ WatchConnectivity
+ │ sendMessage (1-3 sec)
+ ↓
+┌─────────────────────────────────────────────┐
+│ iPhone │
+│ ┌─────────────────────────────────────┐ │
+│ │ App.xcodeproj (Capacitor) │ │
+│ │ ┌─────────────────────────────┐ │ │
+│ │ │ CoachLiveBridge.swift │←──┼───┼── L3
+│ │ │ (Capacitor Plugin natif) │ │ │
+│ │ └─────────────────────────────┘ │ │
+│ │ ↓ event JS │ │
+│ │ ┌─────────────────────────────┐ │ │
+│ │ │ WKWebView (loads /live) │←──┼───┼── L4
+│ │ └─────────────────────────────┘ │ │
+│ └─────────────────────────────────────┘ │
+└──────────────────────┬──────────────────────┘
+ │ HTTP (au démarrage seulement)
+ ↓
+┌─────────────────────────────────────────────┐
+│ Backend coach.hypnotruck.ch │
+│ ┌─────────────────────────────────────┐ │
+│ │ /live route (FastAPI + template) │←──── L5
+│ └─────────────────────────────────────┘ │
+└─────────────────────────────────────────────┘
+```
+
+## Décisions d'archi (à respecter)
+
+### Décision 1 — App watchOS native, pas SwiftUI Capacitor
+
+Le live workout nécessite `HKLiveWorkoutBuilder` qui n'est dispo que sur watchOS
+natif (Xcode target Watch App). Capacitor ne tourne PAS sur watchOS.
+
+### Décision 2 — `sendMessage` plutôt que `transferUserInfo`
+
+`sendMessage(_:replyHandler:errorHandler:)` est temps réel mais nécessite
+les 2 devices reachable. Si la Watch perd la connexion à l'iPhone (BLE down),
+on passe en fallback `transferUserInfo` (queued, livraison différée).
+
+Stratégie : tenter `sendMessage` toutes les 1-3 sec. Si `WCSession.isReachable == false`,
+buffer 30 sec puis dump via `transferUserInfo` au retour de connexion.
+
+### Décision 3 — Pas de Live Activity iOS pour le MVP
+
+Live Activity (Dynamic Island + Lock Screen) = ActivityKit framework, plus
+de complexité, plus de surface de bug, validation Apple Store plus stricte.
+Reporté à la V2 si la V1 cartonne en TestFlight.
+
+V1 = juste page web `/live` qui affiche les données. iPhone doit être déverrouillé
+et l'app au premier plan pour voir le live. Comportement acceptable car le user
+est en train de faire du sport — il a son iPhone à côté de lui (vélo, treadmill).
+
+### Décision 4 — Démarrage du workout depuis l'app Watch (pas l'iPhone)
+
+`HKLiveWorkoutBuilder` doit être démarré côté watchOS. Donc l'user **lance la
+séance depuis l'app CoachWatch sur sa montre**, pas depuis l'app native Workout.
+
+Trade-off accepté : moins ergonomique (l'user pourrait préférer Workout native),
+mais c'est la seule voie publique. Workaround possible plus tard :
+**WatchOS App Intent / Complication** pour "Lancer séance Coach" depuis le
+cadran (1 tap).
+
+### Décision 5 — Bundle ID watchOS
+
+`ch.hypnotruck.coach.watchkitapp` (extension du bundle iOS `ch.hypnotruck.coach`).
+Signing automatique via Apple Developer Account déjà actif.
+
+## Phase 1 — Création de la cible watchOS dans Xcode
+
+**Owner** : Sylvain (manuel UI Xcode) ou Claude Code Mac via édition
+`project.pbxproj` direct.
+
+### Étape 1.1 — Add target Watch App
+
+Dans Xcode :
+1. `File > New > Target`
+2. Choisir **watchOS > App** (pas "App with Companion" car notre iPhone est Capacitor)
+3. Product Name : `CoachWatch`
+4. Interface : **SwiftUI**
+5. Language : Swift
+6. Bundle ID : `ch.hypnotruck.coach.watchkitapp`
+7. Embed in Application : **App** (l'app iPhone existante)
+8. Finish
+
+### Étape 1.2 — Entitlements watchOS
+
+Dans Signing & Capabilities de la target CoachWatch :
+1. `+ Capability` → **HealthKit**
+ - Cocher : Workout Processing
+2. `+ Capability` → **Background Modes**
+ - Cocher : Workout processing
+3. Vérifier le provisioning profile (auto-managed devrait suffire avec Apple Dev Account)
+
+### Étape 1.3 — Info.plist watchOS
+
+Ajouter 2 clés (FR pour App Store FR) :
+- `NSHealthShareUsageDescription` : "Coach Hypnotruck lit ta fréquence cardiaque et tes calories pendant la séance pour les afficher en direct sur ton iPhone."
+- `NSHealthUpdateUsageDescription` : "Coach Hypnotruck enregistre ta séance dans l'app Santé."
+
+## Phase 2 — Code Swift watchOS
+
+**4 fichiers Swift à créer** dans la target CoachWatch :
+
+### `CoachWatchApp.swift` (entry point SwiftUI)
+
+```swift
+import SwiftUI
+import WatchKit
+
+@main
+struct CoachWatchApp: App {
+ @WKApplicationDelegateAdaptor private var appDelegate: AppDelegate
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ .environmentObject(WorkoutManager.shared)
+ .environmentObject(ConnectivityManager.shared)
+ }
+ }
+}
+
+class AppDelegate: NSObject, WKApplicationDelegate {
+ func applicationDidFinishLaunching() {
+ // Activate WCSession dès le boot pour ne pas rater les early messages
+ _ = ConnectivityManager.shared
+ }
+}
+```
+
+### `WorkoutManager.swift` (HKLiveWorkoutBuilder)
+
+Pattern :
+- Singleton `@MainActor`
+- `@Published` pour les samples (heartRate, activeEnergyKcal, distanceMeters, elapsedSec)
+- `func startWorkout(activityType: HKWorkoutActivityType)` qui demande auth HK, crée la session + builder, configure le delegate
+- `HKLiveWorkoutBuilderDelegate` : `workoutBuilder(_:didCollectDataOf:)` qui pull les samples + push via Connectivity
+- `func endWorkout()` qui clôture proprement
+
+APIs minimales requises :
+- `HKHealthStore`
+- `HKWorkoutSession(healthStore:configuration:)`
+- `HKLiveWorkoutBuilder(...)`
+- `HKQuantityTypeIdentifier.heartRate`, `.activeEnergyBurned`, `.distanceWalkingRunning` (ou `.distanceCycling`)
+- `HKWorkoutConfiguration` avec `activityType` + `locationType` (outdoor/indoor)
+
+### `ConnectivityManager.swift` (WCSession)
+
+Pattern :
+- Singleton avec `WCSession.default` activé
+- `func sendSample(...)` qui try `sendMessage` si reachable, sinon buffer en RAM + flush via `transferUserInfo`
+- Throttle : un sample max toutes les 1-3 sec pour ne pas saturer BLE
+
+```swift
+import WatchConnectivity
+
+@MainActor
+final class ConnectivityManager: NSObject, ObservableObject, WCSessionDelegate {
+ static let shared = ConnectivityManager()
+ private var buffer: [[String: Any]] = []
+
+ override init() {
+ super.init()
+ guard WCSession.isSupported() else { return }
+ WCSession.default.delegate = self
+ WCSession.default.activate()
+ }
+
+ func sendSample(_ payload: [String: Any]) {
+ let session = WCSession.default
+ if session.isReachable {
+ session.sendMessage(payload, replyHandler: nil) { [weak self] _ in
+ // Fallback queue
+ self?.buffer.append(payload)
+ }
+ } else {
+ buffer.append(payload)
+ if buffer.count >= 10 || buffer.count == 1 {
+ flushBuffer()
+ }
+ }
+ }
+
+ private func flushBuffer() {
+ guard !buffer.isEmpty else { return }
+ WCSession.default.transferUserInfo(["samples": buffer])
+ buffer.removeAll()
+ }
+
+ // WCSessionDelegate stubs
+ func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {}
+ func sessionReachabilityDidChange(_ session: WCSession) {
+ if session.isReachable { flushBuffer() }
+ }
+}
+```
+
+### `ContentView.swift` (UI SwiftUI watchOS)
+
+3 vues :
+- **Picker activité** : Course / Vélo / Marche / Renfo (les 4 sports principaux du plan)
+- **Live workout view** : grosses metrics centrées (FC, calories, durée), bouton Pause/Resume + End
+- **Summary view** : après End, montre les totaux + bouton "Retour" qui ferme l'app
+
+Patterns SwiftUI iOS HIG conformes :
+- `TabView` avec `.tabViewStyle(.page)` pour swipe entre vues
+- `.digitalCrown` rotation pour ajuster nothing (skip)
+- Haptic feedback sur start/pause/end (`WKInterfaceDevice.current().play(.start)` etc.)
+- Always-on display via `.scenePhase` (preserve simplified UI quand `.inactive`)
+
+## Phase 3 — Plugin Capacitor iOS (côté iPhone)
+
+### `CoachLiveBridge.swift`
+
+À placer dans `ios/App/App/CoachLiveBridge.swift`. C'est un plugin
+Capacitor natif qui :
+1. Active `WCSession` côté iPhone au boot
+2. Implémente `WCSessionDelegate.session(_:didReceiveMessage:)` et `didReceiveUserInfo:`
+3. Pousse les samples au JS via `notifyListeners("liveSample", payload)`
+
+```swift
+import Foundation
+import Capacitor
+import WatchConnectivity
+
+@objc(CoachLiveBridge)
+public class CoachLiveBridge: CAPPlugin, WCSessionDelegate {
+
+ override public func load() {
+ super.load()
+ guard WCSession.isSupported() else { return }
+ WCSession.default.delegate = self
+ WCSession.default.activate()
+ }
+
+ public func session(_ session: WCSession,
+ activationDidCompleteWith activationState: WCSessionActivationState,
+ error: Error?) {}
+ public func sessionDidBecomeInactive(_ session: WCSession) {}
+ public func sessionDidDeactivate(_ session: WCSession) {
+ // Re-activer pour les sessions futures (pattern recommandé)
+ WCSession.default.activate()
+ }
+
+ public func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
+ DispatchQueue.main.async { [weak self] in
+ self?.notifyListeners("liveSample", data: message)
+ }
+ }
+
+ public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
+ if let samples = userInfo["samples"] as? [[String: Any]] {
+ DispatchQueue.main.async { [weak self] in
+ for s in samples { self?.notifyListeners("liveSample", data: s) }
+ }
+ }
+ }
+}
+```
+
+### Enregistrement du plugin
+
+Dans `Podfile` (déjà géré par Capacitor build) et dans le JS web :
+
+```javascript
+// Côté web (à ajouter dans /live template)
+import { registerPlugin } from '@capacitor/core';
+const CoachLiveBridge = registerPlugin('CoachLiveBridge');
+CoachLiveBridge.addListener('liveSample', (sample) => {
+ // sample.heartRate, sample.activeEnergyKcal, sample.distanceMeters, sample.elapsedSec
+ updateLiveUI(sample);
+});
+```
+
+## Phase 4 — Route web `/live` côté backend
+
+À ajouter dans `coach_sportif/web/routers/misc.py` ou nouveau router :
+
+```python
+@router.get("/live", response_class=HTMLResponse)
+def live_workout(request: Request, token: str | None = Query(default=None)):
+ """Page affichage live workout : reçoit les samples push de la Watch via
+ CoachLiveBridge (plugin Capacitor) et affiche les metrics en temps réel.
+
+ Cette page n'est utile QUE dans le wrapper Capacitor iOS — en navigateur
+ pur, aucun WCSession donc 0 samples reçus."""
+ check_auth(request, token)
+ return templates.TemplateResponse(
+ request, "live_workout.html",
+ {"token": token or "", "v": assets_version()},
+ )
+```
+
+Template `coach_sportif/web/templates/live_workout.html` (squelette) :
+
+```html
+{% extends "_layout.html" %}
+{% block title %}Live workout — Coach{% endblock %}
+{% block content %}
+
+
+
+ FC
+ —
+ bpm
+
+
+ Calories
+ —
+ kcal
+
+
+ Distance
+ —
+ km
+
+
+ Durée
+ —
+
+ En attente de la Watch…
+
+
+{% endblock %}
+```
+
+## Phase 5 — Tests
+
+### Tests unitaires possibles
+- `WorkoutManager` : impossible sans device (HKHealthStore mock = expérimental)
+- `ConnectivityManager` : peut tester le buffer logique sans réseau
+- Plugin iOS : pareil — buffer + notifyListeners testable en unit
+
+### Tests device (obligatoires)
+- iPhone + Apple Watch jumelée déverrouillées
+- Lancer app CoachWatch sur la montre
+- Choisir activité (Course par ex)
+- Vérifier que FC commence à s'afficher dans 5-10 sec
+- Ouvrir Coach Hypnotruck iPhone → `/live` → vérifier que les chiffres défilent
+- Test fallback : couper le Bluetooth pendant 30 sec, voir que le buffer flush au retour
+
+### Test simulator (limité)
+- Simulator watchOS supporte HKLiveWorkoutBuilder en mock mais valeurs aléatoires
+- WatchConnectivity entre simulators iPhone + Watch supportée depuis Xcode 14
+
+## Estimation effort (révisée après recherche)
+
+| Phase | Effort | Owner |
+|---|---|---|
+| 1 — Création target Xcode + entitlements | 1-2h | Sylvain (UI Xcode) ou Claude Mac via pbxproj |
+| 2 — Code Swift watchOS (4 fichiers) | 4-6h | Claude Mac (compile, lit erreurs) |
+| 3 — Plugin Capacitor iOS | 2-3h | Claude Mac |
+| 4 — Route web `/live` + template | 1h | Faisable depuis n'importe où (Linux ou Mac) |
+| 5 — Tests device + fixes | 4-8h | Sylvain + Claude Mac |
+| **Total réaliste** | **12-20h sur 3-5 jours** | |
+
+## Risques connus + mitigations
+
+| Risque | Mitigation |
+|---|---|
+| HKLiveWorkoutBuilder permissions refusées par user | Doc claire dans onboarding + bouton "Réessayer" qui force re-prompt |
+| WCSession.isReachable false en permanence (BLE flaky) | Fallback `transferUserInfo` queued (livraison différée mais garantie) |
+| Sample rate trop élevé → batterie Watch crame | Throttle à 1-3 sec, agréger côté Watch avant push |
+| User lance Workout natif Apple au lieu de CoachWatch | Doc + onboarding pédagogique. V2 : App Intent / Complication |
+| Apple rejette en TestFlight pour signing/entitlements | Tester en internal TestFlight d'abord, pas production directe |
+| `notifyListeners` Capacitor ne reçoit pas en background | App doit rester au premier plan iPhone (acceptable pendant séance) |
+
+## Pas dans le scope V1
+
+- Live Activity iOS (Dynamic Island) — V2 si V1 cartonne
+- App Intent / Complication "Lancer séance" — V2
+- Sync vers Polar / Garmin pendant la séance — backlog séparé
+- Multi-user / Apple Watch partagée — irrelevant pour Sylvain
+- Workouts piscine (HKWorkoutSwimmingLocationType) — exclu, Sylvain ne nage pas
+
+## Références à valider sur Mac (les liens Apple ne se rendent pas via WebFetch Linux)
+
+- HKLiveWorkoutBuilder : `developer.apple.com/documentation/healthkit/hkliveworkoutbuilder`
+- WCSession : `developer.apple.com/documentation/watchconnectivity/wcsession`
+- HKWorkoutSession : `developer.apple.com/documentation/healthkit/hkworkoutsession`
+- Capacitor Custom Plugin iOS : `capacitorjs.com/docs/plugins/creating-plugins/ios-guide`
+
+Le Claude Mac doit lire ces 4 pages dans sa session pour valider les
+signatures avant de les utiliser.