Commit Graph

30 Commits

Author SHA1 Message Date
Sylvain Bettinelli
eb819b68ca Watch : afficher la zone cardiaque pendant la séance
L'app Coach de la montre n'affichait que la FC brute — aucune notion de zone
dans `ContentView.swift`. C'est pourtant au poignet que « 142 bpm » a besoin
d'être traduit ; l'écran iPhone portait déjà l'information, mais on ne le
regarde pas en courant.

- `HeartRateZones.swift` : modèle partagé (App + CoachLiveActivity + les deux
  cibles Watch), câblé dans les 4 phases Sources du pbxproj comme
  `CoachWidgetSnapshot.swift`. ⚠️ Les bornes viennent de `/api/cardiac-zones`,
  version FIGÉE et datée — jamais recalculées côté natif, ce serait rouvrir les
  jeux de zones concurrents supprimés le 12/08.
- `zone(for:)` retient la zone la plus HAUTE qui contient la valeur : les
  bornes se chevauchent d'un battement (Z1 100-109, Z2 109-118), et 109 bpm
  doit se lire Z2, pas Z1. Au-delà du haut de Z5, on reste en Z5 — un effort
  maximal n'est pas une absence de zone.
- `ConnectivityManager` publie les zones reçues avec le snapshot et repart du
  dernier connu au lancement (la montre a pu redémarrer). Un snapshot partiel
  n'efface pas celles en place.
- `HeartRateMetric` : pastille « Z3 », valeur colorée, et la plage sous le
  chiffre. ⚠️ La couleur ne porte jamais l'information seule — illisible pour
  un daltonien, et le contraste d'un écran de montre en plein soleil ne se
  prête pas aux nuances. Libellé VoiceOver explicite.

`switch` en `return` explicites plutôt qu'en expression : la syntaxe courte
exige Swift 5.9 pile, et je ne peux pas compiler ici.

