docs: analyse migration iOS natif vs hybride (aide à la décision, ancrée inventaire réel)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-06-29 05:05:14 +00:00
parent fe268a88d1
commit edf3783fb5

View File

@@ -0,0 +1,123 @@
# Analyse — migration « iOS pur et dur » (natif SwiftUI) vs hybride actuel
**Date** : 2026-06-29
**Statut** : aide à la décision (aucun code engagé)
**Demandé par** : Sylvain — « sinon on migre mon application iOS pur et dur »
**Méthode** : estimations ancrées sur l'inventaire réel du code (pas d'extrapolation).
---
## 0. TL;DR
- **Le backend ne bouge pas** dans aucun scénario : `coach_sportif` (~9 600 lignes Python) est déjà une API + des intégrations. Il sert aussi bien une WebView qu'une app native.
- **Ce qu'une migration native jette / réécrit** : tout le **front** — ~14 900 lignes de templates, ~10 000 lignes de CSS, ~2 800 lignes de JS séparé (+ beaucoup de JS inline dans les pages). Mature, itéré pendant des mois.
- **Coût réaliste d'un rewrite SwiftUI à parité** (dev solo) : **~5 à 8 mois** plein temps, **iOS seulement**. Atteindre la parité Android en plus = quasi le même effort en Compose, ou abandonner Android.
- **Recommandation** : **ne pas faire de big-bang natif.** Soit rester hybride avec des îlots natifs ciblés (déjà ta trajectoire), soit, si tu veux vraiment du natif, une **migration incrémentale page-par-page** (« strangler ») étalée — mais elle a un coût de complexité propre (cf. §6).
---
## 1. Ce que tu as aujourd'hui (hybride Capacitor)
| Brique | Volume | Sort en cas de rewrite natif |
|---|---|---|
| Backend FastAPI (`web/app.py` + `web/routers/`) | ~9 600 lignes | **Conservé** (devient l'API du natif) |
| Templates Jinja (pages) | 26 pages, ~14 900 lignes | **Réécrit en SwiftUI** |
| CSS design system (`style.css` + `critical.css`) | ~10 000 lignes | **Jeté** (refait en SwiftUI/tokens) |
| JS client (`web/static/*.js`) | ~2 800 lignes | **Réécrit en Swift** |
| JS inline dans les templates | gros (ex. `meals.html` ≈ 2 900 lignes dont bcp de JS) | **Réécrit en Swift** |
| Libs front (Chart.js, Leaflet) | vendor | Remplacé par **Swift Charts** / **MapKit** |
| Plugins natifs déjà écrits | 8 (HealthKit, WorkoutKit, Live Activity, watchOS, auth Apple/Google…) | **Conservés/réutilisés** |
| Wrapper Android + PWA Windows | partagé avec le web | **Perdu** (ou à maintenir à part) |
**Atouts hybride** que la migration sacrifie :
- **1 base UI → 3 cibles** (iOS, Android, PWA) servies par le même web.
- **Déploiement instantané sans review Apple** (l'app charge la prod en direct) : ~95 % de tes itérations ne touchent pas le Mac.
- Déjà en TestFlight, déjà fonctionnel.
---
## 2. Effort de réécriture, page par page (ancré sur le volume réel)
Échelle indicative pour **un dev solo**, à parité fonctionnelle + tests device (charts = Swift Charts, cartes = MapKit, formulaires natifs) :
### Pages lourdes (le gros du coût)
| Page | Lignes | Pourquoi c'est lourd | Estim. |
|---|---|---|---|
| `meals` | 2 878 | Scan code-barres, recherche FatSecret + Open Food Facts, fiche aliment, hydratation, analyse Coach IA, modales | **34 sem** |
| `calendar` | 2 610 | Modales séance, segments, rappels locaux, export ICS, vues semaine/jour, états « fait/auto » | **34 sem** |
| `workout` (détail) | 1 950 | Carte Leaflet (trace GPS), graphes FC/puissance/zones, laps, comparaison de trajets | **23 sem** |
| `body` | 1 084 | Graphes multi-métriques, Withings, saisie manuelle | **1.52 sem** |
| `settings` | 1 135 | Intégrations (connexions OAuth), debug plugins natifs, prefs | **1.52 sem** |
| `sleep` | 783 | Graphes sommeil, sources HealthKit | **11.5 sem** |
| `live_workout` | 323 | Temps réel (déjà fortement natif via CoachLive*) | **0.51 sem** |
### Pages moyennes
`home` (286), `forme` (428), `workouts` (402), `hrv` (396), `week` (376), `profile` (395), `stats` (232), `reviews`+`review_detail` (283) — graphes, listes, navigation. **≈ 35 jours chacune** → ~**46 sem** cumulées.
### Pages simples
`login`, `register`, `reset_password`, `privacy`, `usage`, `icons*`, `dashboard*`**0.51 j chacune** → ~**1 sem**.
### Transverse (à faire une fois, mais incompressible)
- **Design system SwiftUI** réimplémentant les tokens `--c-*`/`--radius-*` + composants `.ap-*` : **23 sem**.
- **Navigation / tab bar / TileManager** (perso. d'accueil) en SwiftUI : **12 sem**.
- **Couche réseau + modèles + auth** (consommer l'API, session JWT, gestion offline/cache) : **23 sem**.
- **Coach IA chat** (streaming SSE, outils) en natif : **12 sem**.
**Total iOS seul (parité) : ≈ 58 mois** plein temps solo, hors imprévus device/App Store. C'est un ordre de grandeur, pas un devis.
---
## 3. Ce qu'on garde / ce qu'on jette
**On garde (déjà fait, réutilisable tel quel)**
- Tout le **backend** + intégrations (Polar/Bosch/Withings/TP/FatSecret), auth multi-user JWT, Coach IA serveur.
- Les **8 plugins natifs** : HealthKit (`CoachHealthRoute`), WorkoutKit, Live Activity, watchOS (`CoachWatch`/`CoachLiveBridge`), Sign in Apple/Google. En natif ils deviennent des services directs (plus besoin du pont Capacitor).
**On jette / réécrit**
- 100 % du front web (HTML/CSS/JS), soit ~25 000 lignes cumulées de code mûr.
- L'avantage « déploiement sans review ».
- Le partage de code avec **Android** et la **PWA**.
---
## 4. Impact Android / PWA (souvent l'angle mort)
Aujourd'hui, iOS, **Android** (Health Connect) et **PWA Windows** partagent le même front web. Un rewrite **iOS natif** :
- **Casse la parité** : Android resterait sur le wrapper web → tu te retrouves avec **2 fronts** (SwiftUI + web) à maintenir en parallèle, divergents.
- Pour re-parité Android native, il faudrait **Jetpack Compose** = un second rewrite d'ampleur comparable.
- La **PWA** disparaît du périmètre natif.
→ Le coût réel d'un « tout natif » multi-plateforme n'est pas « 1 rewrite » mais potentiellement « 2 rewrites + le web maintenu pour la PWA ».
---
## 5. Risques
- **Tunnel** : 58 mois sans valeur livrable visible pendant que le produit (pivot commercial FR) doit avancer. Risque #1 pour un solo.
- **Régressions** : tu réécris des comportements subtils déjà debuggés (Safari Date, caches SW, états calendar, footguns HealthKit…). Chaque page = re-tester sur device.
- **Dépendance Mac/review** sur chaque correctif (perte de vélocité).
- **Double maintenance** pendant la transition (le natif et le web coexistent forcément un moment).
- **Coût d'opportunité** : ces mois ne vont pas dans les features qui différencient (Coach IA, agrégateur, intégrations).
---
## 6. Les 3 options, honnêtement
### Option A — Rester hybride + îlots natifs (recommandé)
Garder la WebView, ajouter du natif **uniquement là où ça paie** : widgets, complications Watch, écrans temps réel (déjà fait pour le live), animations clés. **Coût : jours/semaines par îlot. Aucune perte.** C'est ta trajectoire actuelle.
### Option B — Migration incrémentale « strangler » (si tu veux vraiment du natif)
Remplacer **page par page** : on garde la WebView comme coquille et on substitue une vue **native** pour une route donnée (le wrapper route vers du SwiftNav au lieu de la WebView). Tu commences par 12 pages à fort enjeu UX (ex. `home` + `live`), tu mesures le gain réel, tu décides de continuer ou non. **Avantage** : valeur livrable en continu, réversible, pas de big-bang. **Inconvénient** : période de cohabitation web/natif à gérer (navigation, auth, état partagé).
### Option C — Big-bang « tout natif »
Le rewrite complet décrit ci-dessus. **58 mois iOS, Android/PWA sacrifiés ou doublés.** Justifié seulement si l'app devient le cœur du business ET que la WebView est un plafond de verre prouvé (perf/UX), ET que tu as le temps/les moyens. **Pour un solo en phase de pivot : déconseillé.**
---
## 7. Recommandation
1. **Ne pas lancer de big-bang (Option C) maintenant.**
2. Si l'envie de natif vient d'**irritants précis** (perf d'une page, feeling de scroll, bug WebView) → **Option A**, on cible l'îlot. Commençons par les **widgets** (déjà cadrés) puis l'îlot qui te gêne le plus.
3. Si tu veux *stratégiquement* tendre vers le natif sans tout risquer → **Option B**, on fait **1 page pilote** (je suggère `home` ou `live`) et on juge sur pièce avant d'investir plus.
**Prochaine étape proposée** : tu choisis A ou B. En A, je reprends le scaffold des 2 widgets iPhone. En B, je scope la page pilote (archi de cohabitation WebView↔SwiftUI, auth/état partagés, routeur) avant de coder.