# ADR-005 — Distribution mobile : builds natifs + Expo Go pour l'aperçu

- **Statut** : acceptée (recette terrain R4/R5, 19 juillet 2026)
- **Contexte** : la recette « mode avion » sur téléphone (due depuis `release/r4`) a buté
  sur Expo Go dès la première tentative — l'app installée depuis l'App Store affiche
  « supported SDK 54 » et refuse le projet (SDK 57). Apple a un retard d'approbation
  structurel sur les nouvelles versions d'Expo Go (constaté : seul SDK 54 installable sur
  iPhone physique début mai 2026, alors qu'Expo publiait déjà SDK 57) — ce n'est pas un
  problème côté projet, et rétrograder le SDK du projet pour suivre Expo Go serait
  disproportionné pour un aléa hors de notre contrôle.

## Décision

**Deux canaux de distribution mobile, en parallèle :**

1. **Builds natifs locaux** (Xcode pour iOS, Android Studio pour Android) — la voie pour
   tester *vraiment* sur un appareil (mode avion réel, caméra réelle, persistance),
   indépendante des aléas de version d'Expo Go. Signature gratuite (Apple ID personnel →
   Personal Team Xcode, sans compte payeur Apple Developer Program) pour les builds de
   recette ; à revoir si une distribution à plusieurs testeurs (étudiants) est nécessaire.
2. **Expo Go conservé** comme canal léger « sans installation » pour prévisualiser
   rapidement sur d'autres appareils, quand la version d'Expo Go de l'appareil correspond
   au SDK du projet — rien à changer, c'est le fonctionnement actuel de `expo start`.

Le **managed workflow est conservé** : `ios/` et `android/` restent générés à la demande
par `expo prebuild`/`expo run:*` (déjà gitignorés), jamais committés.

## Obstacles rencontrés en recette — et leurs correctifs

Cinq heures de recette terrain ont débusqué des problèmes réels, chacun avec un
correctif ou une limite désormais documentés pour ne pas être redécouverts à froid :

1. **Nom d'appareil avec apostrophe** rejeté par `expo run:ios --device "iPhone d'aziz"`
   → cibler par **UDID** (`xcrun xctrace list devices`), jamais par nom.
2. **Ne jamais court-circuiter `expo run:ios`/`expo run:android` par un `xcodebuild`
   manuel** : un contournement de signature via `xcodebuild -allowProvisioningUpdates`
   a produit une app qui compilait mais **crashait au lancement**
   (`dyld: Library not loaded: @rpath/React.framework/React`, signal 6) — la commande
   officielle inclut des étapes (embed frameworks, configuration Expo) qu'un appel manuel
   saute silencieusement.
3. **Incompatibilité des modules précompilés (SDK 56/57)** : Expo distribue par défaut
   des XCFrameworks précompilés pour ses modules, et React Native 0.86 précompile aussi
   son cœur (`RCT_USE_PREBUILT_RNCORE`) — ce projet, en liaison statique (pas de
   `use_frameworks!`), n'est pas compatible avec ces frameworks dynamiques. C'est la
   cause RÉELLE du crash ci-dessus (le contournement xcodebuild n'a fait que le révéler
   plus tôt). **Fix** : `EXPO_USE_PRECOMPILED_MODULES=0` et `RCT_USE_PREBUILT_RNCORE=0`
   avant `pod install`/`expo run:ios` — tout se recompile depuis les sources.
4. **`fetch` global incompatible avec l'upload multipart natif** : depuis SDK 57, `fetch`
   pointe vers `expo/fetch` (WinterCG), qui ne comprend pas le raccourci FormData
   `{uri, name, type}` utilisé pour joindre une photo (D5) — échec
   `Unsupported FormDataPart implementation`, **classé à tort comme une coupure réseau**
   par notre gestion d'erreur (`catch` générique de `envoyerSaisie`), donc invisible et
   mis en boucle de retry silencieuse. **Fix permanent** : `EXPO_PUBLIC_USE_RN_FETCH=1`
   dans `apps/mobile/.env` (committé — ce n'est pas un secret), qui restaure le `fetch`
   classique de React Native, compatible avec ce raccourci.
5. **Découverte réseau de Metro par le dev client peu fiable** : un `git clone` de
   dépendance (`ZXingObjC`) a échoué deux fois sur timeout réseau (retry + réglages git
   `http.postBuffer`/`http.lowSpeedTime` ont suffi) ; l'IP LAN du Mac a changé quatre fois
   pendant la session ; et **rouvrir l'app depuis son icône après une fermeture complète
   échoue souvent** (`No script URL provided`, RCTFatal) même quand Metro tourne et est
   joignable — la découverte Bonjour du dev client n'est pas fiable sur ce Wi-Fi. **Fix
   fiable** : relancer via `npx expo run:ios --device <udid>` (ou `run:android`), qui
   transmet l'adresse de Metro explicitement par deep link
   (`siop://expo-development-client/?url=...`) au lieu de compter sur la découverte
   automatique.
6. **Limite de fond, pas un bug** : un build **dev client** n'a aucun bundle JS embarqué —
   il ne peut PAS démarrer à froid en étant déjà hors-ligne (aucun fallback possible). La
   persistance de la file (D1) reste validée par un scénario réaliste et suffisant :
   app **déjà ouverte**, saisie mise en file hors-ligne, **process tué puis relancé** avec
   le réseau qui revient — la file survit et se rejoue (vérifié : un OT passé `ON_HOLD`
   après fermeture complète + reconstruction de l'app). Valider un vrai démarrage à froid
   totalement hors-ligne exigerait un build **Release** (bundle embarqué) — hors périmètre
   d'une recette de développement, à envisager si ce cas précis devient un critère de
   recette client.
7. **Détail UX, pas un bug** : le badge de compte (avatar) répond à un **appui long**
   (`onLongPress`), pas un tap simple — c'est le déclencheur de déconnexion
   (`app/(tabs)/journee.tsx`), volontairement sans confirmation puisqu'il est purement
   local (jeton, file, cache — jamais le réseau, ADR-003). Vaut d'être rendu plus
   visible si un retour utilisateur le demande.

## Conséquences

- La recette terrain iOS (7 points : connexion, scan caméra réel avec rejet de QR
  étranger, vrai mode avion, conflit D2 tranché par l'humain, persistance, suggestions R5
  au vrai modèle, purge de sécurité au changement de compte) est **validée sur iPhone 15
  Pro physique** — la validation due depuis `release/r4` est levée.
- Android : build natif installé et passage santé fait sur **émulateur**
  (`Pixel_3a_API_34`, connexion démo, navigation, écran Scanner sans plantage, résolution
  de référence confirmée via la saisie manuelle — équivalent du scan sans caméra réelle).
  La recette terrain sur **appareil Android physique** (vrai mode avion, vraie caméra)
  reste un reste, comme pour iOS avant aujourd'hui, en l'absence d'appareil disponible.
