docs: handoff backend /live (Mac->Linux) + pont watch->iPhone prouvé E2E
HANDOFF-LIVE-BACKEND.md : tâches coach_sportif pour finir /live (auth cookie de session, lien d'accès depuis l'app, JS template) + contrat exact du plugin (event liveSample, champs heartRate/activeEnergyKcal/distanceMeters/elapsedSec/ts/sim). Plan mis à jour : pont watch->iPhone validé end-to-end sur simulateur. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
101
HANDOFF-LIVE-BACKEND.md
Normal file
101
HANDOFF-LIVE-BACKEND.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Handoff — Backend `/live` (Mac → session Linux `coach_sportif`)
|
||||
|
||||
**De** : Claude Code Mac mini, 2026-05-25 (chantier watchOS Live Workout).
|
||||
**Pour** : session Claude Code sur le repo `coach_sportif` (backend FastAPI + templates).
|
||||
**Objet** : finir le **point 1** — la page `/live` doit s'afficher dans l'app et
|
||||
recevoir les samples temps réel poussés par la Watch.
|
||||
|
||||
## Contexte : le natif est TERMINÉ et PROUVÉ
|
||||
|
||||
Phases 1-3 (target watchOS `CoachWatch` + 4 fichiers Swift + plugin Capacitor
|
||||
`CoachLiveBridge`) livrées sur `coach-ios` (commits `5d60a40` → `678b5a4`).
|
||||
|
||||
**Pont watch → iPhone validé end-to-end sur simulateur** (paire iPhone+Watch) :
|
||||
les logs montrent chaque sample envoyé par la Watch reçu à l'identique par le
|
||||
plugin iPhone (HR 130.69 → 148.32, latence ~300 ms). Donc dès que `/live`
|
||||
s'affiche dans l'app et écoute l'event, **les chiffres défileront**.
|
||||
|
||||
Un **mode Test (FC simulée)** existe dans l'app watch (bouton sous les activités) :
|
||||
il envoie des samples synthétiques **sans HealthKit**. → tu peux valider `/live`
|
||||
**sans** dépendre de la vraie FC (la Santé de la Watch physique de Sylvain a un
|
||||
bug système séparé, hors scope ici).
|
||||
|
||||
## Le problème observé
|
||||
|
||||
En ouvrant `/live` dans l'app, la page renvoie **« token invalide »**.
|
||||
→ La route `/live` ne reconnaît pas la session authentifiée de l'app.
|
||||
|
||||
Rappel : l'app (wrapper Capacitor) s'authentifie via **injection de cookie de
|
||||
session dans `AppDelegate`** (coach-ios). Toutes les pages authentifiées marchent
|
||||
via ce cookie. La route `/live` (Phase 4) avait été écrite avec
|
||||
`check_auth(request, token)` où `token` venait d'un `?token=` — ce qui ne matche
|
||||
pas le flux cookie de l'app.
|
||||
|
||||
## À faire côté `coach_sportif`
|
||||
|
||||
### 1. Auth de `/live` = même mécanisme que les autres pages authentifiées
|
||||
Faire en sorte que `/live` accepte la **session/cookie** de l'app (comme les
|
||||
autres routes protégées), `?token=` restant un fallback optionnel. Concrètement :
|
||||
utiliser le même `Depends`/helper d'auth que les autres pages, au lieu d'exiger
|
||||
le token en query. Objectif : ouvrir `/live` dans l'app authentifiée → 200, pas
|
||||
« token invalide ».
|
||||
|
||||
### 2. Un accès à `/live` depuis l'UI web
|
||||
Ajouter un lien/bouton vers `/live` dans la nav de l'app (là où c'est pertinent —
|
||||
ex. page séance / accueil), pour que l'utilisateur y arrive sans taper d'URL.
|
||||
|
||||
### 3. JS du template `live_workout.html` — contrat EXACT du plugin natif
|
||||
|
||||
Le plugin iPhone émet l'event **`liveSample`** (jsName du plugin :
|
||||
**`CoachLiveBridge`**). Champs d'un sample :
|
||||
|
||||
| champ | type | sens |
|
||||
|---|---|---|
|
||||
| `heartRate` | number | FC en bpm |
|
||||
| `activeEnergyKcal` | number | calories actives cumulées |
|
||||
| `distanceMeters` | number | distance cumulée en mètres |
|
||||
| `elapsedSec` | number | durée écoulée en secondes |
|
||||
| `ts` | number | timestamp epoch (secondes, fractionnaire) |
|
||||
| `sim` | bool | présent (=true) uniquement en mode Test |
|
||||
|
||||
JS à mettre dans le template (page chargée DANS l'app Capacitor) :
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { registerPlugin } from '/static/vendor/capacitor-core.js';
|
||||
const CoachLiveBridge = registerPlugin('CoachLiveBridge');
|
||||
let lastTs = 0;
|
||||
CoachLiveBridge.addListener('liveSample', (s) => {
|
||||
document.getElementById('live-hr').textContent = Math.round(s.heartRate || 0);
|
||||
document.getElementById('live-kcal').textContent = Math.round(s.activeEnergyKcal || 0);
|
||||
document.getElementById('live-dist').textContent = ((s.distanceMeters || 0) / 1000).toFixed(2);
|
||||
document.getElementById('live-elapsed').textContent = fmtMS(s.elapsedSec || 0);
|
||||
document.getElementById('live-status').textContent = 'En direct';
|
||||
lastTs = Date.now();
|
||||
});
|
||||
function fmtMS(sec){const h=Math.floor(sec/3600),m=Math.floor((sec%3600)/60),x=Math.floor(sec%60);
|
||||
return h?`${h}h${String(m).padStart(2,'0')}`:`${m}:${String(x).padStart(2,'0')}`;}
|
||||
setInterval(()=>{ if (Date.now()-lastTs>8000)
|
||||
document.getElementById('live-status').textContent='Connexion perdue avec la Watch'; },1000);
|
||||
</script>
|
||||
```
|
||||
|
||||
> Vérifier que `/static/vendor/capacitor-core.js` (ou équivalent) expose bien
|
||||
> `registerPlugin`. En navigateur pur (hors app) il n'y a aucun WCSession donc 0
|
||||
> sample — c'est normal, la page n'est utile que dans le wrapper iOS.
|
||||
|
||||
## Test d'acceptation (sans device, sur simulateur)
|
||||
|
||||
1. App iOS (sim ou device) sur `/live`, page au premier plan.
|
||||
2. App watch → bouton **Test (FC simulée)**.
|
||||
3. `/live` doit afficher FC/calories/distance/durée qui défilent (~ toutes les 2 s)
|
||||
et le statut « En direct ».
|
||||
|
||||
Côté natif c'est déjà prouvé (plugin `CoachLiveBridge` logue
|
||||
`liveSample reçu de la Watch hr=…`). Si `/live` ne bouge pas alors que ce log
|
||||
apparaît, c'est le JS/registerPlugin du template à corriger.
|
||||
|
||||
## Hors scope de ce handoff
|
||||
- Vraie FC sur la Watch physique de Sylvain (bug système Santé de SA montre —
|
||||
NanoSettings crashe, à régler par désappairage/réappairage).
|
||||
- TestFlight (signing/App Store Connect côté Sylvain).
|
||||
@@ -477,10 +477,19 @@ UI picker rendue.
|
||||
3. Enregistrement plugin Capacitor 8 = `bridge?.registerPluginInstance(...)`
|
||||
dans `capacitorDidLoad()` (PAS la macro `CAP_PLUGIN`), CAPBridgedPlugin.
|
||||
|
||||
### Reste à faire
|
||||
### Pont watch -> iPhone PROUVÉ E2E (simulateur, 2026-05-25)
|
||||
|
||||
- **Test device** (FC réelle, WatchConnectivity → /live) : nécessite l'Apple
|
||||
Watch physique + signing (impossible en simulateur, pas de FC réelle).
|
||||
- **Cross-check** : la page `/live` déployée doit utiliser `registerPlugin('CoachLiveBridge')`
|
||||
et lire `heartRate / activeEnergyKcal / distanceMeters / elapsedSec / ts`.
|
||||
- TestFlight interne.
|
||||
Validé sur paire simulée iPhone+Watch (mode Test FC simulée + hook env `AUTOSIM`) :
|
||||
chaque sample envoyé par la Watch (`sendSample … hr=130.69→148.32`) est reçu à
|
||||
l'identique par le plugin iPhone (`liveSample reçu … hr=130.69→148.32`), latence
|
||||
~300 ms. Vue live watch rendue (FC/calories/distance). Toute la chaîne native
|
||||
fonctionne ; il ne reste que l'affichage web et la vraie FC.
|
||||
|
||||
### Reste à faire — 2 blocages EXTERNES (pas le code natif)
|
||||
|
||||
1. **Backend `/live`** (« token invalide ») : route à passer en auth cookie de
|
||||
session + lien d'accès depuis l'app + JS template. Détail + contrat exact dans
|
||||
**`HANDOFF-LIVE-BACKEND.md`** (repo `coach_sportif`, session Linux).
|
||||
2. **Vraie FC sur la Watch physique de Sylvain** : bug système Santé de SA montre
|
||||
(NanoSettings crashe, Santé spinne) — désappairer/réappairer la Watch.
|
||||
3. TestFlight interne (signing / App Store Connect).
|
||||
|
||||
Reference in New Issue
Block a user