Files
coach-ios/docs/widgets-runbook-mac.md
Sylvain Bettinelli ec975a9a0d docs(ios): corrige la note capacitor.config — fichier gitignored/généré, .ts déjà OK
Rectification : capacitor.config.json n'est PAS suivi (gitignored, généré par cap
sync). Le .ts source a déjà les bonnes valeurs → le finding audit 'JSON divergent'
était un artefact local périmé, pas un bug du repo. cap sync sur le Mac régénère
correctement. Pas d'action de versionnement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 09:20:14 +00:00

110 lines
7.5 KiB
Markdown

# Runbook Mac — widgets natifs iPhone (Séance du jour + Score de forme)
## ⚠️ Fixes audit iOS 2026-06-29 — à finir sur le Mac
-**capacitor.config.json : RIEN à corriger dans le repo.** Vérifié 2026-06-29 : ce fichier est **gitignored / non suivi** (`ios/.gitignore`) — c'est un artefact GÉNÉRÉ par `cap sync`. La source versionnée `capacitor.config.ts` a déjà les bonnes valeurs (`contentInset: never`, fonds `#000000`, StatusBar `LIGHT`). Le finding audit « JSON embarqué divergent » était un **artefact local périmé**, pas un problème du code. → Sur le Mac : un simple `npx cap sync ios` régénère le JSON correctement depuis le `.ts`. (Aucune action de versionnement nécessaire.)
-**`aps-environment`** dans `ios/App/App/App.entitlements` = `development`**push cassés en prod/TestFlight**. NON modifié depuis Linux (flipper en `production` casserait les builds dev locaux ; le bon fix est par configuration Release sur le Mac, ou via la capability « Push Notifications » qui le gère automatiquement). À traiter sur le Mac AVANT toute distribution utilisant le push distant.
-**Entitlements widget + cible Watch** : cf. sections plus bas (App Group / CODE_SIGN_ENTITLEMENTS / Target Membership) — c'est aussi un finding de l'audit (widgets muets sans App Group câblé).
**Pour le Claude « Cowork » (Mac) / Sylvain.** Le code Swift + entitlements + le
hook web sont écrits côté `coach_sportif`/`coach-ios` par le Claude dev (Linux).
Il **reste les étapes Mac/portail Apple** ci-dessous — elles ne peuvent pas se
faire depuis Linux (provisioning + ajout de fichiers aux cibles dans Xcode).
## Ce qui est déjà fait (committé)
**coach-ios** (natif) :
- `ios/App/App/CoachWidgetSnapshot.swift` — modèle partagé App ↔ extension (App Group).
- `ios/App/App/CoachWidgetBridge.swift` — plugin Capacitor `setSnapshot`/`clear` + `WidgetCenter.reloadAllTimelines()`.
- `ios/App/CoachLiveActivity/CoachWidgets.swift` — 2 widgets (TimelineProvider + vues SwiftUI).
- `ios/App/CoachLiveActivity/CoachLiveActivityBundle.swift` — widgets ajoutés au `WidgetBundle`.
- `ios/App/App/MainViewController.swift` — plugin enregistré dans `capacitorDidLoad()`.
- `ios/App/App/App.entitlements` — App Group `group.ch.hypnotruck.coach` ajouté.
- `ios/App/CoachLiveActivity/CoachLiveActivity.entitlements`**créé** avec l'App Group.
**coach_sportif** (web, déjà en prod via webhook) :
- `web/static/widget-bridge.js` + include dans `_layout.html` — pousse `today` (depuis `/api/today`) + `forme` (recovery_score). No-op hors app native.
- `web/templates/home.html``data-form-score` exposé sur le hero.
## Étapes Mac à faire (dans l'ordre)
### 1. App Group sur le portail Apple
developer.apple.com → Certificates, IDs & Profiles → **Identifiers****App Groups**
créer `group.ch.hypnotruck.coach` (si pas déjà créé).
### 2. Activer la capability sur les 2 cibles (Xcode)
⚠️ **Quitter puis `git pull`** d'abord (règle COWORK : jamais de pull Xcode ouvert).
Ouvrir `ios/App/App.xcworkspace`, puis pour **App** ET **CoachLiveActivity** :
- onglet **Signing & Capabilities****+ Capability** → **App Groups**
cocher `group.ch.hypnotruck.coach`.
- Vérifier que `CODE_SIGN_ENTITLEMENTS` pointe bien sur le bon `.entitlements`
(App → `App/App.entitlements` ; CoachLiveActivity → `CoachLiveActivity/CoachLiveActivity.entitlements`,
à régler si Xcode ne l'a pas pris).
### 3. Ajouter les nouveaux fichiers aux bonnes cibles (Target Membership)
Les fichiers existent sur disque mais doivent être référencés par le projet :
- `CoachWidgetSnapshot.swift`**cocher App ET CoachLiveActivity** (fichier partagé).
- `CoachWidgetBridge.swift`**cible App** uniquement.
- `CoachWidgets.swift`**cible CoachLiveActivity** uniquement.
- `CoachLiveActivity.entitlements` → pas une source (référencé par le build setting).
Méthode : clic droit sur le dossier de la cible → *Add Files to "App"…* (décocher
*Copy items*, cocher la bonne cible), ou sélectionner le fichier → inspecteur de
droite → *Target Membership*.
### 4. Build + test device
- **Clean Build Folder** (`Cmd+Shift+K`) → **Run** sur l'iPhone.
- Ouvrir l'app (charge la prod) une fois pour que `widget-bridge.js` pousse le snapshot.
- Écran d'accueil → ajouter les widgets **Coach** : « Séance du jour » (small/medium) et « Score de forme » (small).
- Vérifier qu'ils affichent les vraies données du jour. Tap → ouvre `/calendar` resp. `/forme`.
### 5. Debug si le widget reste vide
- Vérifier le snapshot écrit : Safari → Développement → iPhone → console →
`await Capacitor.Plugins.CoachWidgetBridge.setSnapshot({today:{sport:"running",title:"Test",done:false}})`
puis long-press l'écran d'accueil → le widget doit se mettre à jour.
- Si rien : l'App Group n'est pas partagé (étape 2) → `UserDefaults(suiteName:)` renvoie nil.
## Modèle de données (contrat web ↔ widget)
```jsonc
{
"today": { "sport": "running", "title": "Sortie longue", "subtitle": "90 min · Z2", "done": false },
"forme": { "score": 72, "label": "Prêt" } // null si pas de score
}
```
`sport` ∈ running | cycling | strength | mobility | hiking | walking | rest.
## Complication Apple Watch (code écrit — reste la target Xcode)
⚠️ **Rappel archi** : l'App Group n'est PAS partagé entre iPhone et Watch (conteneurs
distincts). Le flux est : iPhone `CoachWidgetBridge.pushToWatch``updateApplicationContext`
`CoachWatch/ConnectivityManager` reçoit → écrit dans l'App Group de la MONTRE →
la complication lit ce conteneur. Tout ce code est déjà écrit & committé.
### Déjà fait (committé)
- iPhone : `CoachWidgetBridge.pushToWatch()` envoie le snapshot via WCSession (sans être delegate).
- Watch : `CoachWatch/ConnectivityManager` gère `didReceiveApplicationContext`/`didReceiveUserInfo`
`CoachWidgetStore.save` + `WidgetCenter.reloadAllTimelines()`.
- `ios/App/CoachWatchWidgets/CoachWatchComplications.swift` — 2 complications
(Forme : circular/corner ; Séance : rectangular/inline) + `@main` WidgetBundle.
### Étapes Mac
1. **Créer la target** : File → New → Target → **Widget Extension** (plateforme **watchOS**),
nom `CoachWatchWidgets`, embarquée dans l'app Watch `CoachWatch`. Supprimer le fichier
template généré, puis **Add Files** `CoachWatchComplications.swift` à cette target.
2. **Target Membership de `CoachWidgetSnapshot.swift`** : cocher **CoachWatch** (l'app Watch,
pour `ConnectivityManager`) **ET CoachWatchWidgets** (l'extension complication).
3. **App Group** `group.ch.hypnotruck.coach` : activer la capability sur **CoachWatch** ET
**CoachWatchWidgets** (même ID que l'iPhone ; conteneur physique distinct côté montre).
4. **Build** la target Watch + extension, lancer sur la montre. Ajouter les complications
sur un cadran (Forme = circular ; Séance = rectangular). Ouvrir l'app iPhone une fois
pour pousser le 1er snapshot (l'app Watch doit être installée & appairée).
5. Si la complication reste vide : vérifier que `updateApplicationContext` arrive
(log `widgetSnapshot reçu de l'iPhone` côté watch) et que l'App Group est bien partagé
sur les deux targets watch.
## Suite possible (backlog)
- Lockscreen widgets iPhone (`accessoryRectangular` / `accessoryCircular`) — ajouter les familles aux widgets iOS.
- `transferCurrentComplicationUserInfo` (réveil complication, budget ~50/j) si besoin de fraîcheur en arrière-plan.
- Background refresh iPhone (App Background Tasks) pour un widget à jour sans ouvrir l'app.