Écran d'une application : nom de l'application (FR/EN/NL), case
d'affichage du scan QR, et liens stores réservés au SuperAdmin.
MultiStringInputContainer accepte des langues explicites, faute de
configuration sélectionnée sur cet écran.
Le QR d'une section pointe vers app.myinfomate.be : la page /download
si l'instance a une app mobile, la section web directement sinon.
web.mymuseum.be était désuet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regroupe la configuration de carte en deux blocs (Carte / Points d'interet),
ajoute le filtre par categorie et l'editeur de point geographique, et passe
les ecrans Users et les jauges de quota par AppLocalizations (FR/EN/NL).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
L'infobulle promettait que ces phrases seraient traduites « comme le reste
de vos contenus », mais rien ne les traduisait jamais : BuildFallbackInstruction
filtre sur la langue du visiteur et, ne trouvant rien, laisse le modèle
improviser son refus. Les formulations du client ne servaient qu'en français.
L'enregistrement traduit désormais vers les langues déclarées sur les canaux,
en n'appelant l'IA que pour ce qui en a besoin : les langues sans traduction,
et toutes les langues si une phrase source a changé depuis le chargement.
Une sauvegarde qui ne touche pas aux formulations ne consomme donc pas de
jetons. Les langues visées sont celles des canaux, pas les dix supportées,
pour la même raison.
Un échec de traduction n'empêche pas l'enregistrement : le reste est sauvé,
les traductions précédentes sont conservées et l'écran le signale. Effacer
toutes les formulations efface aussi leurs traductions, devenues sans objet.
Corrige au passage le mot de réveil affiché : le modèle OpenWakeWord est
« hey_viva » / « hey_marco », le visiteur doit donc dire « Hey Viva », pas
« Viva ».
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La carte « Où le guide est disponible » affichait la valeur entière de l'enum
AppType (0, 1, 2…) au lieu d'un libellé. Réutilise les clés statsChannel*
déjà traduites en FR/EN/NL pour l'écran Statistiques.
L'activation de l'assistant par canal se réglait dans chaque onglet
d'application, alors que le champ vit sur ApplicationInstance et se résout
par (InstanceId, AppType). Le réglage passe dans la liste des canaux du
Guide IA, où tous les canaux sont visibles au même endroit ; le paramètre
showAssistant de AppConfigurationLinkScreen devient inutile.
Les notifications de cet écran passaient par ScaffoldMessenger (en bas),
contrairement au reste de l'application qui utilise showNotification
(toastification, topRight).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Portail de facturation : le bouton manquait, et l'écran n'affichait rien
du tout hors essai — if (isTrialActive) enveloppait le seul bouton, donc
un client payant arrivait sur un écran sans action. Désormais Checkout
pendant l'essai, portail après ; les deux ne coexistent jamais.
Le 409 a son propre message : les instances venues de Mongo n'ont pas de
StripeCustomerId, « réessayez plus tard » les ferait attendre quelque
chose qui n'arrivera jamais.
Diviseur de questions : guide_ia_screen lit aiTokensPerQuestion du serveur
au lieu du /1000 codé en dur, par appel http direct — le motif déjà établi
dans cet écran pour knowledge et insights. Si l'appel échoue, la ligne
« ~N questions » disparaît plutôt que d'afficher un repli : sur une jauge
qui sert à décider d'un achat, pas de chiffre vaut mieux qu'un chiffre
faux, ce que le /1000 faisait depuis le début à un facteur 10 près.
manager_api_new étendu à la main, générateur non relancé.
4 clés i18n FR/EN/NL. flutter analyze lib sans erreur, flutter build web OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Écran « Activité » sur GET /api/Audit, réservé au SuperAdmin comme la policy
de l'endpoint (menuId 13, conditionné à role.value == 0). Filtres instance,
type d'entité, utilisateur et plage de dates, pagination 50, clic sur une
ligne pour le détail avant/après en table plutôt que le JSON brut. Les ids
d'instance et d'utilisateur sont résolus en noms, un id absent de l'annuaire
restant affiché tel quel : le journal doit rester lisible après la
suppression de ce qu'il décrit.
manager_api_new n'est pas touché. L'écran passe par un http.get direct au
Bearer, comme _loadKnowledge / _loadInsights / _reindex du Guide IA : une
lecture seule ne justifie pas d'étendre un client qui s'édite à la main, et
le déclencheur de génération n'existe plus depuis le lot A.
Côté utilisateurs, compteur « X / 5 » et bouton d'ajout désactivé au
plafond. Le SuperAdmin en est exclu : son GET /api/User renvoie toutes les
instances, compter cette liste contre un plafond par instance n'aurait
aucun sens. Le masquage du rôle SuperAdmin pour un InstanceAdmin, resté en
question dans todo-features.md, était déjà fait — _allowedRoles filtre sur
r >= callerRole.
Trois pièges relevés en câblant :
- AuditController renvoie les entités brutes, pas un DTO — les champs sont
ceux d'AuditLog en camelCase.
- invokeAPI ne lève pas sur un code d'erreur et son résultat était ignoré
dans users_screen : un e-mail déjà utilisé (409) ou un rôle refusé (403)
ne produisait aucun message. Le statut est désormais testé et le corps
affiché, ce qui rendra visible sans retouche le futur 422 du plafond.
- DropdownButtonFormField ne relit pas initialValue sur reconstruction
(FormField.didUpdateWidget ne traite que forceErrorText) : « Réinitialiser
les filtres » vidait la requête sans vider l'affichage. Remplacé par un
DropdownButton piloté.
40 clés i18n FR/EN/NL, flutter gen-l10n relancé. flutter analyze propre sur
les dossiers travaillés, flutter build web vert.
Deux dettes serveur restent ouvertes, hors périmètre de ce commit : le
plafond de 5 n'est pas appliqué par UserController (ce qui est livré ici est
un garde-fou d'interface, un POST direct passe toujours), et aucune section
n'est journalisée — AuditedTypes.Contains exige l'égalité exacte alors que
Section est abstraite. Détail dans DOCS/todo-features.md et v1-plan.md lot F.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Option A. Il fallait cinq surfaces empilées pour poser une question sur une
étape et l'écrire en néerlandais ; la profondeur maximale retombe à 2 —
l'éditeur, puis la traduction.
Les trois showNewOrUpdate… sont supprimés au profit d'un rail d'étapes
toujours visible à gauche, d'un panneau de détail à droite, et d'une question
qui se déplie sur place au lieu d'ouvrir une quatrième fenêtre.
Les champs sont d'abord sortis en trois widgets autonomes (ParcoursFields,
EtapeFields, QuestionFields) : c'est ce qui a rendu la refonte possible sans
tout réécrire — 1663 lignes retirées, l'écran n'a pas réécrit les champs, il
les a réagencés.
SAUVEGARDE AU FIL DE L'EAU (héritée de DB2) — plus de bouton Sauvegarder,
tout part à la saisie avec un débounce de 700 ms ; ajouts, suppressions et
réordonnancements partent sans attendre. GuidedPathApi masque à l'éditeur le
fait qu'un parcours vive sous une SectionParcours ou sous une SectionMap :
mêmes opérations, seule la classe générée change.
Deux pièges du backend, trouvés en lisant le contrôleur plutôt qu'en le
supposant :
- UpdateGuidedPath supprime les étapes absentes du DTO, donc le payload
n'emporte que celles qui ont déjà un id — les autres attendent leur
CreateGuidedStep et seraient dupliquées.
- Les questions n'ont pas d'endpoint propre (c'est voulu : elles partent avec
leur étape, que GuidedStep.FromDTO synchronise). Leur id entier est donc
récupéré après coup par `order`, seul repère stable entre la liste locale
et celle du serveur.
Le parcours est créé à la première modification, pas à l'ouverture : une
fenêtre neuve refermée intacte ne laisse rien en base. Si une écriture
échoue, la fenêtre refuse de se fermer et le pied de page porte un
« Réessayer ».
Le garde-fou de DB2 est retiré, comme prévu : un avertissement de perte de
travail n'a plus d'objet quand il n'y a plus rien à perdre. 14 clés i18n
FR/EN/NL ajoutées, 3 retirées.
flutter build web ✅, analyse du dossier sans erreur. Reste la vérification à
l'œil (DB5).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PDF en média d'étape — la liste de types du sélecteur de médias devient un
paramètre (`kSliderContentResourceTypes` par défaut) et l'étape de parcours
passe `kGuidedStepResourceTypes`, soit les types du slider plus Pdf.
QuestionType — le client généré nomme ses valeurs number0/1/2, ce qui
poussait le front à comparer des entiers bruts (`?.value == 2 ? 'Puzzle'`).
Alias nommés d'après l'enum serveur ajoutés à la main dans
question_type.dart (simple / multipleChoice / puzzle), `values` inchangé :
ce sont les mêmes trois valeurs. Les 7 usages number* du dialogue de
question sont migrés avec.
Fournisseur de carte (W1) — décision : visitapp-web reste sur Leaflet. Le
champ ne peut pas être masqué pour le web, il est porté par la section et
une même configuration est servie au mobile comme au web ; il est donc
conservé et documenté dans l'interface comme réglage mobile/tablette.
flutter build web ✅, analyse du dossier sans erreur ni warning.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les trois dialogues imbriqués (Parcours, Étape, Question) fermaient sur
« Annuler » par un Navigator.pop sans rien demander, chacun jetant tout ce
qui était saisi sous lui — le plus coûteux étant une question de quiz avec
ses réponses.
Confirmation ajoutée aux trois, plus interception du retour arrière du
navigateur par PopScope : le clic hors fenêtre et la touche Échap étaient
déjà neutralisés par barrierDismissible: false, le retour navigateur était
le seul chemin de perte encore ouvert.
Deux points non évidents :
- l'instantané de référence est pris après la première frame, parce que
ensureSimpleResponse écrit dans la question pendant la construction et
ferait passer un dialog intact pour modifié ;
- les deux dialogues partageant le même Navigator, popper depuis onYes
fermerait la confirmation et non l'éditeur — la fermeture est différée
d'une frame pour être déterministe.
Réutilise showConfirmationDialog plutôt que d'ajouter un second composant.
3 clés i18n FR/EN/NL. flutter build web ✅, analyse du dossier propre.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Socle visuel (constants.dart)
- 11 rôles typographiques, 8 espacements, 5 rayons, paddings de carte et de page,
extraits du CSS des maquettes validées (DOCS/claude design/).
- Les couleurs restent celles de l'app : un écran neuf se fond dans manager-app,
il n'y ouvre pas une seconde palette. Seules les valeurs sans équivalent
existant sont reprises — remplissages discrets, bordures, gris atténué, ambre.
- Mesuré avant : 11 tailles de police différentes dans lib/Components/, de 9 à 25 px.
Écran Guide IA
- Coquille à deux onglets : Configuration / Ce que demandent vos visiteurs.
- Aperçu de conversation branché sur le vrai POST /api/AI/chat, pas une simulation.
- Carte « Ce que connaît votre guide » sur GET /api/Ai/knowledge/{id} : mesuré sur
l'index vectoriel, pas sur les tables de contenu — une section désactivée en est
purgée. Les « points d'intérêt » de la maquette deviennent « morceaux de contenu » :
les points d'une carte sont indexés dans le texte de leur SectionMap.
- Onglet des questions visiteurs alimenté par GET /api/Ai/insights/{id}. Une carte
sans données se masque au lieu d'afficher un cadre creux.
Client API
- AIApi était dans le client généré mais exposé nulle part dans client.dart, où
les 18 autres façades le sont. Câblé.
34 clés i18n FR/EN/NL. flutter analyze propre, flutter build web ✅.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Statistiques — refonte complète, aucun changement backend
statistics_screen.dart réécrit (+1832) : barres horizontales monochromes à la place
des barres verticales tronquées et des deux anneaux, barre de filtres unique avec les
volumes par canal, règle mono-canal, 4 KPI portant chacun leur variation, bandeau
« à retenir », courbe en aire avec bandes de week-end. La période précédente s'obtient
en rappelant le même endpoint.
statistics_report.dart : export PDF généré côté client (paquet pdf Dart), il partage
les valeurs calculées de l'écran — un chiffre ne peut pas diverger entre l'écran et le
document envoyé à la commune. Deux puces du sommaire promettaient des données
inexistantes (parcours terminés, questions au guide IA), retirées.
⚠️ Jamais ouvert dans un navigateur. Cases de test : test-plan.md §8bis / §8ter.
Guide IA
Screens/GuideIa/guide_ia_screen.dart — onglet Configuration. Menu conditionné à
isAssistant, le même drapeau que la garde d'AiController. L'onglet « Ce que demandent
vos visiteurs » n'est pas dans ce commit : le schéma backend est prêt, l'UI non.
Onboarding self-service
Screens/Auth/ (mot de passe oublié, définition du mot de passe),
Screens/Billing/subscription_screen.dart, ai_quota_hint.dart.
⚠️ Aucun parcours joué de bout en bout — test-plan.md §18.
Parcours guidés
progression_mode.dart : 9 booléens sur 3 niveaux remplacés par 3 questions.
Popups GuidedPath / GuidedStep / QuizQuestion mises à jour en conséquence.
Client API (manager_api_new) — édité À LA MAIN, ne pas relancer la génération
onboarding_api.dart, authentication_api.dart (+80), instance_dto (champs Guide*),
guided_step / quiz_question_guided_step (flags morts retirés).
Le // @dart=2.18 manquant dans onboarding_api.dart cassait les 3 apps Flutter d'un
coup — corrigé ici.
i18n : ~180 clés par langue (FR/EN/NL) + fichiers générés.
Tests : progression_mode_test, statistics_report_test (le second a attrapé deux
plantages qui seraient sortis au premier clic).
flutter build web ✅. flutter analyze : 68 erreurs, toutes dans les fichiers modèle
orphelins de manager_api_new — dette connue, pas une régression, ces fichiers ne sont
pas dans le graphe de compilation.