diff --git a/.gitignore b/.gitignore index 68ede99..c01d430 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules/ dist/ .env +# apps/mobile/.env ne porte aucun secret — un réglage de build (fetch RN +# classique, cf. ADR-005) qui doit être le même pour tout le monde. +!apps/mobile/.env .turbo/ coverage/ *.tsbuildinfo diff --git a/CLAUDE.md b/CLAUDE.md index c8d4e1b..97ae0d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,5 +66,6 @@ pnpm + Turborepo. `apps/api` : NestJS, Prisma, PostgreSQL (pgvector + PostGIS), - ✅ **Recette R5 sans clé API + durcissement** (17/07) : la recette a invalidé MiniLM-384 (page-réponse classée derrière des passages sans rapport, 0,24 vs 0,41) → **bascule mesurée vers `paraphrase-multilingual-mpnet-base-v2` 768 d** (ADR-004 amendé, migration `r5_embeddings_mpnet`, découpage ~350 car., seuils 0,45/0,40/0,55) ; recette type ✓ (réponse sourcée p. 2, refus honnête, D1-D5, tout en extractif) ; revue pixel publiée (artefact, 3 arbitrages) ; Dockerfile `siop2-ai` (modèle au build, non-root), compose Dokploy (service interne, `AI_SERVICE_TOKEN` requis, génération opt-in), runbook §5-6 (service IA, calibrage seuils client, réindexation post-déploiement). - ✅ **Revue pixel R5 validée par le référent (17/07)** — 1 correction appliquée sur arbitrage : la Bibliothèque-corpus passe en **tableau** (Document/Rattaché à/Indexation/Corpus + actions ; interrupteur éteint pour les non-indexables), vignettes conservées sur les cartes Documents des fiches ; « Appliquer » direct et calibrage au runbook validés tels quels. 16/16 Playwright rejoués. - 🏁 **R5 CLOSE (17/07/2026, tag `release/r5`)** : recettée par le référent (revue pixel + arbitrage tableau), recette passée SANS clé API, durcissement répété en local conteneurisé. **R0 → R5 : périmètre v1 couvert.** -- 🔄 **Reprise ici** : redéploiement Dokploy de l'instance ENSET (`AI_SERVICE_TOKEN` à créer — runbook §2 — puis « Réindexer tout »). Restes : recette R4 sur téléphone (Expo Go, due avant production client mobile), calibrage `AI_SEUIL_*` sur corpus SPELEV réel, secret `DOKPLOY_WEBHOOK_URL`, production client SPELEV (attend les accès serveur du partenaire). +- ✅ **Recette terrain mobile sur iPhone 15 Pro — 7/7 validés (19/07, ADR-005)** : Expo Go bloqué par le retard d'approbation Apple (SDK 54 vs notre SDK 57) → **décision : builds natifs locaux (Xcode/Android Studio, signature gratuite) pour la vraie recette, Expo Go conservé pour l'aperçu sans installation**. 5 obstacles techniques réels corrigés et documentés (UDID plutôt que nom, ne jamais contourner `expo run:*` par un xcodebuild manuel, modules Expo/RN précompilés SDK 56/57 incompatibles avec la liaison statique du projet, `fetch` global incompatible avec l'upload multipart natif — fix permanent `EXPO_PUBLIC_USE_RN_FETCH=1` dans `apps/mobile/.env`, découverte réseau du dev client peu fiable). Recette iOS : connexion, scan caméra réel (résolution + rejet QR étranger), vrai mode avion, conflit D2 tranché par l'humain avec photo réellement téléversée, persistance à travers fermeture/reconstruction de l'app, suggestions R5 au vrai modèle, purge de sécurité au changement de compte — tout validé. Android : build natif sur émulateur, passage santé complet. +- 🔄 **Reprise ici** : redéploiement Dokploy de l'instance ENSET (`AI_SERVICE_TOKEN` à créer — runbook §2 — puis « Réindexer tout »). Restes : recette Android sur appareil physique (quand disponible), calibrage `AI_SEUIL_*` sur corpus SPELEV réel, secret `DOKPLOY_WEBHOOK_URL`, production client SPELEV (attend les accès serveur du partenaire). - Détail quotidien : `docs/journal/journal.md`. Dépôt : `siop-spelev/siop2` (privé), jalons R0→R5. diff --git a/apps/mobile/.env b/apps/mobile/.env new file mode 100644 index 0000000..3a29651 --- /dev/null +++ b/apps/mobile/.env @@ -0,0 +1,8 @@ +# Depuis Expo SDK 57, le `fetch` global est `expo/fetch` (WinterCG) — qui ne +# comprend pas le raccourci FormData natif `{uri, name, type}` utilisé pour +# joindre un fichier au multipart (photos d'intervention, D5). Sans ce +# réglage, l'upload échoue avec `Unsupported FormDataPart implementation`, +# systématiquement classé à tort comme une coupure réseau (D1) et mis en +# boucle de retry silencieuse — trouvé en recette terrain iOS (17/07/2026). +# Voir ADR-005 et docs/journal/journal.md. +EXPO_PUBLIC_USE_RN_FETCH=1 diff --git a/apps/mobile/app.json b/apps/mobile/app.json index db197be..5b47a98 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -7,7 +7,8 @@ "icon": "./assets/icon.png", "userInterfaceStyle": "automatic", "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.anonymous.siop2-mobile" }, "android": { "adaptiveIcon": { @@ -16,7 +17,8 @@ "backgroundImage": "./assets/android-icon-background.png", "monochromeImage": "./assets/android-icon-monochrome.png" }, - "predictiveBackGestureEnabled": false + "predictiveBackGestureEnabled": false, + "package": "com.anonymous.siop2mobile" }, "web": { "favicon": "./assets/favicon.png" diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 7431b97..2e79c58 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -39,8 +39,8 @@ }, "scripts": { "start": "expo start", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "typecheck": "tsc -p tsconfig.json --noEmit", "test": "jest", diff --git a/docs/03-architecture/adr/ADR-005-distribution-mobile.md b/docs/03-architecture/adr/ADR-005-distribution-mobile.md new file mode 100644 index 0000000..851b16e --- /dev/null +++ b/docs/03-architecture/adr/ADR-005-distribution-mobile.md @@ -0,0 +1,91 @@ +# 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 ` (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. diff --git a/docs/journal/journal.md b/docs/journal/journal.md index ffdb8b8..105b4e5 100644 --- a/docs/journal/journal.md +++ b/docs/journal/journal.md @@ -4,6 +4,53 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook** --- +## 2026-07-19 — Pr. Daaif (+ Claude) — Recette terrain mobile sur iPhone 15 Pro : les 7 points validés (ADR-005) + +**Actions** + +- **La recette « mode avion » due depuis `release/r4`** a enfin pu se jouer sur un vrai appareil (iPhone 15 Pro + du référent). Premier obstacle immédiat : Expo Go (App Store, dernière version) refuse le projet — « supported + SDK 54 » contre notre SDK 57, retard d'approbation Apple structurel, hors de notre contrôle. **Décision du + référent** : construire des builds natifs locaux (Xcode/Android Studio, signature gratuite via Apple ID + personnel) pour la vraie recette terrain, tout en conservant Expo Go comme canal léger pour un aperçu sans + installation ailleurs — actée dans **ADR-005**. +- **Cinq obstacles techniques réels** rencontrés et corrigés en route (détaillés dans ADR-005) : ciblage par UDID + plutôt que nom d'appareil (apostrophe) ; ne jamais contourner `expo run:ios` par un `xcodebuild` manuel (a + produit un crash `dyld: Library not loaded React.framework`) ; **incompatibilité des modules Expo/RN + précompilés (SDK 56/57) avec la liaison statique du projet** — fix `EXPO_USE_PRECOMPILED_MODULES=0` + + `RCT_USE_PREBUILT_RNCORE=0` ; **`fetch` global (`expo/fetch`) incompatible avec l'upload multipart natif** + (`Unsupported FormDataPart implementation`, classé à tort « réseau indisponible » par notre propre gestion + d'erreur) — fix permanent `EXPO_PUBLIC_USE_RN_FETCH=1` committé dans `apps/mobile/.env` ; découverte réseau du + dev client peu fiable sur ce Wi-Fi (IP du Mac changée 4 fois, reprises via `expo run:ios --device ` qui + transmet l'adresse de Metro par deep link plutôt que par découverte automatique). +- **Recette terrain iOS — 7/7 points validés sur iPhone physique** : (1) connexion démo ; (2) scan caméra réel — + résolution locale d'une étiquette réelle (D4) + rejet propre d'un QR étranger ; (3) vrai mode avion (D1) — file + d'écriture visible ; (4) verrou optimiste (D2) — conflit provoqué en modifiant l'OT côté serveur pendant la + coupure, écran Synchro affichant le conflit, tranché par l'humain, OT clos avec bilan complet et **photo + réellement téléversée** (885 Ko, compression D5 confirmée) ; (5) persistance — saisie en file survivant à une + fermeture complète + reconstruction de l'app, resynchronisée au retour réseau (limite documentée : un build dev + client ne peut PAS démarrer à froid hors-ligne, aucun bundle embarqué — un vrai test « cold start offline » + demanderait un build Release, hors périmètre aujourd'hui) ; (6) suggestions IA R5 au vrai modèle sur le + téléphone, chips appliqués pré-remplissant les sélecteurs ; (7) sécurité ADR-003 — déconnexion (appui long, + purement locale) puis bascule de compte démo, file et cache vides, aucune fuite entre comptes. +- **Android** : build natif installé sur l'émulateur (`Pixel_3a_API_34`, Android Studio déjà en place) ; passage + santé complet — connexion démo, navigation Ma journée → fiche appareil (résolution de référence manuelle, + équivalent du scan sans caméra d'émulateur, confirmée fonctionnelle), écran Scanner sans plantage. + +**Décisions** + +- ADR-005 actée : deux canaux de distribution mobile (builds natifs pour la vraie recette, Expo Go pour l'aperçu + sans installation), managed workflow conservé (`ios/`/`android/` jamais committés). +- La validation terrain due depuis `release/r4` est **levée pour iOS**. La recette sur **appareil Android + physique** (vrai mode avion, vraie caméra) reste un reste, faute d'appareil disponible aujourd'hui — même + situation qu'iOS avant cette session. + +**Prochaine étape** : recette Android sur appareil physique quand disponible ; redéploiement Dokploy +(`release/r3` puis r5 avec `AI_SERVICE_TOKEN`) ; calibrage `AI_SEUIL_*` sur corpus SPELEV réel ; secret +`DOKPLOY_WEBHOOK_URL`. + +--- + ## 2026-07-17 — Pr. Daaif (+ Claude) — 🏁 R5 CLOSE : tag `release/r5` **Actions**