From 4af3f5668a73cac669725d8e90af42d646ffa011 Mon Sep 17 00:00:00 2001 From: pr-daaif Date: Sat, 1 Aug 2026 22:09:04 +0100 Subject: [PATCH] =?UTF-8?q?fix(mobile):=20dict=C3=A9e=20valid=C3=A9e=20sur?= =?UTF-8?q?=20iPhone=20physique=20=E2=80=94=202=20bugs=20r=C3=A9els=20corr?= =?UTF-8?q?ig=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setAudioModeAsync({allowsRecording:true}) manquant avant recorder.record() (RecordingDisabledException iOS) ; EXPO_USE_PRECOMPILED_MODULES/ RCT_USE_PREBUILT_RNCORE jamais persistés dans .env (documentés dans ADR-005 mais seulement exportés en shell ad-hoc) — corrigé pour que tout rebuild depuis zéro ne reproduise plus le crash dyld. Recette terrain complète vérifiée : dictée → note → clôture → réindexation → recherche sémantique. Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 3 +- apps/mobile/.env | 8 +++++ apps/mobile/app/ot/[id]/cloture.tsx | 11 ++++++- .../03-architecture/adr/ADR-004-modeles-ia.md | 18 ++++++++-- docs/journal/journal.md | 33 +++++++++++++++++++ 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 48b7e59..f15d98f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,5 +68,6 @@ pnpm + Turborepo. `apps/api` : NestJS, Prisma, PostgreSQL (pgvector + PostGIS), - 🏁 **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.** - ✅ **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. - ✅ **Dictée implémentée (22/07, ADR-004 §5)** : écran Voix R5 (jamais construit) — `faster-whisper` local (CTranslate2, CPU, opt-in `AI_TRANSCRIPTION=off|locale`, défaut off), audio jamais persisté (purge serveur ET mobile quoi qu'il arrive), transcription → relecture humaine → suggestion immédiate et/ou sauvegarde dans `InterventionReport.note` (champ existant depuis R2, jamais eu d'écran jusqu'ici) → `note` rejoint le corpus à la clôture comme les bilans codés. Mobile : `expo-audio`/`expo-file-system`, bouton dicter sur l'écran de clôture. Docker `siop2-ai` embarque le modèle (1,54→2,19 Go). Vérifié réellement (voix de synthèse → texte fidèle, bout en bout API, conteneur Docker construit, chaîne corpus complète note→clôture→réindexation→recherche). 80 tests API, 29 pytest ai, mobile vert. -- 🔄 **Reprise ici** : test tactile de la dictée sur iPhone (bloqué par une connexion USB qui ne s'est pas rétablie malgré câble/port/redémarrage — reporté comme la recette Android). Redéploiement Dokploy de l'instance ENSET (`AI_SERVICE_TOKEN` à créer — runbook §2 — puis « Réindexer tout »). Restes : recette Android sur appareil physique, calibrage `AI_SEUIL_*` et qualité darija sur corpus SPELEV réel, secret `DOKPLOY_WEBHOOK_URL`, production client SPELEV (attend les accès serveur du partenaire). +- ✅ **Dictée validée sur iPhone physique (01/08, ADR-004 §5)** : le blocage USB du 22/07 était un faux négatif de `system_profiler` (outil de diagnostic en panne, pas le câble). Deux bugs réels trouvés et corrigés : `expo-audio` iOS exige `setAudioModeAsync({ allowsRecording: true })` avant `record()` (absent, jamais détecté hors appareil réel) ; le correctif des modules précompilés (ADR-005) n'avait jamais été rendu permanent dans `apps/mobile/.env` — corrigé. Chaîne complète vérifiée en vrai : micro → dictée → transcription fidèle → note → clôture → réindexation → **retrouvé par la recherche sémantique**. +- 🔄 **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, calibrage `AI_SEUIL_*` et qualité darija 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 index 3a29651..d8c48fd 100644 --- a/apps/mobile/.env +++ b/apps/mobile/.env @@ -6,3 +6,11 @@ # 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 + +# Modules Expo/RN précompilés (XCFrameworks, SDK 56/57) incompatibles avec la +# liaison statique de ce projet (pas de use_frameworks!) : sans ce réglage, +# l'app compile mais CRASHE au lancement +# (dyld: Library not loaded: @rpath/React.framework/React, signal 6). +# Force la recompilation depuis les sources — voir ADR-005. +EXPO_USE_PRECOMPILED_MODULES=0 +RCT_USE_PREBUILT_RNCORE=0 diff --git a/apps/mobile/app/ot/[id]/cloture.tsx b/apps/mobile/app/ot/[id]/cloture.tsx index a3a97e3..d808638 100644 --- a/apps/mobile/app/ot/[id]/cloture.tsx +++ b/apps/mobile/app/ot/[id]/cloture.tsx @@ -2,7 +2,12 @@ import * as FileSystem from 'expo-file-system'; import { router, useLocalSearchParams } from 'expo-router'; import { useState } from 'react'; import { Pressable, ScrollView, Text, TextInput, View } from 'react-native'; -import { RecordingPresets, requestRecordingPermissionsAsync, useAudioRecorder } from 'expo-audio'; +import { + RecordingPresets, + requestRecordingPermissionsAsync, + setAudioModeAsync, + useAudioRecorder, +} from 'expo-audio'; import { SafeAreaView } from 'react-native-safe-area-context'; import { BILAN_FIELD_LABELS, @@ -197,6 +202,9 @@ function CarteSuggestion({ const demarrerDictee = async () => { const { granted } = await requestRecordingPermissionsAsync(); if (!granted) return; + // iOS refuse recorder.record() tant que la session audio n'a pas été + // explicitement autorisée à enregistrer (RecordingDisabledException). + await setAudioModeAsync({ allowsRecording: true, playsInSilentMode: true }); await recorder.prepareToRecordAsync(); recorder.record(); setEnregistrement(true); @@ -205,6 +213,7 @@ function CarteSuggestion({ const terminerDictee = async () => { await recorder.stop(); + await setAudioModeAsync({ allowsRecording: false }); // n'arme le micro que le temps de dicter setEnregistrement(false); const uri = recorder.uri; if (!uri) return; diff --git a/docs/03-architecture/adr/ADR-004-modeles-ia.md b/docs/03-architecture/adr/ADR-004-modeles-ia.md index 48d90c5..7e97644 100644 --- a/docs/03-architecture/adr/ADR-004-modeles-ia.md +++ b/docs/03-architecture/adr/ADR-004-modeles-ia.md @@ -80,6 +80,18 @@ - Implémentée et vérifiée le 22/07/2026 : transcription réelle (français, voix de synthèse) juste et rapide en local, bout en bout via l'API NestJS, et dans l'image Docker de production construite pour l'occasion (modèle embarqué, aucun téléchargement - au démarrage). **Reste** : le test tactile sur iPhone physique (bouton dicter, permission - micro) n'a pas pu se jouer — blocage USB persistant malgré câble/port/redémarrage - multiples, reporté comme la recette Android (attend un accès matériel qui fonctionne). + au démarrage). Le test tactile sur iPhone physique n'avait pas pu se jouer ce jour-là + (blocage USB), reporté. +- **Recette terrain sur iPhone physique complétée le 01/08/2026** : micro, dictée réelle, + transcription fidèle, sauvegarde dans le bilan, clôture, puis retrouvée par la recherche + sémantique après réindexation — la chaîne complète (D5) est validée sur vrai matériel. + Deux bugs réels trouvés et corrigés en route : + 1. `expo-audio` sur iOS refuse `recorder.record()` tant que la session audio n'a pas + été explicitement autorisée à enregistrer — `RecordingDisabledException`, corrigée + par un appel à `setAudioModeAsync({ allowsRecording: true })` avant l'enregistrement + (et remis à `false` après, par hygiène — le micro ne reste pas « armé »). + 2. Le contournement des modules Expo/RN précompilés (`EXPO_USE_PRECOMPILED_MODULES=0`, + `RCT_USE_PREBUILT_RNCORE=0` — ADR-005) n'avait **jamais été rendu permanent** dans + `apps/mobile/.env`, contrairement au correctif FormData voisin : une reconstruction + ultérieure sans ces variables aurait reproduit le crash au lancement déjà documenté. + Désormais committé aux côtés de `EXPO_PUBLIC_USE_RN_FETCH`. diff --git a/docs/journal/journal.md b/docs/journal/journal.md index 310e09e..4874d3c 100644 --- a/docs/journal/journal.md +++ b/docs/journal/journal.md @@ -4,6 +4,39 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook** --- +## 2026-08-01 — Pr. Daaif (+ Claude) — Dictée validée sur iPhone physique (ADR-004 §5) + +**Actions** + +- Reprise du reste laissé le 22/07 : le test tactile de la dictée sur iPhone, alors bloqué par une connexion + USB qui ne se rétablissait pas. Diagnostic cette fois : `system_profiler` (l'outil utilisé pour vérifier la + détection USB) était lui-même en panne et renvoyait un faux négatif silencieux — `ioreg` a révélé que + l'iPhone était en réalité détecté. Le vrai câble/port n'a jamais été le problème. +- Une fois l'app reconstruite (certificat gratuit expiré après plus d'une semaine — régénéré via le pipeline + officiel `expo run:ios`, jamais par un contournement manuel), **deux bugs réels trouvés et corrigés** : + 1. `expo-audio` sur iOS refuse d'enregistrer sans un appel explicite à `setAudioModeAsync({ allowsRecording: + true })` avant `recorder.record()` — absent du code initial, jamais détecté hors d'un vrai appareil. + 2. Le correctif des modules Expo/RN précompilés (ADR-005, `EXPO_USE_PRECOMPILED_MODULES=0` / + `RCT_USE_PREBUILT_RNCORE=0`) n'avait **jamais été committé** dans `apps/mobile/.env`, contrairement au + correctif FormData voisin déjà permanent — un vrai trou qui aurait refait planter l'app à la prochaine + reconstruction depuis zéro. Corrigé : les deux variables vivent maintenant côte à côte dans `.env`. +- **Recette réelle bout en bout** : micro → dictée d'une phrase → transcription fidèle (`faster-whisper` réel) + → relecture → sauvegarde dans le bilan → clôture de l'OT → réindexation du corpus → **le contenu dicté + retrouvé par la recherche sémantique** (score 0,476). La boucle complète de l'idée d'origine (« cette + transcription devrait alimenter le corpus ») est validée sur vrai matériel, vraie voix, vrai réseau. +- Infra locale relancée depuis zéro après une semaine d'inactivité (Docker, conteneurs `siop2-*`, API, + service IA) — occasion de vérifier que le redémarrage à froid fonctionne proprement. + +**Décisions** + +- Aucune — cette session referme un reste déjà décidé, sans nouvelle décision de conception. + +**Prochaine étape** : recette Android sur appareil physique (toujours en attente d'un appareil) ; redéploiement +Dokploy (`AI_SERVICE_TOKEN`) ; calibrage `AI_SEUIL_*` et qualité darija sur corpus SPELEV réel ; secret +`DOKPLOY_WEBHOOK_URL`. + +--- + ## 2026-07-22 — Pr. Daaif (+ Claude) — Dictée implémentée : audio → transcription locale → corpus (ADR-004 §5) **Actions**