Cinquante-cinq chantiers clos entre le 5 et le 13 août 2026.
+ Cinquante-six chantiers clos entre le 5 et le 13 août 2026.
+
+ Une conversation, plusieurs surfaces — miroir vocal et conversationId
+ Le chat écrit, le vocal et le proactif partagent désormais une seule conversation, portée par VisitAppContext.assistant. Les trois AssistantService séparés ont disparu ; le maxHistory: 6 du vocal s'aligne sur 10, deux surfaces qui partagent une conversation ne pouvant pas la tronquer différemment selon le point d'entrée. La concision vocale vient d'isVoice, qui change le prompt côté serveur.
+ ⚠️ Le vrai obstacle n'était pas l'affichage mais la forme du chat. AssistantChatSheet gardait ses messages en List<Widget> — des bulles déjà construites. On ne rejoue pas une conversation à partir de widgets, et un tour vocal survenu pendant que la feuille était fermée n'aurait jamais pu y entrer. Le service porte maintenant les tours en données et notifie ; la feuille rend depuis eux et se met à jour en direct.
+ ⚠️ Deux listes, délibérément : celle envoyée au modèle et celle affichée ne coïncident pas. Un message d'erreur se montre sans repartir au guide — il n'a pas à s'excuser d'une panne au tour suivant ; le prompt d'un déclenchement proactif part au guide sans jamais s'afficher, seule sa réponse apparaît, marquée « À voix haute ». Sans ce marquage, le visiteur trouverait dans son chat des messages qu'il n'a jamais tapés.
+ ✅ conversationId est enfin envoyé — et le champ manquait dans le client généré, ce qui explique que personne ne l'envoyait. Ajouté à la main. Il est renouvelé quand la conversation est vidée, sinon la visite entière d'un visiteur n'en formerait qu'une. 2 tests serveur fixent le lien entre deux tours et le repli Guid.NewGuid(), que rien ne couvrait. dotnet test 215, APK dev ✅, flutter build web ✅.
+
+
Les prompts du mode proactif ne polluent plus « Ce que demandent vos visiteurs »
Le dernier bloquant du proactif. RecordVisitorQuestion écrivait Question = request.Message : chaque passage devant une œuvre aurait ajouté une fausse question de visiteur — une consigne que le système s'écrit à lui-même — dans l'écran fait pour montrer ce que les humains demandent. Et HasAnswer se déduisant des sources, un prompt d'accueil sans citation aurait gonflé le bloc ambre des questions sans réponse. Même famille que WeatherSyncService noyant le journal d'audit.
diff --git a/v1-plan.md b/v1-plan.md
index a2d2575..85cdf5b 100644
--- a/v1-plan.md
+++ b/v1-plan.md
@@ -176,6 +176,16 @@ Ne change pas : la popup de traduction (un niveau justifié, langues verticales,
✅ **Volet `manager-service` terminé le 2026-08-13.** Customer Portal livré, ratio jetons → questions calé sur du réel ; le rate limiting, l'endpoint `ApplicationInstance` et les quotas seed **étaient déjà faits** et n'attendaient qu'une vérification (détail dans les lignes concernées). `dotnet build` vert, `dotnet test` **211 au total : 197 passés, 14 sautés** (Testcontainers, pas de démon Docker), 0 échec.
+✅ **Miroir de la conversation vocale livré le 2026-08-13.** `flutter analyze lib` sans erreur, APK `dev` ✅, `dotnet test` **215**, `flutter build web` ✅.
+
+**Une seule conversation, portée par `VisitAppContext.assistant`.** Le chat écrit, le vocal et le proactif partagent l'instance ; les trois `AssistantService` séparés ont disparu. Le `maxHistory: 6` du vocal s'aligne sur 10 : deux surfaces qui partagent une conversation ne peuvent pas la tronquer différemment selon le point d'entrée, et la concision vocale vient d'`isVoice`, qui change le prompt côté serveur.
+
+⚠️ **Le vrai obstacle n'était pas l'affichage mais la forme du chat** : `AssistantChatSheet` gardait ses messages en `List` — des bulles déjà construites. On ne rejoue pas une conversation à partir de widgets, et un tour vocal survenu pendant que la feuille était fermée n'aurait jamais pu y entrer. Le service porte désormais les tours **en données** (`AssistantTurn`) et notifie ; la feuille rend depuis eux et se met à jour en direct.
+
+⚠️ **Deux listes, et c'est délibéré** : `_history` (envoyée au modèle) et `_turns` (affichée) ne coïncident pas. Un message d'erreur se montre sans repartir au guide — il n'a pas à s'excuser d'une panne au tour suivant ; et le prompt d'un déclenchement proactif part au guide **sans jamais s'afficher**, seule sa réponse apparaît, marquée « À voix haute ». Sans ce marquage, un visiteur ouvrant le chat trouverait des messages qu'il n'a jamais tapés.
+
+✅ **`conversationId` est enfin envoyé, et le champ manquait dans le client généré** — c'est ce qui expliquait que personne ne l'envoyait. Ajouté à la main dans `manager_api_new`. Il est renouvelé quand la conversation est vidée : sinon la visite entière d'un même visiteur n'en formerait qu'une. **2 tests serveur** fixent le lien entre deux tours et le repli — la branche `Guid.NewGuid()` n'était couverte par rien.
+
**Ce qui reste au lot F, et dans quel repo** — le backend ne bloque plus rien :
- ~~`manager-app`~~ ✅ **livré le 2026-08-13** : bouton du Customer Portal et diviseur `aiTokensPerQuestion`. `flutter analyze lib` **sans erreur**, `flutter build web` ✅. Détail ci-dessous.
- `mymuseum-visitapp` : déclenchement proactif (+ M3), miroir de la conversation vocale, émission des événements vocaux. C'est désormais **tout le reste du lot F**, et le plus gros.