⚠️ NON COMPILÉ — build Xcode requis, avec `getSnapshot` (`8a25f36`) en attente.
2026-08-12 19:13:07 +00:00
Sylvain Bettinelli
a61ba25590 Test de VMA guide au poignet
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>
2026-08-11 18:47:03 +00:00
Sylvain Bettinelli
326e73aaae L'extension LiveActivity etait passee en build 23, seule contre les autres
En corrigeant le build de la nouvelle target CoachWatchWidgets (creee en 1 par
Xcode, refusee a l'installation), la valeur a aussi ete changee sur
ch.hypnotruck.coach.LiveActivity - l'extension qui porte les widgets iPhone, la
Live Activity et le widget Saisie rapide. Elle se retrouvait en 23 quand son app
parente reste en 22.

Verifie : avant cette session, aucune ligne du pbxproj n'etait en 23.

En debug sur device, l'ecart passe. A l'archivage, Apple rejette l'upload pour
CFBundleVersion Mismatch - une extension doit porter le meme CFBundleVersion que
l'app qui la contient. Ca se serait vu dans l'Organizer, apres l'archive, au
moment le plus penible.

Les huit configurations sont ramenees a 22. Au prochain TestFlight, monter les
quatre targets ENSEMBLE (App, CoachWatch, CoachWatchWidgets, LiveActivity) -
c'est ce que l'etape 7 du guide prevoit avec 1.3 / build 23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 14:47:45 +00:00
Sylvain
5984685916 chore(xcode): target CoachWatchWidgets, fichiers du widget Saisie rapide, build 22 aligne 2026-08-11 16:45:51 +02:00
Sylvain
37d52bf18d chore(xcode): signing et capabilities 2026-08-06 11:07:02 +02:00
Sylvain Bettinelli
f032ee2126 feat(watch): séance guidée au poignet
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>
2026-08-03 16:32:29 +00:00
Sylvain
95d2961a56 chore(xcode): App Groups sur App, CoachLiveActivity et CoachWatch 2026-08-03 17:01:53 +02:00
Sylvain Bettinelli
f30cc60da5 chore(xcode): câble les fichiers Swift des widgets aux bonnes targets
Réduit le travail manuel de la session Xcode : CoachWidgetBridge (App),
CoachWidgets (CoachLiveActivity) et CoachWidgetSnapshot (partagé App +
CoachLiveActivity + CoachWatch, un PBXBuildFile par target = ce que fait
"Target Membership") n'étaient référencés nulle part depuis le 2026-06-29.

Reste manuel sur le Mac, car il faut créer une target :
  CoachWatchWidgets/CoachWatchComplications.swift

Backup : project.pbxproj.bak-widgets

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:30:09 +00:00
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
Sylvain
728d7fb288 chore(ios): link GoogleSignIn to App target 2026-06-05 08:42:31 +02:00
Sylvain
120adbed26 chore(ios): GoogleSignIn SPM 2026-06-05 08:38:47 +02:00
Sylvain Bettinelli
947d76714e feat(ios): plugin natif Sign in with Google (CoachGoogleAuth)
- CoachGoogleAuth.swift : plugin Capacitor (SDK GoogleSignIn) → signIn() renvoie
  { idToken, email?, givenName? }, POSTé sur /auth/google/web (aud = client iOS).
  Garde #if canImport(GoogleSignIn) → le repo build même avant l'ajout du SDK.
- MainViewController : enregistre CoachGoogleAuthPlugin.
- AppDelegate : GIDSignIn.sharedInstance.handle(url) dans application(_:open:).
- project.pbxproj : CoachGoogleAuth.swift dans la target App.

⚠️ Xcode (Mac) : ajouter le SDK GoogleSignIn-iOS via SPM + GIDClientID/URL
scheme (reversed client id) dans Info.plist (cf. HANDOFF). Sans le SDK, le
bouton ne s'affiche pas (signIn rejette proprement).
2026-06-05 06:15:17 +00:00
Sylvain Bettinelli
bf975ce8e6 feat(ios): plugin natif Sign in with Apple (CoachAppleAuth)
- CoachAppleAuth.swift : plugin Capacitor (AuthenticationServices) → signIn()
  renvoie { identityToken, user, email?, givenName? } au JS, POSTé sur
  /auth/apple/web (aud = bundle id ch.hypnotruck.coach).
- App.entitlements : capability com.apple.developer.applesignin (Default).
- project.pbxproj : enregistre CoachAppleAuth.swift dans la target App.
- AppDelegate : l'injection du cookie bypass n'écrase plus une session
  existante (sinon le login natif serait remplacé à chaque lancement).

Côté Xcode : ajouter la capability 'Sign in with Apple' puis build device.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:05:52 +00:00
Sylvain
a5795bf83f feat(live): Live Activity / Dynamic Island (WidgetKit extension iOS 16.2+/17+)
Nouvelle target "CoachLiveActivity" (Widget Extension, bundle
ch.hypnotruck.coach.LiveActivity, iOS 17.0) embarquée dans App via copy phase
"Embed App Extensions" (dstSubfolderSpec=13). API ActivityKit + WidgetKit
vérifiées sur SDK iOS 26.5 swiftinterface (Activity.request(content:),
ActivityConfiguration, DynamicIsland(expanded:compactLeading:trailing:minimal:)).

Architecture :
- CoachLiveActivityAttributes (modèle partagé App ↔ extension)
- CoachLiveActivityWidget (Lock Screen + Dynamic Island UI)
- CoachLiveActivityBundle (@main WidgetBundle)
- LiveActivityManager (App side, wrap Activity.request/update/end)
- LiveStore hook : start sur 1er sample, update à chaque ingest, end après
  60s "lost". Tout guardé @available(iOS 16.2, *) pour préserver deployment iOS 15.
- Info.plist App : NSSupportsLiveActivities = YES.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:51:57 +02:00
Sylvain
f65d96e908 feat(live): vue native SwiftUI avec vrai Liquid Glass iOS 26
- LiveStore.swift : bus singleton main-actor, alimenté par CoachLiveBridge,
  observable par SwiftUI. Watchdog 8s pour status .lost.
- CoachLiveView.swift : vue SwiftUI @available(iOS 26.0,*) avec
  .glassEffect()/GlassEffectContainer (vrai Liquid Glass), aurora background,
  4 cartes métriques teintées, pulse heartbeat sur FC en direct.
- CoachLiveBridge : nouvelle méthode plugin openNativeLive() qui présente
  CoachLiveView en plein écran (fallback web pour iOS < 26). emit() alimente
  aussi LiveStore.shared.
- pbxproj : registration des 2 nouveaux .swift dans la target App.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:05:21 +02:00
Sylvain
3c0e266cdc chore(ios): recommended build settings (Xcode) + WKRunsIndependentlyOfCompanionApp=false
- "Update to recommended settings" d'Xcode (CLANG_WARN_*, ENABLE_USER_SCRIPT_SANDBOXING,
  STRING_CATALOG_GENERATE_SYMBOLS, BuildIndependentTargetsInParallel, LastUpgradeCheck)
