diff --git a/COWORK.md b/COWORK.md index 6a537a5..48fe1b9 100644 --- a/COWORK.md +++ b/COWORK.md @@ -190,6 +190,45 @@ de temps reste à faire**. ⚠️ Et jamais un `Timer` de vue SwiftUI : il ne tourne pas écran éteint, donc pendant l'essentiel d'une séance (même famille de piège que `VmaTestView`). +## 📌 Point du 2026-08-21 — build OK, vérifs GPS EN SUSPENS + +**Build Xcode passé sur iPhone (iOS 26.6)**, après un `git pull` qui partait de +`be1ffec` : le Mac n'avait donc **ni `LocationTracker`, ni `RouteFilter`, ni le +correctif du doublon pbxproj** (`19fbfd1`) — le build du 20/08 ne les contenait +pas. Contrôles avant build : 2/2/2 déclarations, `UIBackgroundModes = [location]` +et `NSLocationWhenInUseUsageDescription` présents, un seul `@main` par cible. + +**Une erreur de compilation, corrigée (`3e657f5`)** : +`finishRoute(with:)` **n'accepte pas d'optionnel** — vérifié dans la doc Apple : +`func finishRoute(with workout: HKWorkout, metadata:)`, « You must have already +saved this workout to the HealthKit store ». Le commentaire du fichier +promettait une trace « sauvegardée sans association » : **c'est impossible**, +aucune API ne clôt une route orpheline. Traité en amont : quand +`finishWorkout()` rend `nil` (montre verrouillée), la séance EST dans HealthKit +et `WorkoutManager.recentlySavedWorkout()` va la rechercher — dernier workout de +`HKSource.default()` **croisant** les 5 dernières minutes (chevauchement, jamais +`startDate` : filtrer sur le début raterait toute séance longue). Sinon +`discardRoute()` jette la trace explicitement et le journalise comme une perte. + +⏳ **Les 4 vérifications au poignet n'ont PAS encore été faites** — la séance +doit être lancée **depuis l'app sur la montre**, les logs du 21/08 ne montrent +que l'app iPhone (sync HealthKit complète et saine : 7 séances, 7 traces, 13 +scores d'effort, App Group en écriture `ok:true`). + +Relevé au passage, non traité : avertissement `UIScene lifecycle will soon be +required` (Capacitor, échéance future) ; un échantillon de pas venant de +`"iPhone de …"` et non de la Watch — à surveiller, **la cadence étant dérivée +des pas relus par séance**, deux sources sur une même séance la fausseraient. + +## 🖥️ Vues d'entraînement au choix (WorkOutDoors) — cadré le 21/08, rien codé + +Cadrage complet : **`docs/watch-training-screens.md`**. En deux lignes : +`LiveWorkoutView` a **cinq métriques en dur**, aucune notion de champ ni de +page. Décision prise : la configuration s'édite **côté web** (réutiliser +`TileManager`) et voyage par `WCSession` — donc **aucun rebuild pour changer ses +écrans**. Le modèle et le catalogue sont du Foundation pur, donc écrits et +testés sur Linux ; seul le rendu `TabView` exige Xcode. Châssis ≈ 1 jour. + ## 👉 À 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. diff --git a/docs/watch-training-screens.md b/docs/watch-training-screens.md new file mode 100644 index 0000000..e2d1f12 --- /dev/null +++ b/docs/watch-training-screens.md @@ -0,0 +1,107 @@ +# Vues d'entraînement au choix sur la montre — cadrage du 2026-08-21 + +Chantier **non commencé**. Ce document fixe ce qui a été établi le 21/08 pour +qu'une session ultérieure reprenne sans refaire l'analyse. + +Objectif : des écrans de séance configurables façon **WorkOutDoors** — plusieurs +pages, des champs au choix, un profil par sport. + +## Point de départ réel (vérifié, pas supposé) + +`LiveWorkoutView` (`ios/App/CoachWatch/ContentView.swift`, ~l.392-441) est un +`ScrollView` avec **cinq métriques écrites en dur** : FC + zone, calories, +distance, vitesse km/h, durée — puis l'état iPhone et les boutons +Pause / Terminer. + +**Il n'existe aujourd'hui aucune notion de champ, de page, ni de configuration.** +Tout est à créer. + +## Les quatre briques manquantes + +### 1. Un catalogue de champs + +Chaque métrique doit devenir une valeur identifiable (`id` stable, libellé, +unité, couleur, formatage) et non une ligne de vue. C'est ce qui permet à une +page de déclarer `["hr", "pace", "distance"]` sans que la vue connaisse les +champs à l'avance. + +**Disponibles immédiatement, aucune collecte nouvelle :** + +| Champ | Source | +|---|---| +| FC + zone | `WorkoutManager.heartRate` + `ConnectivityManager.zones` | +| Calories | `WorkoutManager.activeEnergyKcal` | +| Distance | `WorkoutManager.distanceMeters` | +| Vitesse km/h | `WorkoutManager.speedKmh` | +| Durée | `WorkoutManager.elapsedSec` | +| **Allure min/km** | dérivée de `speedKmh` (rien à collecter) | +| **D+ / D−** | `LocationTracker.ascentMeters` / `descentMeters` | +| **Précision GPS** | `LocationTracker.horizontalAccuracy` | +| **Étape du fractionné** | `IntervalEngine`, dès qu'il est câblé (cf. COWORK) | + +**Demandent du travail :** + +- **FC moyenne** et **temps passé par zone** : rien ne les accumule aujourd'hui. +- **Cadence** : non collectée sur la montre. +- **Puissance de course** (`runningPower`, watchOS 9+). +- **Tours / laps** : aucun `HKWorkoutEvent` posé aujourd'hui. + +### 2. Un modèle de page + +``` +WorkoutScreenConfig { sport: String, pages: [WorkoutPage] } +WorkoutPage { id: String, fields: [String] } +``` + +Rendu par un `TabView` paginé (défilement vertical + Digital Crown, watchOS 10+). +1 à 4 champs par page selon la taille d'écran — l'Ultra en tient davantage. + +💡 **Ce modèle est du Foundation pur** : il s'écrit et se teste **sur Linux** +(`./tests-linux/run.sh`), comme `IntervalEngine` et ses 17 tests. Seul le rendu +SwiftUI exigera Xcode. + +### 3. Où l'utilisateur choisit — décision prise le 21/08 + +**La configuration s'édite côté web et se pousse à la montre.** Pas d'écran de +réglages sur la montre. + +- `TileManager` (long-press, drag, toggle) existe déjà côté web : le paradigme + est là, éprouvé sur les tuiles. +- Même chemin de transport que la routine et les zones : `WCSession` → + `ConnectivityManager`. +- **Conséquence décisive** : changer ses écrans ne demanderait **aucun rebuild + Xcode**. C'est exactement ce qui rend la routine agréable aujourd'hui — « les + blocs voyagent avec l'état, rien n'est codé en dur côté watchOS ». +- Un **profil par sport** (course ≠ vélo ≠ renfo), comme WorkOutDoors. + +⚠️ **Repli obligatoire** : sans configuration reçue, la montre affiche les cinq +champs actuels. Jamais d'écran vide — la config peut ne pas être arrivée, et une +séance ne s'interrompt pas pour ça. + +### 4. Deux contraintes watchOS, à tenir dès la conception + +- **Le CPU tue le GPS** (piège n°4 documenté dans `LocationTracker.swift`) : + watchOS suspend une app trop gourmande et les positions s'arrêtent **sans + aucune erreur**. ⇒ rafraîchissement **1 Hz maximum**, et ne recalculer **que + la page visible**, jamais les quatre. Ne pas utiliser un `Timer` de vue. +- **Écran always-on** : en luminance réduite (`@Environment(\.isLuminanceReduced)`), + espacer les mises à jour, sinon la batterie fond sur une sortie longue. + +## Effort estimé + +| Lot | Estimation | +|---|---| +| Châssis (catalogue + modèle + config web + push + `TabView`) | ~1 jour | +| Chaque métrique dérivée supplémentaire | 30 min à 2 h | +| Always-on propre | ½ jour | + +## Ordre proposé + +1. **Modèle + catalogue en Foundation pur**, testés sur Linux — sans Mac. +2. **Endpoint serveur + éditeur web** (réutiliser `TileManager`). +3. **Push `WCSession`** et publication dans `ConnectivityManager`. +4. **Rendu `TabView`** dans `LiveWorkoutView`, avec le repli en dur. +5. Métriques accumulées (FC moyenne, temps en zone) — chacune isolément. + +⚠️ Ne pas commencer par le rendu : c'est la seule partie qui exige le Mac, et +elle ne se juge qu'une fois les données au bon format.