Seize chantiers clos entre le 5 et le 10 août 2026.
Les maquettes sortent du compte Claude et entrent dans le repo
Cause diagnostiquée d'un défaut qui se répétait : les trois maquettes vivaient dans des artifacts d'un autre compte, illisibles depuis la machine de dev — chaque session redessinait. D'où un écran Guide IA « livré » à la moitié de sa spec et des stats d'assistant à zéro. Le HTML est rapatrié dans claude design/, JS interactif compris. Même découpage que ce tableau : contenu dans le repo, diffusion par artifact. Constat en ouvrant les fichiers : Guide IA et Statistiques déclarent des tokens rigoureusement identiques — c'était déjà un design system, jamais porté en Dart — et --brand #264863 est kPrimaryColor.
Socle visuel — 11 tailles de police deviennent 11 rôles
Mesuré dans lib/Components/ : onze tailles différentes (9 à 25 px), aucune échelle, constants.dart portant encore // TO FILL WITH CORRECT COLOR et deux styles. L'échelle, les espacements et les rayons sont extraits du CSS des maquettes ; 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. Pas de thème sombre. La reprise des écrans existants reste son propre chantier, pas un effet de bord.
Écran Guide IA — la coquille à onglets, et ce que le guide sait vraiment
L'écran passe de cinq cartes en colonne à la structure de la maquette : deux onglets, deux colonnes. L'aperçu de conversation interroge le vrai /api/AI/chat, pas une simulation — au passage, AIApi était dans le client généré mais exposé nulle part dans client.dart, où les 18 autres façades sont. Câblé. Nouveau GET /api/Ai/knowledge/{id} pour la carte « Ce que connaît votre guide » : tout est agrégé sur ContentEmbedding, donc sur ce qui est réellement indexé — compter les sections publiées donnerait un chiffre plus flatteur et faux, une section désactivée étant purgée de l'index. Le « points d'intérêt » de la maquette devient « morceaux de contenu » : les points d'une carte sont indexés dans le texte de leur SectionMap, les compter à part répondrait à une autre question. 34 clés i18n FR/EN/NL, dotnet test 130/130, flutter build web ✅.
Le changement de plan donne enfin ce qu'il vend
Trois défauts qui se tenaient la main. 1) Updateinstance changeait SubscriptionPlanId mais ne recopiait jamais les quotas du nouveau plan — CreateInstance le faisait, pas lui : passer un client de Starter à Premium ne lui donnait ni stockage ni jetons IA. 2) CheckQuota ne bloquait que si quota > 0 : à 0 jeton, ni blocage ni compteur — l'ambiguïté vient du code lui-même, où 0 veut dire illimité pour le stockage et pas d'IA pour les jetons. Une instance Starter avec IsAssistant à true consommait donc gratuitement et sans compteur, et toute instance migrée depuis Mongo arrive à 0 (écart c du §1quinquies). Corrigé en 403 avant tout appel au modèle. 3) BackfillInstanceAsync n'était appelé par personne : branché automatiquement quand AiTokensPerMonth passe de 0 à une valeur, plus un bouton de relance réservé au SuperAdmin dans l'écran Guide IA — outil de réparation, pas fonctionnalité : exposé au client il serait cliqué à chaque réponse décevante, pour un coût d'embedding complet et sans rien améliorer. IBackgroundJobClient injecté au lieu de la façade statique, qui lève sans JobStorage et cassait les tests. 130/130.
La dimension 768 n'est plus un pari
IEmbeddingService + GoogleEmbeddingService (batch par 50), et surtout vérifiés contre l'API réelle. Trois choses qu'aucun plan ne disait : le modèle sort en 3072 dimensions par défaut — dimensions: 768 est obligatoire ; les vecteurs réduits ne sont pas normalisés (norme mesurée 0,578, pas 1) et sans normalisation côté client le classement cosinus serait faussé sans lever d'erreur ; et Google ne renvoie pas le champ index du contrat OpenAI. Recherche cross-lingue jouée en base : question NL → contenu NL 0,8145, FR pertinent 0,7982, FR hors-sujet 0,4818. L'écart NL/FR n'étant que de 0,016, le bonus de score par langue est bien nécessaire.
L'historique des stats passe à 13 mois pour tous
L'axe « durée » disparaît du découpage commercial : HasAdvancedStats devient le seul différenciateur stats, et l'export PDF est inclus dès qu'il y a les stats. Motif : un dossier de subside est annuel — plafonné à 30 jours le rapport n'était envoyable à personne. Et la landing ne promettait aucune durée (la chaîne « 30 jours » existe dans translations.ts mais n'est affichée nulle part). Migration UnifyStatsRetentionTo13Months — elle met à jour les plans et reprend les instances en SQL, sans quoi les clients actuels seraient restés à 30 jours : c'est Instance.StatsHistoryDays que lit le contrôleur. dotnet test 124/124. ⛔ La purge associée est codée mais inactive jusqu'à la mise en place du pg_dump.
Configuration des parcours — 4 correctifs
Relecture du 09/08 de l'écran refondu le 06/08. Le plus sérieux : un parcours créé sans toucher à la question de progression affichait « Dans l'ordre » et enregistrait « Libre ». Les 12 tests de progression_mode ne pouvaient pas le voir — ils couvrent la relecture des booléens, pas le DTO fabriqué par la popup. Aussi : vidéos et audios d'étape rendus en dur comme des images dans les deux apps visiteur, « Quelle ambiance ? » restée en case à cocher, menu « Carte de base » disparaissant sans un mot. À valider — test-plan §19.13 cas 0.
Le schéma du RAG est en base
Image postgis + pgvector (0.8.6, base épinglée par digest), table ContentEmbedding avec index HNSW, 7 colonnes IA/stockage sur Resource, table VisitorQuestion. 62 migrations, dotnet test 124/124, données intactes. Deux surprises : la base locale avait une migration de retard — les colonnes Guide* du lot 1 n'existaient pas, toute requête sur Instances aurait planté ; et un warning de collation (glibc 2.36→2.31) qui désalignait les index texte, corrigé par REINDEX puis REFRESH COLLATION VERSION — dans cet ordre. D'où l'épinglage par digest.
Le guide lit enfin sa configuration
L'écran Guide IA ne pilote plus dans le vide. En ouvrant le fichier : quatre blocs de prompt et non deux — le relevé s'était arrêté au scope configuration. Les quatre lisent le nom, la personnalité et les messages de repli du client ; le ton imposé (« tu es chaleureux ») est retiré, la politique de repli divergente de la ligne 550 réconciliée. Deux prompts sur quatre n'avaient aucune règle hors-sujet — sur web et mobile, un visiteur pouvait demander une recette de tarte. Le tirage du repli est fait côté serveur pour que la phrase du client soit reprise mot pour mot. dotnet test 124/124, jamais vu tourner.
Écran Statistiques refondu, export PDF compris
Les 7 défauts corrigés, aucun changement backend : barres horizontales monochromes, filtres sur une seule ligne avec le volume de chaque canal, règle mono-canal, 4 KPI portant chacun leur variation, bandeau « à retenir », courbe en aire. La période précédente s'obtient en rappelant le même endpoint. Le rapport PDF se génère dans le navigateur (paquet pdf Dart déjà présent) et partage les valeurs calculées de l'écran — un chiffre ne peut pas diverger entre l'écran et le document envoyé à la commune. Le test de génération a attrapé deux plantages qui seraient sortis au premier clic. Au passage : VisitEvent porte bien l'AppType, le canal vocal est branché ; et deux puces du sommaire promettaient des données inexistantes (parcours terminés, questions au guide IA), retirées.
Les builds sont débloqués
3 apps Flutter + visitapp-web + dotnet test 124/124. Un seul fichier sans // @dart=2.18 cassait les trois apps Flutter d'un coup.
Bug backend révélé par les tests
BuildAuditEntries() modifiait le ChangeTracker pendant son énumération — exception sur toute écriture d'entité auditée. Invisible tant que les tests ne compilaient plus.
Assistant IA en web + alignement mobile
Débloque l'add-on IA sur le plan Essentiel, qui est web-only. Suggestions dérivées du contenu réel, zéro configuration client.
Refonte de la progression des parcours
9 booléens sur 3 niveaux remplacés par 3 questions. IsStepLocked supprimé : il rendait une étape définitivement infranchissable même après réussite.
Centrage de carte, horaires, icônes
M1, M2 et W2 corrigés — des champs que le client saisissait sans qu'ils aient le moindre effet.
Audit de cohérence de la doc
Le LLM annoncé était Claude à 4 endroits (c'est Gemini), l'infra email « inexistante » (Resend est en place), et deux docs se contredisaient sur l'état des builds.
CGU reprises et landing corrigée
Les CGU décrivaient des plans abandonnés (Starter/Standard/Premium 69/99/199 €). Alignées sur la landing, avec un quota IA défini en tokens renvoyant au back-office plutôt qu'un chiffre figé dans un contrat. Landing : « req/mois » → « questions de visiteurs / mois » en 4 langues.
Écran Guide IA — onglet Configuration livré
Migration additive (4 colonnes nullable), champs sur Instance, client généré étendu à la main, écran avec 35 clés i18n FR/EN/NL. dotnet build et flutter build web passent. Menu conditionné à isAssistant, le même drapeau que la garde d'AiController.
Trois plans écrits, deux écrans maquettés
Médias & stockage, visite hors ligne, écran Guide IA, écran Statistiques. Le découpage V1/V2 est tranché : le RAG sur contenu CMS part en V1, l'ingestion documentaire en V2. Les deux écrans maquettés sont désormais implémentés.