- Info.plist watch : WKRunsIndependentlyOfCompanionApp=false (app watch dépendante du compagnon)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 21:36:09 +02:00
Sylvain
ef69240af2 chore(watchos): supprime le warning AccentColor manquant
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME vidé sur la target CoachWatch
(pas de couleur d'accent custom, l'UI utilise des couleurs explicites). Build
Release device + Debug sim : 0 warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:15:09 +02:00
Sylvain
ae5ac7ec25 feat(watchos): AppIcon CoachWatch — prérequis TestFlight
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>
2026-05-25 19:09:23 +02:00
Sylvain
b2e9836b95 feat(ios): plugin Capacitor CoachLiveBridge (Phase 3) — relais WCSession -> /live
CAPPlugin + CAPBridgedPlugin (jsName CoachLiveBridge). Active WCSession au load(),
reçoit didReceiveMessage (temps réel) + didReceiveUserInfo (lot bufferisé) et
relaie au JS via notifyListeners("liveSample"). Enregistré dans
MainViewController.capacitorDidLoad(). Méthode getStatus pour debug JS. Build OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:27:15 +02:00
Sylvain
c6f5a7ffc9 feat(watchos): WorkoutManager (HKLiveWorkoutBuilder) — séance live + push iPhone
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>
2026-05-24 16:20:11 +02:00
Sylvain
5864cfcbef feat(watchos): ConnectivityManager (WCSession) — envoi des samples vers l'iPhone
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>
2026-05-24 16:11:58 +02:00
Sylvain
5d60a4049e feat(watchos): target CoachWatch (Phase 1) — app watchOS embarquée
- nouvelle target watchOS app CoachWatch (ch.hypnotruck.coach.watchkitapp), SwiftUI, SDK watchos
- entitlement HealthKit + Info.plist FR (NSHealthShare/UpdateUsageDescription) + WKBackgroundModes workout-processing
- "Embed Watch Content" + dépendance App -> CoachWatch (CoachWatch.app dans App.app/Watch)
- schemes partagés App + CoachWatch
- CoachWatchApp.swift + ContentView.swift minimaux

Build validé: target watchsimulator + embed dans App.app (iphonesimulator).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:09:37 +02:00
Sylvain
ec26ae0ca7 chore(ios): healthkit background-delivery + bump version 1.2
- entitlement com.apple.developer.healthkit.background-delivery (CoachWorkoutObserver)
- registration CoachWorkoutObserver.swift dans la target App
- LSApplicationCategoryType = healthcare-fitness
- MARKETING_VERSION 1.1 -> 1.2, CURRENT_PROJECT_VERSION 2 -> 22

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:50:25 +02:00
Sylvain Bettinelli
8d011610be feat(ios): plugin natif CoachHealthRoute (HKWorkoutRouteQuery)
@capgo/capacitor-health n'expose pas les routes GPS des workouts. Ce
plugin Swift custom appelle HKSampleQuery + HKWorkoutRouteQuery pour
récupérer la liste des CLLocation associées à un workout (par UUID).

API JS :
  CoachHealthRoute.getRoute({workoutUUID})
    → {available: bool, route: [{latitude, longitude, altitude, timestamp, speed}], count}

Format payload aligné sur HAE pour réutiliser le parser backend
existant (tools/health_parser.py).
2026-05-11 14:25:37 +00:00
Sylvain Bettinelli
a251d369df Bump v1.1 (build 2) + ITSAppUsesNonExemptEncryption=false pour TestFlight
Préparation upload TestFlight v1.1 avec :
- 12 phases d'évolution depuis v1.0 du 7 mai (bilan IA hebdo, /calendar
  refonte vue semaine, /workouts page, /profile, plan additions custom,
  WorkoutKit Apple Watch 6 commits, builder chips blocs interactif)
- MARKETING_VERSION 1.0 → 1.1 (visible côté user TestFlight)
- CURRENT_PROJECT_VERSION 1 → 2 (build number, doit être strictement
  croissant côté Apple)
- ITSAppUsesNonExemptEncryption=false : évite le questionnaire crypto
  au moment de l'upload (l'app n'utilise pas d'algorithmes hors crypto
  iOS standard)
