Files
coach-ios/docs/widgets-runbook-mac.md
Sylvain Bettinelli a6bdebcfc8 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>
2026-06-29 05:18:17 +00:00

4.2 KiB

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.entitlementscréé 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.htmldata-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 → IdentifiersApp 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+ CapabilityApp 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.swiftcocher App ET CoachLiveActivity (fichier partagé).
  • CoachWidgetBridge.swiftcible App uniquement.
  • CoachWidgets.swiftcible 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)

{
  "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.