feat(widgets): widgets natifs iPhone (Séance du jour + Score de forme)
Pont WebView→App Group→WidgetKit : - CoachWidgetSnapshot.swift : modèle partagé App↔extension (App Group group.ch.hypnotruck.coach) - CoachWidgetBridge.swift : plugin Capacitor setSnapshot/clear + WidgetCenter.reloadAllTimelines - CoachWidgets.swift : 2 widgets (TimelineProvider + vues SwiftUI, small/medium) - bundle + enregistrement plugin + entitlements App Group (App + CoachLiveActivity) ⚠️ Reste câblage Mac (App Group portail + capability + Target Membership) : docs/widgets-runbook-mac.md. Hook web côté coach_sportif. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
docs/widgets-runbook-mac.md
Normal file
74
docs/widgets-runbook-mac.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Runbook Mac — widgets natifs iPhone (Séance du jour + Score de forme)
|
||||
|
||||
**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.
|
||||
|
||||
## Suite possible (backlog)
|
||||
- Complication **Watch** (nouvelle target widget watchOS — à créer dans Xcode).
|
||||
- Lockscreen widgets (`accessoryRectangular` / `accessoryCircular`) — ajouter les familles.
|
||||
- Background refresh (App Background Tasks) si on veut un widget à jour sans ouvrir l'app.
|
||||
Reference in New Issue
Block a user