Files
coach-ios/docs/routine-watch-runbook-mac.md
Sylvain Bettinelli caeac37f38 feat(watch): routine quotidienne cochable sur l'Apple Watch
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>
2026-08-03 14:22:33 +00:00

5.5 KiB

Runbook Mac — Routine quotidienne sur l'Apple Watch

Écrit côté dev (Linux) le 2026-08-03. Rien n'est compilé. Ce document dit quoi faire sur le Mac pour activer la fonctionnalité, et comment la tester.

Ce que ça fait

La routine anti-douleurs (6 blocs) apparaît sur la montre, cochable au doigt. Une coche faite sur la Watch remonte à l'iPhone, qui la POSTe au serveur — donc la page /routine et le widget de la home reflètent immédiatement l'état, et inversement.

Architecture (à connaître avant de débugger)

   page /routine (web)                    Apple Watch (RoutineView)
        │  coche                                │  coche
        ▼                                       ▼
   POST /api/routine/check              ConnectivityManager.sendRoutine
        │                                       │  sendMessage / transferUserInfo
        ▼                                       ▼
   data/routine_log.json  ◄──────────  CoachLiveBridge (délégué WCSession)
        ▲   POST /api/routine/day               │  routeIfNotLiveSample()
        │                                       ▼
        └───────────────────────  CoachRoutineBridge (NotificationCenter)
                                                │  notifyListeners
                                                ▼
                                     routine.js re-render

Trois décisions structurantes, à ne pas défaire sans raison :

  1. L'état est serveur, plus en localStorage. La Watch n'a aucun accès au localStorage du WKWebView : il fallait un arbitre commun aux trois surfaces.
  2. On transmet l'état COMPLET du jour, jamais un delta. Si un message WatchConnectivity se perd, le suivant recale tout au lieu de laisser les deux côtés diverger. C'est aussi pour ça que updateApplicationContext convient : le système n'en garde que le dernier, ce qui est la sémantique voulue.
  3. CoachRoutineBridge n'est PAS WCSession.delegate. CoachLiveBridge l'est déjà et le délégué est unique. CoachLiveBridge.routeIfNotLiveSample() aiguille les messages routineDone vers NotificationCenter — c'est le seul endroit où j'ai touché au flux live workout (validé E2E sur device le 31/05), et il retourne true sans rien émettre, donc LiveStore n'est pas pollué.

Les blocs eux-mêmes voyagent avec l'état : rien n'est codé en dur côté watchOS. Modifier la routine côté web ne demande donc aucun rebuild.

Étapes Mac

1. Récupérer le code

# Xcode FERMÉ (règle COWORK)
cd ~/coach-ios && git pull
npm install && npx cap sync ios
open ios/App/App.xcworkspace

2. Rien à ajouter dans Xcode

Les trois nouveaux fichiers sont déjà référencés dans project.pbxproj, dans la bonne target :

Fichier Target
App/CoachRoutineBridge.swift App
CoachWatch/RoutineStore.swift CoachWatch
CoachWatch/RoutineView.swift CoachWatch

Vérification rapide si un doute :

grep -c "RoutineView.swift" ios/App/App.xcodeproj/project.pbxproj   # doit valoir 4

(4 = PBXBuildFile + PBXFileReference + children du groupe + Sources phase.)

Backup du pbxproj avant modification : project.pbxproj.bak-routine.

3. Build

Clean Build Folder (Cmd+Shift+K) → Run sur l'iPhone, avec la Watch appairée.

⚠️ CoachAuth.swift doit exister (gitignored) : le plugin s'en sert pour authentifier son POST vers /api/routine/day. Sans lui, ça ne compile pas.

4. Tester le cycle complet

  1. Ouvrir l'app iPhone sur /routine — ça pousse les blocs vers la Watch.
  2. Sur la Watch : app Coach → Routine du jour → la liste des 6 blocs doit apparaître avec la progression.
  3. Cocher un bloc sur la Watch → retour à l'iPhone, la page /routine doit se mettre à jour sans rechargement (event routineUpdated).
  4. Cocher un bloc sur l'iPhone → rouvrir la vue Watch, l'état doit suivre.
  5. Vérifier côté serveur :
    ssh ubuntu@83.228.246.229 'cat /home/ubuntu/coach_sportif/data/routine_log.json'
    

5. Si la Watch affiche « Ouvre l'app Coach sur l'iPhone »

C'est l'état vide normal au premier lancement : la montre n'a pas encore reçu de blocs. Si ça persiste après avoir ouvert /routine sur l'iPhone :

  • console Safari (Développement → iPhone → coach.hypnotruck.ch) :
    await Capacitor.Plugins.CoachRoutineBridge.getStatus()
    
    paired et watchAppInstalled doivent être true.
  • Xcode console, filtre subsystem:ch.hypnotruck.coach category:routine.
  • updateApplicationContext échoue silencieusement si la session n'est pas activée : vérifier que CoachLiveBridge.load() a bien tourné (c'est lui qui active la session).

6. Si une coche Watch ne remonte pas

  • Hors de portée de l'iPhone, c'est normal et attendu : transferUserInfo garantit la livraison différée, la coche partira au prochain rapprochement. La vue affiche un bouton « Renvoyer vers l'iPhone » si l'envoi a échoué net.
  • Sinon : vérifier le log POST /api/routine/day côté Xcode (le plugin logue le code HTTP en cas d'échec — un 401 signifie un CoachAuth.kCoachWebToken périmé).

Reste à faire (hors périmètre de ce chantier)

  • Complication watchOS « Routine » : la target CoachWatchWidgets n'existe toujours pas (cf. docs/widgets-runbook-mac.md).
  • Notification de rappel sur la montre si la routine n'est pas faite à 20 h.