16 Commits

Author SHA1 Message Date
Sylvain Bettinelli
cc88431f6b feat(watch): complications Apple Watch (Forme + Séance du jour)
L'App Group n'étant pas partagé entre appareils, le snapshot transite par
WatchConnectivity : iPhone CoachWidgetBridge.pushToWatch (updateApplicationContext)
-> CoachWatch/ConnectivityManager reçoit -> App Group de la montre + reload des
complications. Nouvelles complications (CoachWatchWidgets/CoachWatchComplications.swift) :
Forme (accessoryCircular/corner, Gauge) + Séance du jour (accessoryRectangular/inline).

⚠️ Reste Mac : créer la target widget watchOS + App Group/capability + Target
Membership. Runbook complet dans docs/widgets-runbook-mac.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 05:50:29 +00:00
Sylvain
5a1568ef2f perf(watchos-live): FC poussée en temps réel à chaque échantillon (throttle 2s -> envoi immédiat)
Le throttle fixe de 2s ajoutait jusqu'à 2s de latence sur /live vs l'app
Exercice native d'Apple (instantanée). Désormais chaque nouvel échantillon FC
est envoyé immédiatement ; throttle léger 0.5s conservé uniquement pour
calories/distance (anti-saturation BLE).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 12:05:27 +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
678b5a41d0 chore: logs os_log bout-en-bout (watch sendSample + iPhone liveSample) + hook AUTOSIM
Logger unified (capturable via simctl) côté watch (envoi) et iPhone (réception)
pour valider le pont WCSession. Hook AUTOSIM (env var) démarre le mode test au
lancement, pour validation automatisée sur simulateur. Pont prouvé E2E sur sim
pair iPhone+Watch : HR identiques watch->iPhone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:37:03 +02:00
Sylvain
3ce8ca9e28 feat(watchos): mode Test (FC simulée) — valide le flux sans HealthKit
Bouton "Test (FC simulée)" qui envoie des samples synthétiques (FC ~110-150,
calories, distance, durée) via WCSession toutes les 2s, sans démarrer de séance
HealthKit. Permet de valider watch -> iPhone -> /live indépendamment de
l'autorisation Santé (bloquée par un sous-système Santé coincé sur la Watch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:21:24 +02:00
Sylvain
fef7781db7 chore(watchos): renomme l'app watch en "Coach Watch" pour la distinguer dans Santé
Les deux apps s'appelaient "Coach" (iPhone + watch, bundles distincts) -> impossible
d'identifier laquelle régler dans l'app Santé. Le nom distinct permet d'accorder
l'autorisation HealthKit à la bonne app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:03:44 +02:00
Sylvain
d1c1b88da4 feat(watchos): message clair si accès Santé refusé (au lieu de Error(7))
Si authorizationStatus(workoutType) == .sharingDenied après requestAuthorization,
on n'essaie pas de démarrer la séance (qui échouerait en HKErrorAuthorizationDenied)
et on affiche un message demandant d'activer Coach dans Réglages → Santé.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:54:48 +02:00
Sylvain
6cb89359c6 chore(watchos): logs diagnostics séance (autorisation + erreur HK domaine/code)
Logger os "ch.hypnotruck.coach.watchkitapp/workout" : trace requestAuthorization,
statut de partage, startActivity/beginCollection, transitions de session et
surtout didFailWithError (domaine+code) pour identifier la cause de l'Error(7).
Logger en constante de fichier (Sendable) pour rester accessible aux délégués
nonisolated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:50:56 +02:00
Sylvain
a4f8276b13 fix(watchos): empêche les sessions HK multiples + nettoie après échec/fin
Garde anti double-démarrage (guard !isRunning, session == nil) pour éviter la
cascade de HKLiveWorkoutBuilder en Error(7) quand on retape l'activité. Reset
session/builder à nil après échec, fin et erreur de séance pour permettre une
nouvelle tentative propre. Le statusMessage d'erreur reste donc visible sur le
picker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:47:17 +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
1cd96de7c6 fix(watchos): WKCompanionAppBundleIdentifier — l'app watch refusait de s'installer
Sans cette clé (ou WKWatchOnly), iOS rejette l'install d'une app watch compagnon
(erreur "WKCompanionAppBundleIdentifier missing or malformed"), sur simulateur
comme sur device/TestFlight. Détecté au smoke test simctl install. Pointe vers
le bundle iOS ch.hypnotruck.coach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 18:59:07 +02:00
Sylvain
518529891b feat(watchos): UI live workout + entry point (Phase 2)
CoachWatchApp: WKApplicationDelegateAdaptor active WCSession au boot, injecte les
managers. ContentView: picker 4 activités (course/vélo/marche/renfo) -> vue live
(FC/calories/distance/durée + état liaison iPhone) -> bouton Terminer. Build OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:23:06 +02:00
Sylvain
167c1e4c27 fix(watchos): WorkoutManager @MainActor — concurrency Swift 6 clean
Classe @MainActor (implicitement Sendable) ; callbacks HealthKit nonisolated qui
calculent les valeurs hors-main puis hoppent via Task { @MainActor }. Récupère le
builder depuis la session passée au délégué (plus de capture de self non-Sendable).
Build watchsimulator : 0 warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:22:17 +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