La note de ce matin demandait un « Add Files » pour IntervalEngine. Les trois fichiers du chantier sont désormais déclarés dans la cible CoachWatch directement dans le pbxproj : suivre l'ancienne consigne créerait un doublon. Ajout de l'ordre de vérification au premier build — la trace dans Santé et la non-régression de la FC sur /live sont les deux points qui décident si le chantier tient. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
141 lines
12 KiB
Markdown
141 lines
12 KiB
Markdown
# COWORK — coordination multi-Claude (coach-ios)
|
||
|
||
Ce repo est travaillé **en parallèle par 2 instances de Claude** :
|
||
|
||
- **Claude « dev » (serveur Linux, sans Xcode)** : écrit le **code** (Swift des plugins, config projet) + le backend/web (repo `coach_sportif`). **Ne peut PAS builder iOS** (pas de Xcode).
|
||
- **Claude « Cowork » (app Claude sur le Mac mini de Sylvain)** : fait les **builds Xcode, la signature, les tests sur device**. → C'est toi si tu lis ça depuis le Mac.
|
||
|
||
Les deux ne communiquent **que via git + ce fichier**. ⚠️ La mémoire de chaque Claude est **locale à sa machine** (non partagée) → **l'état partagé, c'est CE fichier** : tiens-le à jour.
|
||
|
||
## 🔑 Règles d'or (pour ne pas se bloquer)
|
||
1. **`git pull` AVANT de toucher au projet** ; **`git push` après chaque changement.**
|
||
2. ⚠️ **Ne jamais laisser Xcode ouvert pendant un `git pull`** — Xcode garde/réécrit le `pbxproj` en mémoire → fichiers Swift non compilés + conflits (ça nous a bloqués 2× le 2026-06-05). Quitter Xcode (`Cmd+Q`), pull, rouvrir.
|
||
3. Après toute modif faite **dans Xcode** (package SPM, capability, signing) → **commit + push `project.pbxproj` (+ `Package.resolved`) immédiatement**, sinon le prochain `git pull` est bloqué par les modifs locales.
|
||
4. Zone sensible = `ios/App/App.xcodeproj/project.pbxproj` : le Claude dev y ajoute les fichiers Swift (entrées manuelles), le Claude Mac y ajoute les packages SPM. → committer de suite limite les collisions.
|
||
|
||
## 🛠️ Workflow type « le dev a poussé du code natif, build-le »
|
||
1. **Quitter Xcode.**
|
||
2. `git pull`
|
||
3. Nouveau `*.swift` ? vérifier qu'il est dans le projet : `grep -c <Nom>.swift ios/App/App.xcodeproj/project.pbxproj` (doit être ≠ 0). Si Xcode ne le voit pas après ouverture → clic droit dossier **App** → *Add Files to "App"…* (décocher *Copy items*, cocher target **App**).
|
||
4. Ouvrir Xcode → **Clean Build Folder** (`Cmd+Shift+K`) → **Run** (`Cmd+R`) sur l'iPhone.
|
||
5. Erreurs → corriger, committer le fix, push.
|
||
6. Diagnostic plugins : Safari → Développement → iPhone → coach.hypnotruck.ch → console → `Object.keys(Capacitor.Plugins).filter(k=>k.indexOf('Coach')===0)`.
|
||
|
||
## 📍 État natif actuel (2026-06-05)
|
||
- **Capacitor 8 n'auto-découvre PAS les plugins in-app** → tous enregistrés dans `MainViewController.capacitorDidLoad()` : CoachWorkoutKit, CoachHealthRoute, CoachWorkoutObserver, CoachLiveBridge, **CoachAppleAuth**, **CoachGoogleAuth**.
|
||
- **Login natif Apple + Google = OK, validé device.** Apple via `AuthenticationServices` (intégré). Google via **SDK GoogleSignIn** (SPM, lié à la target App) + `GIDClientID`/URL scheme dans Info.plist. Les deux POSTent l'id_token sur `/auth/apple|google/web` (backend) → cookie de session.
|
||
- watchOS `CoachWatch` + Live Activity en place (chantiers antérieurs, cf. `HANDOFF-WATCHOS.md`).
|
||
- **Widgets natifs iPhone + complication Watch (2026-06-29)** : code écrit côté dev, **reste le câblage Mac**.
|
||
- iPhone : plugin `CoachWidgetBridge` (enregistré dans `capacitorDidLoad()`), modèle partagé `CoachWidgetSnapshot.swift`, widgets `CoachWidgets.swift` (Séance du jour + Score de forme) dans l'extension `CoachLiveActivity`. App Group `group.ch.hypnotruck.coach`. **Hook web déjà en prod** (`widget-bridge.js`).
|
||
- Watch : `CoachWidgetBridge.pushToWatch` (WCSession) → `CoachWatch/ConnectivityManager` reçoit → App Group montre + reload. Complications `CoachWatchWidgets/CoachWatchComplications.swift` (Forme circular/corner, Séance rectangular/inline). **Nouvelle target widget watchOS à créer dans Xcode.**
|
||
- ⚠️ Étapes Mac obligatoires (App Group portail + capabilities + Target Membership + target Watch + build) : **TOUT est détaillé dans `docs/widgets-runbook-mac.md`.**
|
||
|
||
## 🆕 Routine quotidienne sur la Watch (2026-08-03, dev — non compilé)
|
||
|
||
Nouveau chantier, code complet côté dev, **build Mac requis**. Tout est détaillé
|
||
dans **`docs/routine-watch-runbook-mac.md`** (archi, étapes, débogage).
|
||
|
||
- **Nouveaux fichiers, DÉJÀ référencés dans `project.pbxproj`** (aucun « Add
|
||
Files » à faire) : `App/CoachRoutineBridge.swift` (target App),
|
||
`CoachWatch/RoutineStore.swift` + `CoachWatch/RoutineView.swift` (target
|
||
CoachWatch). Backup : `project.pbxproj.bak-routine`.
|
||
- **Modifs** : `MainViewController.capacitorDidLoad()` enregistre
|
||
`CoachRoutineBridgePlugin` ; `CoachWatch/ContentView` ouvre `RoutineView` en
|
||
sheet ; `ConnectivityManager` (watch) gagne `sendRoutine()` + réception du
|
||
`routineSnapshot`.
|
||
- ⚠️ **`CoachLiveBridge` touché** — un seul ajout : `routeIfNotLiveSample()`
|
||
aiguille les messages `routineDone` vers `NotificationCenter` au lieu de les
|
||
émettre comme samples. Le délégué `WCSession` est unique côté iPhone, on ne
|
||
pouvait pas en ajouter un second. **À re-vérifier au build** que le live
|
||
workout (validé E2E le 31/05) fonctionne toujours.
|
||
- Côté backend `coach_sportif` : **déjà en prod** (`/api/routine`,
|
||
`/api/routine/check`, `/api/routine/day`, `data/routine_log.json`).
|
||
|
||
## 🔗 Universal Links (2026-08-06, dev — non compilé)
|
||
|
||
Constat : **aucun** `.widgetURL` ne ramenait dans l'app. La Live Activity
|
||
(`CoachLiveActivityWidget`), les deux widgets iPhone (`CoachWidgets` → `/calendar`
|
||
et `/forme`) et les deux complications Watch pointent tous sur des URL
|
||
`https://coach.hypnotruck.ch/…`, mais l'entitlement `associated-domains` était
|
||
absent et `/.well-known/apple-app-site-association` renvoyait 404 → **un tap
|
||
ouvrait Safari**, où le cookie d'auth injecté dans la WKWebView n'existe pas :
|
||
écran de login.
|
||
|
||
- **Backend — déjà en prod** (commit `8ec0047`) : route AASA publique déclarant
|
||
`TZ2PVTRKNY.ch.hypnotruck.coach` sur `/live`, `/routine`, `/forme`,
|
||
`/calendar` (chemins énumérés un par un pour ne pas détourner
|
||
`/calendar.ics`), et `static/deeplink.js` qui écoute `appUrlOpen` et amène la
|
||
WebView sur le bon chemin — Capacitor ne le fait pas tout seul.
|
||
- **Côté ce repo** : `App.entitlements` gagne
|
||
`com.apple.developer.associated-domains = applinks:coach.hypnotruck.ch`.
|
||
- ⚠️ **Étape Mac** : ouvrir la cible **App** → *Signing & Capabilities* →
|
||
vérifier qu'**Associated Domains** apparaît avec `applinks:coach.hypnotruck.ch`
|
||
(l'entitlement est là, Xcode doit régénérer le profil ; si erreur de signing,
|
||
décocher/recocher *Automatically manage signing*). Puis rebuild.
|
||
- Effet attendu : tap sur la Live Activity → app → `/live` → la vue Liquid Glass
|
||
`CoachLiveView` s'ouvre d'elle-même (le JS de `/live` appelle
|
||
`openNativeLive`). C'est le point d'entrée qui manquait.
|
||
|
||
## 🆕 Chantier montre outdoor (2026-08-20, dev — non compilé)
|
||
|
||
Branche **`feat/watch-outdoor`**, commits `f2fa3cc` (moteur d'intervalles) et
|
||
`a04dfc4` (trace GPS). `IntervalEngine` guide les intervalles d'une séance au
|
||
poignet ; `RouteFilter` + `LocationTracker` enregistrent le parcours.
|
||
|
||
**Pourquoi ce code existe** : WorkoutKit ne sait pas exécuter une séance
|
||
structurée dans une app tierce — son seul point d'exécution public est
|
||
`WorkoutPlan.openInWorkoutApp()`, qui ouvre l'app Exercice d'Apple. Il faut donc
|
||
écrire la machine à états nous-mêmes.
|
||
|
||
**Le moteur est volontairement pur** : aucun HealthKit, aucun timer, aucune
|
||
horloge interne. Il répond à « où en sommes-nous ? » à partir du temps et de la
|
||
distance qu'on lui pousse. Le temps de référence est `elapsedTime` du builder,
|
||
qui exclut déjà les pauses.
|
||
|
||
✅ **57 tests Swift verts sur Linux** (`./tests-linux/run.sh`, toolchain
|
||
`~/workspace/toolchains/bin`) : 17 pour `IntervalEngine`, 15 pour `RouteFilter`.
|
||
Les deux ont été **vérifiés rouges** en neutralisant leur correction (5 échecs
|
||
sans les frontières théoriques du moteur, 4 sans le lissage du dénivelé). La
|
||
logique est donc validée — reste l'intégration, que Linux ne peut pas compiler.
|
||
|
||
✅ **Plus d'« Add Files » à faire** — correction du 2026-08-20 après-midi. Une
|
||
note antérieure de ce fichier annonçait `IntervalEngine` absent du projet : ce
|
||
n'est plus vrai. Les **trois** fichiers sont déclarés dans la cible `CoachWatch`
|
||
directement dans le `pbxproj` (sauvegarde `project.pbxproj.bak-outdoor`).
|
||
|
||
Contrôle avant de builder — chacun doit rendre **2** (déclaration + phase de
|
||
compilation, soit 1 cible) :
|
||
```bash
|
||
for f in IntervalEngine RouteFilter LocationTracker; do
|
||
echo "$f: $(grep -c "$f.swift in Sources" ios/App/App.xcodeproj/project.pbxproj)"
|
||
done
|
||
```
|
||
|
||
⚠️ **À vérifier au premier build, dans cet ordre** :
|
||
1. **Une séance extérieure demande la localisation** (feuille « lorsque l'app
|
||
est active »). Si rien n'apparaît, la trace ne partira pas.
|
||
2. **La sortie a bien un parcours dans Santé** à la fin. C'est le test qui
|
||
compte : `finishRoute` doit être appelé APRÈS `finishWorkout`, sinon la
|
||
trace existe sans être associée à la séance.
|
||
3. **La FC remonte toujours sur `/live`** — `WorkoutManager` a été modifié,
|
||
c'est le test de non-régression prioritaire.
|
||
4. Écran éteint, poignet baissé : les positions continuent d'arriver. Si elles
|
||
s'arrêtent, chercher du côté du CPU (une boucle d'affichage trop rapide fait
|
||
suspendre l'app), pas du côté des autorisations.
|
||
|
||
⚠️ `Info.plist` de `CoachWatch` gagne `UIBackgroundModes = [location]` et
|
||
`NSLocationWhenInUseUsageDescription`. **La clé de background est vitale** :
|
||
armer `allowsBackgroundLocationUpdates` sans elle termine l'app. Un garde-fou
|
||
la vérifie au démarrage, mais si elle disparaissait du build, le suivi
|
||
retomberait silencieusement au premier plan seul.
|
||
|
||
## 👉 À reprendre
|
||
- ✅ **Phase 4 login — FAIT & DÉPLOYÉ** (révocation Apple à la suppression de compte) : code complet côté backend `coach_sportif` (commit `5ae6e2d`) + clé `.p8` déployée sur le VPS prod (vérifié 2026-06-26). Rien à coder. Détails : `coach_sportif/COWORK.md`.
|
||
- **iOS — bloqué Mac** : builder + uploader TestFlight le natif accumulé (login Apple+Google natif, watchOS live, HeartRateRangeAlert). Sur le Mac : `cd ~/coach-ios && git pull && npm install && npx cap sync ios && open ios/App/App.xcodeproj` → Clean Build Folder → Archive → Upload.
|
||
- ⚠️ **Corrigé le 2026-08-20** : cette ligne disait `open ios/App/App.xcworkspace`. **Ce fichier n'existe pas** (vérifié : le dépôt ne contient que `App.xcodeproj`). Depuis **Capacitor 8, les dépendances passent par SPM** et il n'y a plus de workspace CocoaPods. La commande échouait donc telle quelle.
|
||
- ⚠️ **Fixes robustesse 2026-07-01 (dev, non compilés)** à valider au prochain build : `CoachWidgetBridge` (score lu en `NSNumber.intValue` → corrige un score float perdu ; `save` renvoie Bool → `call.reject` si App Group KO), `CoachAppleAuth` (double-tap : rejette l'ancien `pendingCall` ; erreur testée par domaine `ASAuthorizationError`). Swift pur, aucun nouveau fichier ni capability.
|
||
- ⚠️ **Checklist pré-App-Store (audit sécu 2026-07-01)** : `ios/App/App/App.entitlements` a `aps-environment = development`. Pour la soumission **App Store/TestFlight**, l'APNs prod exige `production`. NON changé côté dev (casserait le push en dev device — arbitrage de signing à faire sur Mac : soit basculer `production` avant l'archive de distribution, soit laisser Xcode le gérer via le profil de distribution auto). À trancher/tester au moment du build release.
|
||
- **watchOS — bloqué Mac** : test device + TestFlight de la cible `CoachWatch` (Phases 1-3 réalisées, build vert sim). Cf. `HANDOFF-WATCHOS.md`.
|
||
- **Android — bloqué externe** : Play Console ($25 validé), upload AAB + Internal Testing depuis machine avec Android SDK.
|
||
- Backend/web : voir `coach_sportif/COWORK.md`.
|