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.
This commit is contained in:
Sylvain Bettinelli
2026-08-12 19:13:07 +00:00
parent 8a25f3604a
commit eb819b68ca
6 changed files with 190 additions and 1 deletions

View File

@@ -10,6 +10,7 @@
01468471ECA28EA66AFE2D6C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB6C76F68679844EC72178F /* Foundation.framework */; };
0318E7E610501FC1F958251B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3342ACCE085010828CF63565 /* Assets.xcassets */; };
19F735674999FD63AB1C566F /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; };
C0AC4E1Z0002HRZONES00001 /* HeartRateZones.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4E1Z0001HRZONES00001 /* HeartRateZones.swift */; };
1B690F0D16AB76A6854CA1F7 /* CoachLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431C7B10248D8EED4D397AB0 /* CoachLiveActivityAttributes.swift */; };
21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */; };
23F47FD81F646516727B5317 /* RoutineStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969DB880395BDEB107C9F2FE /* RoutineStore.swift */; };
@@ -37,6 +38,7 @@
880A76D3302B601000F582D5 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 880A76D2302B601000F582D5 /* SwiftUI.framework */; };
880A76DE302B601100F582D5 /* CoachWatchWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 880A76CF302B601000F582D5 /* CoachWatchWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
880A76E6302B62FB00F582D5 /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; };
C0AC4E1Z0003HRZONES00001 /* HeartRateZones.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4E1Z0001HRZONES00001 /* HeartRateZones.swift */; };
88924CCE2FD2A733002F3A80 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 88924CCD2FD2A733002F3A80 /* GoogleSignIn */; };
88ABE0043024C45A002636FD /* CoachQuickLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABE0023024C45A002636FD /* CoachQuickLog.swift */; };
88ABE0053024C45A002636FD /* CoachQuickSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABE0033024C45A002636FD /* CoachQuickSync.swift */; };
@@ -48,11 +50,13 @@
95D0478A499056FE922211EA /* RoutineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C5902EFB7F60563678E46 /* RoutineView.swift */; };
97116C31D4CC40F71A6A0FE7 /* LiveStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6030BD533EBC2D1D26DB9479 /* LiveStore.swift */; };
97600B69ADF2F19B56E79621 /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; };
C0AC4E1Z0004HRZONES00001 /* HeartRateZones.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4E1Z0001HRZONES00001 /* HeartRateZones.swift */; };
9BED4C04C38FCB8634CE2A94 /* CoachLiveActivity.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 9B5664FCDB4DDE62514D3A1E /* CoachLiveActivity.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
9E88D51C0B0255D24274DACC /* CoachLiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BA75EEC3CBF1F46A9028C66 /* CoachLiveActivityBundle.swift */; };
A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */; };
B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A420239FC6D065C8A599B61C /* ConnectivityManager.swift */; };
B130613187FE0661DDF636C9 /* CoachWidgetSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */; };
C0AC4E1Z0005HRZONES00001 /* HeartRateZones.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4E1Z0001HRZONES00001 /* HeartRateZones.swift */; };
C0AC4A07811751FB78AA0001 /* CoachAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AC4A07811751FB78AA0002 /* CoachAuth.swift */; };
D1384CFA4960060CDBF05FC3 /* CoachLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431C7B10248D8EED4D397AB0 /* CoachLiveActivityAttributes.swift */; };
D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */; };
@@ -158,6 +162,7 @@
8AC9C9B9971F55027D227A06 /* WorkoutManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WorkoutManager.swift; sourceTree = "<group>"; };
958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; };
96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWidgetSnapshot.swift; sourceTree = "<group>"; };
C0AC4E1Z0001HRZONES00001 /* HeartRateZones.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HeartRateZones.swift; sourceTree = "<group>"; };
969DB880395BDEB107C9F2FE /* RoutineStore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RoutineStore.swift; sourceTree = "<group>"; };
97BC6E07E6984342AE95C312 /* CoachRoutineBridge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachRoutineBridge.swift; sourceTree = "<group>"; };
9B5664FCDB4DDE62514D3A1E /* CoachLiveActivity.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CoachLiveActivity.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -302,6 +307,7 @@
50B271D01FEDC1A000F3C39B /* public */,
A9DF3346C49A690C3C3DF2F3 /* CoachLiveBridge.swift */,
96752F30FFA0FAA9E42F9BBA /* CoachWidgetSnapshot.swift */,
C0AC4E1Z0001HRZONES00001 /* HeartRateZones.swift */,
11ECAD3DB1486DE606285921 /* CoachWidgetBridge.swift */,
97BC6E07E6984342AE95C312 /* CoachRoutineBridge.swift */,
6030BD533EBC2D1D26DB9479 /* LiveStore.swift */,
@@ -526,6 +532,7 @@
files = (
79A94FD334CC9EC24D1FB700 /* CoachWidgets.swift in Sources */,
19F735674999FD63AB1C566F /* CoachWidgetSnapshot.swift in Sources */,
C0AC4E1Z0002HRZONES00001 /* HeartRateZones.swift in Sources */,
1B690F0D16AB76A6854CA1F7 /* CoachLiveActivityAttributes.swift in Sources */,
694B2F69591CAA85CE5DEC3A /* CoachLiveActivityWidget.swift in Sources */,
9E88D51C0B0255D24274DACC /* CoachLiveActivityBundle.swift in Sources */,
@@ -539,6 +546,7 @@
buildActionMask = 2147483647;
files = (
B130613187FE0661DDF636C9 /* CoachWidgetSnapshot.swift in Sources */,
C0AC4E1Z0005HRZONES00001 /* HeartRateZones.swift in Sources */,
2BAD3233026A4DFEEA02EAE8 /* CoachWidgetBridge.swift in Sources */,
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
C0AC4A07811751FB78AA0001 /* CoachAuth.swift in Sources */,
@@ -564,6 +572,7 @@
buildActionMask = 2147483647;
files = (
880A76E6302B62FB00F582D5 /* CoachWidgetSnapshot.swift in Sources */,
C0AC4E1Z0003HRZONES00001 /* HeartRateZones.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -572,6 +581,7 @@
buildActionMask = 2147483647;
files = (
97600B69ADF2F19B56E79621 /* CoachWidgetSnapshot.swift in Sources */,
C0AC4E1Z0004HRZONES00001 /* HeartRateZones.swift in Sources */,
543A7005429A43F54C0EC61B /* CoachWatchApp.swift in Sources */,
290D0F17978B7EF3DDB3A792 /* ContentView.swift in Sources */,
B06AB24B350B221F38D99E17 /* ConnectivityManager.swift in Sources */,