La règle n°2 protégeait AppDelegate.swift. Depuis l'adoption d'UIScene, trois callbacks ont déménagé et UIKit ne les appelle plus sur l'AppDelegate même si on les y remet — un futur correctif posé au mauvais endroit compilerait sans broncher et casserait l'OAuth ou le flush des saisies widget en silence. Précisé au passage que didFinishLaunchingWithOptions reste appelé avant la connexion de la scène : c'est ce qui garantit l'ordre cookie -> WebView, et c'est documenté par Apple, pas supposé. Deux commandes ajoutées à la liste de ce qu'on ne lance pas sur ce projet : npx cap migrate (le guide officiel exclut les AppDelegate à gestion d'URL personnalisée) et npm audit fix --force (il redescend le CLI en v7, ce qui supprimerait le support UIScene qu'on vient d'ajouter). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
131 lines
8.7 KiB
Markdown
131 lines
8.7 KiB
Markdown
# 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. Mac mini à jour, builds iOS possibles (confirmé 2026-08-19). Reste bloqué : Play Console $25 ~3-5j pour Android.
|
|
|
|
## 🔒 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. Il vit dans `didFinishLaunchingWithOptions`, qui **reste appelé** en cycle de vie par scènes et **avant** la connexion de la scène — Apple documente la séquence, l'ordre cookie → WebView est donc garanti.
|
|
2bis. **⚠️ Cycle de vie UIScene depuis le 22/09/2026** (obligatoire iOS 27, Capacitor ≥ 8.5.2). Trois callbacks ont quitté `AppDelegate.swift` pour `SceneDelegate.swift`, et UIKit **ne les appelle plus sur l'AppDelegate même si on les y remet** : retour OAuth Google (`scene(_:openURLContexts:)`), Universal Links (`scene(_:continue:)`), flush des saisies widget (`sceneDidBecomeActive`). Aucun ne produit d'erreur de compilation s'il est mal placé — la panne est silencieuse. Le `SceneDelegate` instancie **`MainViewController`**, jamais un `CAPBridgeViewController` nu comme le template Capacitor : la sous-classe porte l'enregistrement des plugins et la config WKWebView. ⛔ Ne pas lancer `npx cap migrate` sur ce projet (le guide officiel exclut les AppDelegate à gestion d'URL personnalisée), ni `npm audit fix --force` (il redescend le CLI en v7 et supprime le support UIScene).
|
|
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`
|
|
|
|
## 🧪 Tests Swift sur Linux (`tests-linux/`)
|
|
|
|
Une toolchain **Swift 6.3** est installée sur la machine de dev :
|
|
`/home/coder/workspace/toolchains/bin/swift`. Lancer : `./tests-linux/run.sh`.
|
|
|
|
⚠️ **Ça ne remplace PAS le build Xcode, et ça ne le remplacera jamais.**
|
|
WidgetKit, SwiftUI, HealthKit, WatchKit et UIKit sont des frameworks Apple,
|
|
fermés et propres à leurs plateformes : absents de Swift pour Linux. Ni l'app,
|
|
ni les widgets, ni les vues ne se compilent ici. Pas de `xcodebuild`, pas de
|
|
simulateur, pas de signature, pas d'`.ipa`.
|
|
|
|
**Ce que ça couvre** : les fichiers qui n'importent que Foundation —
|
|
`CoachWidgetSnapshot.swift` (dont la péremption `asOf(_:)`) et
|
|
`CoachQuickLog.swift`. C'est-à-dire l'arithmétique de dates, là où un décalage
|
|
d'un jour ne se voit pas à la relecture.
|
|
|
|
- Les sources du paquet sont des **liens symboliques** vers les vrais fichiers :
|
|
le test porte sur le code livré, pas sur une copie qui dériverait.
|
|
- `HeartRateZonesStub.swift` est une **doublure** : le vrai type expose une
|
|
propriété `Color` et importe SwiftUI. Elle ne reproduit que les propriétés
|
|
stockées, et `asOf` ne fait que les recopier — si la péremption devait un jour
|
|
TOUCHER aux zones, ce test cesserait d'être pertinent.
|
|
|
|
⚠️ `/home/coder/workspace` est le **seul** chemin persistant du conteneur de
|
|
dev ; tout le reste (y compris les dépôts) vit sur une couche éphémère. D'où
|
|
l'emplacement de la toolchain.
|
|
|
|
## 🔗 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~~ — fait, builds iOS opérationnels (2026-08-19)
|
|
- Play Console validation (~3-5j, $25 one-shot) pour upload Android
|
|
- **watchOS live workout compagnon** — Phases 1-3 RÉALISÉES (2026-05-25, sur
|
|
`main`) : target `CoachWatch` + 4 fichiers Swift watchOS + plugin Capacitor
|
|
`CoachLiveBridge`. Builds verts (watchsimulator + App embed), UI validée sur
|
|
sim. **Reste : test device + TestFlight.** Détails et deltas watchOS 26 dans
|
|
`docs/watchos-live-workout-plan.md` (section "Avancement réel").
|
|
- Push workouts vers Apple Watch (HKWorkoutPlan) / Polar (Training Targets API) / Garmin — backlog 2026-05-12
|
|
- **Lancer la séance du jour depuis la complication Watch** — proposé et mis de
|
|
côté le 2026-08-11. État vérifié ce jour :
|
|
- les complications portent déjà un `widgetURL` (`/forme`, `/calendar`), mais
|
|
**`CoachWatch` n'implémente aucun `onOpenURL`** → un tap ouvre l'app sur
|
|
`ActivityPickerView`, pas sur la séance ;
|
|
- la donnée est **déjà sur la montre** : `CoachWidgetSnapshot.TodaySession`
|
|
(sport, titre, durée) y arrive par WCSession, l'app ne s'en sert pas ;
|
|
- travail estimé ~1 h : lire l'URL entrante, mapper `TodaySession.sport` vers
|
|
le `HKWorkoutActivityType` correspondant (les 4 activités de
|
|
`ActivityPickerView`), ouvrir l'écran de démarrage pré-rempli.
|
|
- ⚠️ **Limite Apple** : une complication ne peut PAS ouvrir l'app *Exercice*
|
|
d'Apple sur une séance programmée précise — aucune URL publique. Le
|
|
fractionné structuré envoyé par WorkoutKit se lancera toujours à la main
|
|
depuis *Exercice → Programmées*. Guider les intervalles dans notre app Watch
|
|
serait un chantier d'1 à 2 jours qui doublonnerait l'app d'Apple.
|
|
|
|
## ⚠️ 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).
|