Pendant un test, le telephone est dans une poche : la montre est le seul ecran
consultable en courant, et c'est elle qui porte deja vitesse, distance et FC via
WorkoutManager.
Deux protocoles. Paliers d'une minute de 8 km/h par pas de 0,5, avec haptique au
changement et l'ecart a l'allure cible affiche — la vitesse brute demanderait un
calcul mental a chaque foulee. Ou six minutes, la distance parcourue donnant
directement la VMA.
⚠️ AUCUN calcul cote montre : elle transmet des mesures (paliers tenus,
distance, FC max), le serveur calcule. Deux implementations d'une meme formule
divergent toujours. Le POST passe par CoachRoutineBridge, qui a deja
l'abonnement NotificationCenter, la base d'API et le cookie — un fichier de plus
devrait etre cable a la main dans le pbxproj.
⚠️ Les paliers avancent sur `elapsedSec` de HKLiveWorkoutBuilder, PAS sur un
compteur accumule au fil du Timer de la vue : ce Timer ne tourne pas quand
l'ecran s'eteint, c'est-a-dire des que le poignet retombe — soit pendant
l'essentiel du test. Meme famille de piege que la Live Activity fantome.
L'envoi reprend la livraison differee de la routine : un test se fait dehors,
l'iPhone peut etre hors de portee, et refaire un test maximal parce qu'un
message s'est perdu n'est pas acceptable. Si l'envoi echoue, les chiffres
restent affiches pour une saisie manuelle.
VmaTestView.swift est un fichier NOUVEAU : cable dans le pbxproj (4 entrees,
sauvegarde project.pbxproj.bak-vma). Verification : `grep -c "VmaTestView.swift
in Sources"` doit rendre 2 (declaration + phase de compilation = 1 cible).
⚠️ NON COMPILE — a builder au prochain passage sur le Mac.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Suite de la demande : voir des km/h en courant, ce que l'app Exercice
d'Apple ne permet pas (vitesse réservée au vélo, allure min/km imposée en
course, aucun réglage).
La vitesse instantanée vient de HealthKit lui-même : HKLiveWorkoutDataSource
collecte .runningSpeed d'office en course extérieure (Series 6+) et
.cyclingSpeed à vélo (watchOS 11+). Elle est lue en mostRecentQuantity —
c'est la vitesse à l'instant t qu'on veut afficher, pas la moyenne de la
séance. Sur un appareil qui ne la produit pas, repli sur distance/temps.
Affichée sur les trois surfaces : écran de séance de la montre, Live
Activity (bannière + île dépliée) et vue native /live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comme l'app Exercice : deux boutons sur la bannière de l'écran verrouillé et
dans l'île dépliée. iOS n'expose aucun geste de balayage pour révéler des
actions — depuis iOS 17, ce sont des boutons intégrés (App Intents), et c'est
le seul mécanisme public.
- Intents `CoachTogglePauseIntent` / `CoachEndWorkoutIntent`, conformes à
LiveActivityIntent (sans quoi `perform()` n'est jamais appelé).
- Ils vivent dans CoachLiveActivityAttributes.swift, seul fichier déjà membre
des deux targets : un fichier neuf imposerait une manip Target Membership
dans Xcode, source d'erreurs répétées ici.
- Commandes relayées à la montre par WCSession, avec repli transferUserInfo :
isReachable retombe à false quand la séance tourne en arrière-plan profond,
la commande est alors différée au réveil de l'app montre.
- L'état de pause remonte depuis HKWorkoutSession (seule source fiable : la
montre peut mettre en pause d'elle-même) et bascule le libellé du bouton.
- Le watchdog de LiveStore est neutralisé pendant la pause : sans ça, l'absence
de samples aurait affiché « connexion perdue » puis terminé l'activité.
- L'app watchOS gagne le bouton Pause qui lui manquait, aligné sur le même état.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
La montre n'annonçait pas la fin : le flux de samples s'arrêtait, et
l'iPhone ne pouvait pas distinguer « séance terminée » de « montre hors de
portée ». La Live Activity restait donc figée sur la dernière FC reçue —
un cœur à 74 bpm en permanence dans la Dynamic Island.
Trois causes cumulées, toutes corrigées :
1. Aucun message de fin. WorkoutManager envoie maintenant `liveEnded` à
l'arrêt de la session HealthKit, en cas d'échec, et en mode test.
2. Le seul chemin de terminaison côté iPhone était le watchdog de
LiveStore, un Timer du main runloop — qui ne tourne pas quand l'app est
suspendue en arrière-plan, c'est-à-dire pendant toute la séance.
`endLive()` termine désormais l'activité dès réception du message.
3. `dismissalPolicy: .default` laissait la bannière ~4 h APRÈS la fin.
Passé en `.immediate`.
Filet de sécurité : `adoptExisting()` au lancement reprend la main sur une
activité orpheline (app tuée pendant une séance, `current` perdu) et
termine celles de plus de 6 h.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pendant natif du lecteur web. Bouton Démarrer par bloc : un exercice à la fois,
décompte en anneau, passage auto, haptique à chaque transition, pause et
navigation. Pendant une routine au sol, la montre est bien plus pratique que
d'aller chercher le téléphone — c'est tout l'intérêt de la version watchOS.
Chaque exercice écoulé est coché via RoutineStore.toggle, donc renvoyé à
l'iPhone puis au serveur : à la fin le bloc est à jour sur les trois surfaces.
La durée par exercice est désormais transmise dans le snapshot (duration_sec,
optionnel pour rester compatible avec les snapshots déjà persistés) : la vue
guidée utilise la vraie durée au lieu de répartir celle du bloc.
RoutineGuidedView.swift est déjà référencé dans le pbxproj (target CoachWatch),
aucun Add Files à faire. Backup : project.pbxproj.bak-guided
NON COMPILÉ — à inclure au prochain build Xcode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pendant natif du commit coach_sportif 01bad9e. Les 24 exercices sont
cochables un par un pendant la séance, comme sur le web.
- RoutineExercise + items dans RoutineBlock ; les exercices voyagent avec le
snapshot, rien n'est codé en dur côté watchOS
- l'en-tête de bloc est tappable : coche/décoche tous ses exercices d'un coup,
pour enchaîner une série sans s'arrêter à chaque ligne
- compteur n/N par bloc + progression globale en exercices
- CoachRoutineBridge.sanitizeBlocks transmet désormais les items
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pendant natif du backend coach_sportif (commit 772385b, déjà en prod).
- CoachRoutineBridge (target App) : pousse blocs + état du jour vers la Watch
via updateApplicationContext, relaie les coches Watch vers /api/routine/day
puis notifie la WebView (event routineUpdated)
- CoachWatch/RoutineStore : état persisté en UserDefaults, reset au changement
de jour. Les blocs viennent de l'iPhone — rien codé en dur côté watchOS, donc
modifier la routine côté web ne demande aucun rebuild
- CoachWatch/RoutineView : liste cochable, progression, haptique, bouton de
renvoi si la synchro a échoué
- ConnectivityManager (watch) : sendRoutine() en sendMessage avec repli
transferUserInfo (coche faite iPhone hors de portée -> livraison différée)
⚠️ CoachLiveBridge touché : routeIfNotLiveSample() aiguille les messages
routineDone vers NotificationCenter. WCSession.delegate est unique côté iPhone,
impossible d'en ajouter un second. Le flux live workout n'est pas modifié mais
doit être re-vérifié au build.
Les 3 nouveaux fichiers sont référencés dans project.pbxproj (bonne target) :
aucun "Add Files" à faire sur le Mac. Runbook : docs/routine-watch-runbook-mac.md
NON COMPILÉ — nécessite une session Xcode sur le Mac mini.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'App Group n'étant pas partagé entre appareils, le snapshot transite par
WatchConnectivity : iPhone CoachWidgetBridge.pushToWatch (updateApplicationContext)
-> CoachWatch/ConnectivityManager reçoit -> App Group de la montre + reload des
complications. Nouvelles complications (CoachWatchWidgets/CoachWatchComplications.swift) :
Forme (accessoryCircular/corner, Gauge) + Séance du jour (accessoryRectangular/inline).
⚠️ Reste Mac : créer la target widget watchOS + App Group/capability + Target
Membership. Runbook complet dans docs/widgets-runbook-mac.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le throttle fixe de 2s ajoutait jusqu'à 2s de latence sur /live vs l'app
Exercice native d'Apple (instantanée). Désormais chaque nouvel échantillon FC
est envoyé immédiatement ; throttle léger 0.5s conservé uniquement pour
calories/distance (anti-saturation BLE).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Logger unified (capturable via simctl) côté watch (envoi) et iPhone (réception)
pour valider le pont WCSession. Hook AUTOSIM (env var) démarre le mode test au
lancement, pour validation automatisée sur simulateur. Pont prouvé E2E sur sim
pair iPhone+Watch : HR identiques watch->iPhone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bouton "Test (FC simulée)" qui envoie des samples synthétiques (FC ~110-150,
calories, distance, durée) via WCSession toutes les 2s, sans démarrer de séance
HealthKit. Permet de valider watch -> iPhone -> /live indépendamment de
l'autorisation Santé (bloquée par un sous-système Santé coincé sur la Watch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Les deux apps s'appelaient "Coach" (iPhone + watch, bundles distincts) -> impossible
d'identifier laquelle régler dans l'app Santé. Le nom distinct permet d'accorder
l'autorisation HealthKit à la bonne app.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Si authorizationStatus(workoutType) == .sharingDenied après requestAuthorization,
on n'essaie pas de démarrer la séance (qui échouerait en HKErrorAuthorizationDenied)
et on affiche un message demandant d'activer Coach dans Réglages → Santé.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Logger os "ch.hypnotruck.coach.watchkitapp/workout" : trace requestAuthorization,
statut de partage, startActivity/beginCollection, transitions de session et
surtout didFailWithError (domaine+code) pour identifier la cause de l'Error(7).
Logger en constante de fichier (Sendable) pour rester accessible aux délégués
nonisolated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Garde anti double-démarrage (guard !isRunning, session == nil) pour éviter la
cascade de HKLiveWorkoutBuilder en Error(7) quand on retape l'activité. Reset
session/builder à nil après échec, fin et erreur de séance pour permettre une
nouvelle tentative propre. Le statusMessage d'erreur reste donc visible sur le
picker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Asset catalog mono-taille 1024 (source resources/icon.png, comme l'app iOS),
ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon. Vérifié: Assets.car + CFBundleIconName
dans l'Info.plist final. Sans icône, l'upload TestFlight est refusé.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sans cette clé (ou WKWatchOnly), iOS rejette l'install d'une app watch compagnon
(erreur "WKCompanionAppBundleIdentifier missing or malformed"), sur simulateur
comme sur device/TestFlight. Détecté au smoke test simctl install. Pointe vers
le bundle iOS ch.hypnotruck.coach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CoachWatchApp: WKApplicationDelegateAdaptor active WCSession au boot, injecte les
managers. ContentView: picker 4 activités (course/vélo/marche/renfo) -> vue live
(FC/calories/distance/durée + état liaison iPhone) -> bouton Terminer. Build OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Classe @MainActor (implicitement Sendable) ; callbacks HealthKit nonisolated qui
calculent les valeurs hors-main puis hoppent via Task { @MainActor }. Récupère le
builder depuis la session passée au délégué (plus de capture de self non-Sendable).
Build watchsimulator : 0 warning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HKWorkoutSession(healthStore:configuration:) + associatedWorkoutBuilder +
HKLiveWorkoutDataSource. Collecte FC (mostRecent), calories/distance (sum) via
statistics(for:), publication @Published sur main, envoi throttlé (2s) vers
ConnectivityManager. Clôture async endCollection(at:)/finishWorkout(). Build OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sendMessage si iPhone joignable, sinon buffer + transferUserInfo (livraison
différée). Délégué WCSession hors-main, hop vers main pour @Published isReachable,
buffer sérialisé sur file dédiée. Build watchsimulator OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>