Sylvain Bettinelli 3755cafa1f Adoption du cycle de vie UIScene : sans lui, l'app ne démarre plus
Le build échouait au lancement sur _UIApplicationEvaluateRuntimeIssueForNo
SceneLifecycleAdoption. Apple est explicite : « Beginning in iOS 27 […] apps
built with the latest SDK must adopt the scene-based life cycle or they fail to
launch » (Transitioning to the UIKit scene-based life cycle). Ce n'est plus un
avertissement comme en iOS 26, c'est un assert. Aucune dérogation n'est
documentée : ni clé Info.plist, ni entitlement, ni date de tolérance — le seul
report possible serait de ne pas construire avec le dernier SDK, ce qui ne fait
que déplacer l'échéance sous contrainte de release.

Capacitor a adopté UIScene en 8.5.0 (PR #8536, « required in iOS 27 »), et
8.5.2 corrige une régression qui faisait échouer chaque démarrage à froid sur
une JS Eval error (#8595). On passe donc de 8.3.1 à 8.5.2, pas à 8.5.0.

⚠️ npx cap migrate N'A PAS été utilisé, et c'est volontaire : le guide officiel
exclut les projets à gestion d'URL personnalisée dans l'AppDelegate — « Projects
with hand-rolled scene delegates or custom AppDelegate URL handling should
follow the manual steps instead ». Notre branche GoogleSignIn en est la
définition. Le SceneDelegate suit le template livré dans le paquet 8.5.2, avec
deux écarts nécessaires décrits ci-dessous.

⚠️ CE QUI CASSE EN SILENCE, et qui est repris dans SceneDelegate.swift. Une
fois le manifeste posé, UIKit cesse d'appeler trois méthodes de l'AppDelegate
« even if you keep them implemented » (Apple) :
- application(_:open:options:) -> le retour OAuth Google partirait vers Safari
  sans jamais revenir. Déprécié iOS 26, remplacé par scene(_:openURLContexts:).
  La signature change : une URL devient un Set, donc on filtre ce que Google
  consomme avant de passer le reste à Capacitor.
- applicationDidBecomeActive -> les saisies du widget (eau, café) resteraient
  bloquées dans l'App Group. Reprise dans sceneDidBecomeActive.
- application(_:continue:restorationHandler:) -> les Universal Links des
  widgets et complications ouvriraient Safari, où le cookie d'auth n'existe pas.
Aucune des trois ne produit d'erreur de compilation. Elles ne sont pas laissées
en doublon dans l'AppDelegate : une méthode morte qui a l'air vivante fait
chercher la panne au mauvais endroit.

✅ CE QUI NE BOUGE PAS, vérifié dans la doc avant d'écrire une ligne :
didFinishLaunchingWithOptions reste appelé, et Apple documente la séquence
(launch -> création des scènes -> scene(_:willConnectTo:)). L'injection du
cookie garde donc son ordre : elle s'exécute avant que la WKWebView charge. La
règle non négociable n°2 du CLAUDE.md est tenue. Les callbacks de notification
distante restent aussi sur l'AppDelegate — UISceneDelegate n'a aucun équivalent
et ils ne sont pas dépréciés.

Écart au template Capacitor : il pose un CAPBridgeViewController() nu comme
rootViewController. L'appliquer tel quel remplacerait MainViewController, qui
porte l'enregistrement des 9 plugins natifs et la config WKWebView — sans
erreur visible. Le manifeste gardant UISceneStoryboardFile = Main, UIKit charge
déjà le storyboard : on ne crée la fenêtre que s'il ne l'a pas fait, et avec
notre sous-classe.

npm audit : 0 vulnérabilité en production. 13 dans l'outillage de build, qui ne
part pas dans l'app. ⛔ Ne PAS lancer npm audit fix --force : il propose de
redescendre @capacitor/cli en 7.6.9, ce qui supprimerait le support UIScene.

⚠️ RESTE À FAIRE SUR LE MAC, et rien ne compilera sans : ajouter
SceneDelegate.swift au target App dans Xcode. Le dossier App/ utilise des
références explicites dans le pbxproj (les groupes synchronisés ne couvrent que
les targets Watch/Widgets), et je n'édite pas un pbxproj à l'aveugle.

Vérifié ici : plist valide et relu par plistlib, syntaxe des deux fichiers
Swift validée par swiftc -parse. Aucune compilation possible sur Linux.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 08:04:14 +00:00

Coach Hypnotruck — Mobile (iOS + Android)

Wrapper natif mobile de coach.hypnotruck.ch basé sur Capacitor 8.

Le repo s'appelle historiquement coach-ios ; renommé coach-mobile à partir de l'ajout de la plateforme Android (2026-05-07). Le filename coach-ios.local.json est conservé pour ne pas casser les références dans le code Swift natif.

Architecture

  • Backend : FastAPI + Jinja2 hébergé sur le VPS, accessible via https://coach.hypnotruck.ch
  • Coque iOS : Capacitor 8 charge l'URL distante dans un WKWebView
  • Coque Android : Capacitor 8 charge l'URL distante dans une WebView (Android 8.0+, API 26+ — requis par Health Connect)
  • Plugins natifs : push notifications, local notifications, géolocalisation, HealthKit (iOS) / Health Connect (Android) via @capgo/capacitor-health, splash, status bar, haptics

Le code web n'est pas embarqué — l'app pointe vers la prod via server.url dans capacitor.config.ts. Avantage : déploiement web = mise à jour instantanée de l'app sans review Apple/Google.

Bundle ID & nom

  • Bundle ID : ch.hypnotruck.coach
  • App Store name : Coach Hypnotruck
  • Display name : Coach Hypnotruck

Prérequis (sur le Mac mini)

  • macOS 13+ (Ventura ou plus récent)
  • Xcode 15+ (App Store, ~10 Go)
  • Command Line Tools : xcode-select --install
  • Homebrew + brew install node cocoapods
  • Node 20 LTS (compatible avec Cap 8)
  • Compte Apple Developer Program actif (99 USD/an)
  • Apple ID dev@hypnotruck.ch connecté dans Xcode → Settings → Accounts

Workflow build

1. Cloner le repo sur le Mac mini

git clone https://git.nexus.hypnotruck.ch/sylvain/coach-ios.git
cd coach-ios
npm install

1bis. Configurer le token d'auth (auto-login)

Le repo ne contient pas le token COACH_WEB_TOKEN (gitignored). Crée le fichier sur le Mac :

cp coach-ios.local.json.example coach-ios.local.json
# Édite coach-ios.local.json et remplace par la vraie valeur
# (visible dans ~/.config/infomaniak.env sur le VPS, ligne export COACH_WEB_TOKEN=)

L'app utilisera ce token pour se connecter automatiquement à coach.hypnotruck.ch/login?token=... au lancement.

2. Générer les icônes & splash

# iOS uniquement
npx @capacitor/assets generate --ios
# Android uniquement
npx @capacitor/assets generate --android
# Les deux
npx @capacitor/assets generate

Génère toutes les tailles requises depuis resources/icon.png (1024×1024) et resources/splash.png (2732×2732).

3. Ajouter la plateforme iOS (à faire UNE SEULE FOIS sur le Mac)

npx cap add ios

Cette commande crée le dossier ios/ avec le projet Xcode, qui sera ensuite committé.

4. Sync (à chaque changement de plugins ou config)

npx cap sync ios

5. Ouvrir dans Xcode

npx cap open ios

Dans Xcode :

  1. Sélectionner le projet App dans la navigation gauche
  2. Onglet Signing & Capabilities :
    • Team : ton équipe Apple Developer
    • Bundle Identifier : ch.hypnotruck.coach
    • Cocher "Automatically manage signing"
  3. Ajouter les capabilities requises :
    • HealthKit (lecture FC, pas, calories, VO2max)
    • Push Notifications
    • Background Modes → Remote notifications
  4. Onglet Info : ajouter les clés Privacy obligatoires (voir section ci-dessous)

6. Build sur simulateur / device

  • Simulateur : choisir un iPhone dans la barre du haut → ⌘R
  • Device physique : connecter iPhone par USB, sélectionner, ⌘R (la 1ère fois iOS demande de faire confiance au certificat dev dans Réglages → Général → VPN et gestion des appareils)

7. Upload TestFlight

  1. Xcode → Product → Archive
  2. Fenêtre Organizer s'ouvre → sélectionner l'archive → Distribute App → App Store Connect → Upload
  3. Sur App Store Connect : créer une nouvelle app (1ère fois), section TestFlight → ajouter testeurs (jusqu'à 100 internes sans review, 10 000 externes avec review légère)

Privacy keys requises (Info.plist)

Sans ces clés, iOS crashe à l'usage du plugin. À ajouter dans Xcode → onglet Info → Custom iOS Target Properties :

Clé Valeur (description affichée à l'utilisateur)
NSHealthShareUsageDescription "Coach Hypnotruck lit vos données de santé (FC, pas, calories) pour adapter vos séances."
NSHealthUpdateUsageDescription "Coach Hypnotruck enregistre vos séances dans Santé pour suivre votre progression."
NSLocationWhenInUseUsageDescription "Coach Hypnotruck utilise votre position pour tracer vos sorties course et VTT."
NSLocationAlwaysAndWhenInUseUsageDescription "Permet le suivi GPS pendant les sorties même app en arrière-plan."
NSMotionUsageDescription "Coach Hypnotruck utilise les capteurs de mouvement pour mesurer votre activité."
NSCameraUsageDescription "Coach Hypnotruck peut utiliser l'appareil photo pour scanner des codes-barres alimentaires."

Anti-rejet App Store (Guideline 4.2)

Apple rejette les apps qui sont juste un wrapper webview sans valeur native. Pour passer review :

  • ✅ Push notifications natives (rappel séances) — implémenté côté iOS via @capacitor/push-notifications
  • ✅ HealthKit (lecture FC, pas, calories) — implémenté via @capgo/capacitor-health
  • ✅ Géolocalisation native pour tracking sorties
  • ✅ Local notifications (rappels hors-ligne)
  • ✅ Haptics sur actions importantes
  • ⏳ À implémenter côté web : appeler ces plugins via JS bridge depuis coach.hypnotruck.ch

Structure repo

coach-ios/
├── capacitor.config.ts       # Config Capacitor (bundle ID, URL, plugins)
├── package.json              # Deps Capacitor + plugins
├── www/                      # Page de bootstrap (chargement, fallback)
│   └── index.html
├── resources/                # Source icônes & splash (1024×1024, 2732×2732)
│   ├── icon.png
│   └── splash.png
├── ios/                      # Projet Xcode (généré par `cap add ios` sur Mac)
└── README.md

Mise à jour de l'app

Type de modif Action requise
Changement web (templates, CSS, routes) Aucune — l'app charge la prod en direct
Ajout plugin Capacitor npm install + npx cap sync + nouveau build TestFlight / Play Store
Changement icône / splash npx @capacitor/assets generate + sync + nouveau build
Changement bundle ID, version, capabilities Build + soumission Apple review / Play Console review

TODO Phase 0

  • Sur le Mac : npx cap add ios et commit du dossier ios/
  • Configurer signing dans Xcode avec Apple ID dev@hypnotruck.ch
  • Ajouter les Privacy keys dans Info.plist
  • 1er build simulateur pour vérifier que la PWA charge bien
  • 1er build device physique (iPhone perso)
  • Upload TestFlight pour beta interne
  • Préparer screenshots App Store (6.7" iPhone, min 3)
  • Privacy policy URL (obligatoire avec HealthKit) — à héberger sur coach.hypnotruck.ch/privacy

Support

Description
No description provided
Readme 2.9 MiB
Languages
Swift 98.5%
HTML 0.5%
TypeScript 0.4%
Java 0.4%
Shell 0.2%