2026-05-08 10:54:26 +00:00
Sylvain Bettinelli
83b5260bc1 Plugin discovery : MainViewController sous-classe CAPBridgeViewController
Capacitor 8 n'auto-découvre pas les plugins in-app Swift (qui ne sont pas
des npm packages). Les plugins définis dans le target App doivent être
enregistrés manuellement via une sous-classe de CAPBridgeViewController
qui override capacitorDidLoad().

Ajouts :
- ios/App/App/MainViewController.swift : sous-classe qui registers
  CoachWorkoutKitPlugin() dans capacitorDidLoad()
- Main.storyboard : customClass pointe sur MainViewController (module
  'App' target) au lieu de CAPBridgeViewController (module Capacitor)
- project.pbxproj : 4 entrées pour MainViewController.swift (BuildFile,
  FileReference, group, Sources phase)

Après rebuild, Capacitor.Plugins.CoachWorkoutKit sera dispo en JS.
2026-05-08 10:05:06 +00:00
Sylvain Bettinelli
2d3e149c0a Plugin natif WorkoutKit : pousse workouts intervalles vers Apple Watch
Plugin Capacitor Swift in-app (ios/App/App/CoachWorkoutKit.swift) qui utilise
le framework Apple WorkoutKit (iOS 17+ / watchOS 10+) pour construire un
CustomWorkout structuré (warmup + IntervalBlock work/rest répété + cooldown)
et présenter la sheet système iOS qui propose à l'user d'ajouter le workout
à ses workouts personnalisés Apple Watch.

API JS (côté WebView) :
  Capacitor.Plugins.CoachWorkoutKit.isAvailable()
  Capacitor.Plugins.CoachWorkoutKit.sendInterval({
    activity, displayName, warmupMin, work, rest, repeats, cooldownMin
  })

Activity supportées : running / cycling / walking / hiking
HR zones (1-5) optionnelles via HeartRateZoneAlert sur work + rest steps.

Workflow attendu user :
1. coach.hypnotruck.ch → /settings ou bouton 'Envoyer Apple Watch' depuis /calendar
2. JS appelle WK.sendInterval(...)
3. Plugin Swift construit CustomWorkout + IntervalBlock + alerts
4. WorkoutScheduler.shared.preview(plan) ouvre la sheet iOS native
5. User tape 'Ajouter aux workouts' → workout dispo dans Apple Watch
   → Exercice → Course → Workouts personnalisés → [displayName]

Build à faire sur le Mac (cap sync ios + Xcode build → install iPhone).

Phase 1 = MVP plugin + bouton test sur /settings de coach.hypnotruck.ch.
Phase 2 = bouton 'Envoyer Apple Watch' sur card du jour /calendar (convertit
la séance prévue en CustomWorkout).
Phase 3 = bibliothèque templates intervalles (10x400m, fartlek, etc.).

Project.pbxproj : ajouté 4 entrées (PBXBuildFile + PBXFileReference + group +
Sources phase) avec IDs D1BD5B1990CF3F2B9C5D000{1,2}.
2026-05-08 09:28:15 +00:00
Sylvain Bettinelli
04581915ac Generate Android icons + splash variants depuis resources/
@capacitor/assets v3.0.5 → 87 assets Android générés à partir des sources
1024x1024 (icon) et 2732x2732 (splash) déjà présentes :
- mipmap-{m,h,xh,xxh,xxxh}dpi : ic_launcher.png + _round + _foreground
  (foreground/background layers pour adaptive icons Android 8+)
- drawable-{land,port}-{l,m,h,xh,xxh,xxxh}dpi : splash.png
- mipmap-anydpi-v26 : ic_launcher.xml et ic_launcher_round.xml
  (XML adaptive icon refs vers les layers PNG)

iOS pbxproj : strip leading zero `0920` → `920` (effet de bord du tool, pas
de comportement Xcode modifié — juste format normalisé).

Reste assets marketing pour Play Console (Feature Graphic 1024x500,
screenshots phone) — à générer au moment du upload, pas dans le repo.
2026-05-07 16:30:35 +00:00
Sylvain Bettinelli
a7809992a9 Enregistre CoachAuth.swift dans project.pbxproj
Évite l'étape manuelle Add Files dans Xcode. Le fichier sera
automatiquement compilé dès qu'il existe dans ios/App/App/CoachAuth.swift.
2026-05-05 19:33:30 +00:00
Sylvain
6e3f74209b Init iOS project (cap add ios) avec privacy keys et entitlements 2026-05-05 21:29:17 +02:00