ResourcesScreen, il appartient au lot Médiathèque.diff --git a/STATUS.md b/STATUS.md index e406d0b..df9091c 100644 --- a/STATUS.md +++ b/STATUS.md @@ -120,7 +120,7 @@ Ordre conseillé : → Audits : **[security/audit-securite-manager-service.md](security/audit-securite-manager-service.md)** et **[security/audit-manager-app.md](security/audit-manager-app.md)** (2026-07-13/14) - **manager-service** — le point critique est la **rotation des secrets committés** (JWT signing key, clés API, connection strings, MQTT, Telegram). Gravité inchangée, mais **déplacée en fin de backlog le 2026-08-11** : les 6 repos sont privés sur un Gitea auto-hébergé. ⚠️ Contrainte à ne pas perdre — **la rotation doit précéder I3** (compose/Traefik prod), sinon la prod naît avec les clés de l'historique et il faut rotationner deux fois. Puis quatre niveaux Haute → Basse. -- **manager-app** — le critique (`manager_api_new` désynchronisé, 68 erreurs de bruit) est ✅ **résolu le 2026-08-11** : `flutter analyze` passe à 3 issues et redevient un feu vert utilisable. Au passage, les 3 déclencheurs de génération OpenAPI ont été supprimés, ce qui **verrouille la règle « `manager_api_new` s'édite à la main »**. Reste Haute → Basse : duplication du shell de dialog, `build()` monolithiques, i18n contournée, clé AES en dur. +- **manager-app** — le critique (`manager_api_new` désynchronisé, 68 erreurs de bruit) est ✅ **résolu le 2026-08-11** : `flutter analyze` passe à 3 issues et redevient un feu vert utilisable. Au passage, les 3 déclencheurs de génération OpenAPI ont été supprimés, ce qui **verrouille la règle « `manager_api_new` s'édite à la main »**. Reste Haute → Basse : duplication du shell de dialog, `build()` monolithiques, i18n contournée, clé AES en dur. → **La duplication du shell de dialog a désormais sa maquette et sa carte** : `DOCS/claude design/refonte-configuration-sections.html` §6 (coquille, inventaire des 29 dialogues, deux dialogues dessinés) et `kanban/cards/5-planifie/290-…`. ⚠️ **Ordre imposé** : les 8 `showNewOrUpdate*` sont *absorbés* par l'éditeur de collection du gabarit B — extraire un scaffold pour eux d'abord, ce serait refactoriser du code à supprimer. Voir la note dans `security/audit-manager-app.md`. → **Détail complet, par priorité et avec les cases à cocher : [status/securite-dette-technique.md](status/securite-dette-technique.md)** diff --git a/claude design/mediatheque-ajout-ressource.html b/claude design/mediatheque-ajout-ressource.html new file mode 100644 index 0000000..ff94a02 --- /dev/null +++ b/claude design/mediatheque-ajout-ressource.html @@ -0,0 +1,416 @@ + +
Le dialogue d'ajout est le dernier écran de la Médiathèque à ne pas avoir été repris. Il ne dénote pas seulement : deux de ses défauts sont des bugs de dimensionnement, et un troisième fait qu'on ne voit pas ce qu'on vient de choisir.
+ +Nouvelle ressource
+ + + +1 Rayon 20 — l'échelle de l'app va de 5 à 10.
+2 Le corps se dimensionne sur l'écran, pas sur le dialogue : 960 × 540 demandés dans 512 de large.
+4 Cinq fichiers choisis, et voilà tout ce qu'on en voit.
+5 « Local » / « En ligne » écrits en dur, en français.
+6 Deux boutons de même poids ; le primaire accepte le clic à vide.
+→ Le gain principal n'est pas l'habillage : c'est la liste. On voit ce qu'on a choisi, et on peut en retirer un.
+Numérotés comme sur la maquette de gauche. Les trois marqués bug ne relèvent pas du goût.
+ +| # | Ce qui cloche | Effet |
|---|---|---|
| 1 | +Rayon 20 sur le dialoguenew_resource_popup.dart:22 | +Hors de l'échelle de l'app (5 / 8 / 10). Exactement le défaut qu'on vient de retirer de la modale de détail. | +
| 2 | +bugLe corps se dimensionne sur MediaQueryupload_content_container.dart:33-35 |
+ width * 0.5 × height * 0.5 de l'écran : sur 1920 × 1080, 960 × 540 demandés dans un dialogue de 512 de large utile. Le contenu déborde et se fait rogner. |
+
| 3 | +bugLe champ URL aussiupload_online_resources_container.dart:107 — // TODO GET SIZE |
+ Même cause : width * 0.35 de l'écran. Le TODO est resté. |
+
| 4 | +bugOn ne voit pas ce qu'on a choisiupload_content_container.dart:57-58 | +Sur le web, après sélection : une carte titrée « Fichiers », sous-titrée « Aucun aperçu possible ». Cinq fichiers ou un seul, même affichage. Aucun moyen d'en retirer un — il faut tout recommencer. | +
| 5 | +Onglets écrits en durresource_tab.dart:30-31 | +« Local » et « En ligne » restent en français en anglais et en néerlandais — le trou qu'on vient de combler ailleurs dans la Médiathèque. | +
| 6 | +Deux boutons de même poids, et une erreur évitablenew_resource_popup.dart:60-92 | +Le primaire accepte le clic à vide, puis affiche une notification orange « aucun fichier chargé ». Désactiver le bouton supprime l'erreur au lieu de l'expliquer. | +
| — | +Type forcé à Image pour tout fichier localresource_tab.dart:93 et :97 |
+ Code mort et trompeur : create() re-déduit le type depuis l'extension. À lire, on croit que le MP3 sera enregistré comme image. |
+
| — | +bugfilesToSendWeb!.length sur une variable nullenew_resource_popup.dart:82 |
+ Branche non-web uniquement : cliquer « Créer » sans rien choisir plante. Invisible en production tant que l'app reste web. | +
new_resource_popup.dart — réécritupload_content_container.dart — réécrit (zone de dépôt + liste)resource_tab.dart — supprimé, il ne portait que la barre d'ongletsupload_online_resources_container.dart — supprimé, remplacé par le champ URL dépliantmedia*resource_tab.dart, upload_content_container.dart et upload_online_resources_container.dart sont supprimés ; le nouveau sélecteur vit dans Components/resource_picker.dart. Le dialogue de confirmation partagé (12 appelants) a été repris dans la foulée : « Oui »/« Non » en dur remplacés, et le bouton de confirmation passe en rouge quand l'action détruit.
+ v1-mediatheque-plan.md ne mentionne ce dialogue nulle part : le lot s'arrête à la grille, aux facettes et au panneau de détail. Ce chantier-ci est adjacent, pas inclus — à faire maintenant dans la foulée, ou à poser comme carte de suivi. C'est un arbitrage, pas une évidence.
+ Le blanc de l'écran actuel ne vient pas de la mise en page de configuration_detail_screen.dart : il vient de quatre composants partagés qui imposent des pastilles pleine largeur, des labels centrés et des listes horizontales à hauteur fixe. On les corrige une fois, et les 13 types de section en héritent.
constants.dart porte déjà un système complet — kSurface2, kInk2, kLine, kSpace1…kSpace8, kRadiusInput 5 / kRadiusCard 8 / kRadiusShell 10 / kRadiusPill, kTitleCard, kLabelField, kTextHint. confirmation_dialog.dart et resource_picker.dart l'appliquent déjà.
Ces deux écrans sont simplement antérieurs à ce système. Il n'y a donc pas de direction visuelle à trancher ici : toutes les valeurs des maquettes ci-dessous sont reprises de constants.dart, et là où une première version de cette page avait inventé un rayon ou une capitale, c'est le token qui a gagné.
Quatre causes, toutes dans des composants réutilisés par tous les écrans d'édition.
+11/50 se retrouve à un mètre du texte.Row
+ string_input_container.dart · resource_input_container.dartauto-fill minmax(146px, 1fr), hauteur pilotée par le contenu, réordonnancement conservé.C'est la brique qui décide de la densité de tous les écrans. Elle se corrige dans trois fichiers.
+Trois cartes empilées deviennent une colonne de travail et un rail latéral. Les actions quittent la barre flottante du bas pour rejoindre l'en-tête.
+Le placement bento ne concerne pas cet écran : gridColSpan / gridRowSpan vivent sur AppConfigurationLink, donc sur le lien entre une instance d'application et une configuration. Les tuiles de la grille bento sont les configurations de l'accueil, réglées dans app_configuration_link_screen.dart via CardShapeSelector.
À l'intérieur d'une configuration, les sections n'ont ni span ni grille : elles ne portent qu'un order, et le visiteur les voit en liste verticale de cartes pleine largeur (SectionList.tsx, SectionCard à 160 px, accent alterné). La grille de la maquette ci-dessous est donc un outil de réordonnancement, pas une prévisualisation.
Un aperçu ne se justifie que là où la décision d'édition est invisible dans le formulaire — les spans bento, le HTML traduit. Ici l'ordre se lit dans la grille, l'image est en vignette, les langues sont des pastilles : l'aperçu ne redessinerait, en plus petit, que ce qui est déjà à l'écran. En échange il faudrait maintenir un troisième moteur de rendu en Dart, alors que le bento est déjà dupliqué entre myinfomate_layout et le CSS Grid de visitapp-web — et un aperçu qui dérive du vrai rendu est pire que pas d'aperçu. Le rail droit porte donc un bouton d'ouverture et le QR : le rendu réel, sans rien à synchroniser.
Même squelette, même grille, même rail. Seul le contenu de la carte « Configuration <type> » change d'un type à l'autre.
+a17c9b…Réponse à ta question : non. Les 13 types se rangent dans trois formes, et c'est la forme qui se dessine.
+Les configs par type ne diffèrent que par leurs champs, pas par leur structure : slider_config, menu_config, quizz_config, article_config, map_config, agenda_config, event_config et parcours_config font tous rigoureusement la même chose — une liste réordonnable + un bouton vert flottant + une modale d'édition, chacun avec sa propre hauteur en MediaQuery.size * 0.x. Une seule brique CollectionEditor les remplace tous les huit.
Un à quatre champs dans la carte de type. Rien à dessiner de plus : la grille de 12 colonnes de l'écran suffit.
+Liste ordonnée d'éléments + édition en place. Maquetté ci-dessus avec le Quiz ; l'élément édité change, la mécanique non.
+Gabarit B, plus un canevas de travail à droite : la carte, la grille du puzzle, le plan d'événement. Le canevas prend la place du rail latéral, qui repasse au-dessus du canevas.
+| Type | Gabarit | Contenu de la carte de type | Dette actuelle |
|---|---|---|---|
| Web | A | URL | — |
| Météo | A | Ville | Champ marqué isUrl à tort |
| Vidéo | A | Source (YouTube / Vimeo / ressource) + URL | Déjà refait, sert de référence |
| B | Liste de fichiers traduits | Bloc figé 70 × 440 px centré | |
| Slider | B | Contenus ordonnés : image, titre, description | Liste horizontale height: 300 |
| Menu | B | Sous-sections ordonnées | Liste horizontale 0.35 × hauteur |
| Article | B | Contenus + audio + lecture auto | Liste horizontale height: 250 |
| Quiz | B | Questions, réponses, points, scores | Le plus dense : 4 zones à hauteur fixe |
| Agenda | B | Événements datés | Modale par événement |
| Parcours | B | Étapes, mode de progression | Éditeur dédié de 785 lignes à raccorder |
| Map | C | Points géo, catégories, images par point | 753 lignes, pas d'aperçu carte à l'édition |
| Événement | C | Blocs de programme, annotations sur plan | Trois modales imbriquées |
| Jeu | C | Image, messages, lignes × colonnes | TabBar dans un SizedBox(height: 650) |
29 fichiers ouvrent un dialogue. Il n'en faut pas 29 maquettes : dix disparaissent ou fusionnent, la coquille est déjà tranchée, et deux seulement portent une vraie décision de design.
+confirmation_dialog.dart l'a fixée, et resource_picker.dart la suit : rayon kRadiusShell, bordure kLine d'1 px, pied en bande kSurface2 séparé par un filet kLineSoft, un TextButton « Annuler » à 13 px et un FilledButton en pastille kRadiusPill qui passe au rouge quand l'action détruit. Les maquettes ci-dessous ne proposent rien de neuf sur ce plan : elles appliquent cette coquille.
C'est l'interface la plus utilisée de l'app : elle s'ouvre depuis chaque titre et chaque contenu de chaque type. Aujourd'hui elle empile un éditeur Quill par langue les uns sous les autres, puis pose deux boutons de 370 × 70 px centrés dans le flux du contenu — « Appliquer à toutes les langues » et « Traduire avec l'IA ». Sur trois langues, l'action IA est sous la ligne de flottaison.
+ +Aujourd'hui ça marche parce que l'empilement vertical monte toutes les langues en même temps : _controllers est une Map<String, QuillController> construite une seule fois dans initState (translation_input_container.dart:52) et libérée seulement dans dispose (198-202). Aucun éditeur n'est jamais démonté en cours de route.
Le rail de langues casse cette propriété si on l'implémente naïvement. N'afficher que la langue sélectionnée en reconstruisant l'éditeur à chaque bascule, c'est créer et détruire des QuillController en boucle — exactement le chemin fragile. La règle : construire la map une fois pour toutes les langues, comme aujourd'hui, et basculer avec un IndexedStack ou un Offstage — jamais en rebâtissant l'éditeur. Et surtout pas de TabBarView : il construit paresseusement et peut lâcher ses enfants hors écran sans AutomaticKeepAliveClientMixin.
Le code garde la trace d'une tentative dans ce sens : translation_tab.dart existe, en TabBar + TabBarView — mais son corps est un TextFormField, pas du Quill, et son appel est en commentaire dans multi_input_modal.dart ligne 46-51. Une version à onglets a été essayée puis abandonnée.
Deux chemins déjà existants libèrent et recréent les contrôleurs, et ce sont eux qu'il faut retester en priorité après la refonte : la traduction par IA (ligne 142) et « appliquer à toutes les langues » (ligne 183). Ils remplacent l'entrée de la map ; une référence gardée ailleurs deviendrait caduque.
+C'est la première interface que voit un nouveau client, et c'est aujourd'hui un menu déroulant de 13 entrées dans un SizedBox(height: 250), avec le champ du nom enfermé dans un height: 100. Un menu déroulant demande de connaître les noms avant de choisir ; une grille les montre.
getSectionIcon, déjà écrites — la grille ne coûte que la mise en page. Et l'avertissement qui compte (le type est définitif) est là où il sert, à côté du bouton qui valide.
+ | Dialogue | Sort | Pourquoi |
|---|---|---|
8 × showNewOrUpdate* | supprimé | Question de quiz, image de slider, point géo, catégorie, fichier PDF, événement d'agenda, bloc de programme, annotation de plan — tous absorbés par le panneau d'édition en place du gabarit B. |
| Traduction | maquetté | L'interface la plus utilisée de l'app. Ci-dessus. |
| Nouvelle section | maquetté | Première impression client. Ci-dessus. |
| Nouvelle configuration | aligné | Formulaire court : la coquille M et la grille de champs suffisent, rien à décider. |
| Confirmation | fait | Déjà repris, et c'est lui la référence. |
| Ajouter une ressource | fait | Maquette dédiée du 03/09, déjà publiée à part. |
| Sélecteur de ressource | cadre seul | Il embarque ResourcesScreen : son contenu appartient au lot Médiathèque (v1-mediatheque-plan.md §3.3, « inchangé si possible »). Seul son cadre est à reprendre — voir ci-dessous. |
| Traduction sans HTML multi_input_modal.dart | fusionné | Le jumeau du modal de traduction. multi_string_input_container appelle l'un ou l'autre selon isHTML : même travail, deux implémentations (560 px de large, une zone de 500 × 200, des boutons de 180 px). La maquette ci-dessus couvre les deux — une fois fusionnés en un seul, la barre de format en moins quand le champ n'accepte pas de HTML. |
| Point géo, géométrie geoloc_ / geometry_input_container.dart | absorbé | Deux AlertDialog de 0,75 × largeur pour poser un point ou tracer une zone : c'est précisément ce que le canevas du gabarit C met à plat dans l'écran. Placer un point sur une carte enfermée dans une modale est le geste le plus contraint qu'on puisse offrir. |
| Fichiers PDF, catégories de carte, sélecteur de couleur | aligné | Trois corps de dialogue honnêtes dans trois cadres cassés (0,65 × 0,7 d'écran, boutons de 180 × 70). Ils héritent de la coquille sans rien décider. Au passage : pdf_file_input_container et category_input_container sont un copier-coller l'un de l'autre — mêmes dimensions, même structure — sauf que le second a ses libellés en dur (« Annuler », « Valider » au lieu des traductions) et un onGetResult(result) commenté. À vérifier : une catégorie modifiée ne remonte peut-être pas à la volée. |
| Éditeur de parcours guided_path_editor.dart | à part | 785 lignes avec ses propres dialogues. Il a déjà sa maquette (sectionparcours-refonte-flux.html) : à raccorder, pas à redessiner. |
| Lien d'application, device kiosk, utilisateurs, clés API, audit, guide IA, menu principal, notifications | aligné | Hors périmètre de cette refonte, mais héritent de la coquille et des boutons dès qu'on y touche. |
showSelectResourceModal encastre l'écran Médiathèque entier dans un AlertDialog. Son contenu a déjà deux sources de vérité — v1-mediatheque-plan.md §3.3 et la maquette « Ajouter une ressource » — et je n'en ajoute pas une troisième. En revanche son cadre a un vrai défaut, pas seulement un défaut de goût.
Le débordement. Un AlertDialog s'insère avec 24 px de marge verticale : il dispose de hauteur − 48. Le contenu en demande 0,85 × hauteur, plus un titre, plus une ligne d'actions de 70 px — soit environ 895 px demandés pour 852 disponibles sur un écran de 900 de haut. C'est la raison d'être du SingleChildScrollView qui l'entoure, et la conséquence est un défilement dans un défilement : on fait glisser le dialogue au lieu de la grille de médias.
Ce qui change, et rien de plus. Coquille standard avec en-tête à la place du title: Center(Text(…)), kRadiusShell au lieu du rayon 20, le bouton « Annuler » de 180 × 70 px descendu dans le pied — il est seul, dans un Row en spaceEvenly, puisque la sélection se fait au clic sur la vignette. La hauteur figée cède la place à un corps souple sous un maxHeight : plus de débordement, plus de double défilement. Et showValues, lignes 64-78, est du code mort — comme la copie qu'en garde multi_input_modal.dart ligne 186 : son unique appel, ligne 53, est en commentaire. Les deux peuvent partir.
Ce qui ne change pas, volontairement : la largeur. Le size.width * 0.85 est conservé tel quel. La règle du rail de facettes, en §3.3 du plan Médiathèque, est écrite sur cette hypothèse — « elle fait déjà size.width * 0.85, et un rail de 194 px y est acceptable ». Rétrécir la modale invaliderait une décision déjà codée dans un autre lot. On corrige donc la hauteur et la coquille, pas la largeur.
Les trois premiers fichiers portent l'essentiel du gain visuel — ils sont importés par tous les écrans d'édition, y compris ceux qui ne sont pas dans cette proposition.
+width: double.infinity et le radius 29. Bordure 1 px, radius 8, marges rendues au parent. Tous les écrans changent d'un coup — à vérifier au passage sur Ressources et Instances.MainAxisAlignment.center et des tailles en dur (width: 200, height: 54, la case à cocher de 50 px). Le composant ne décide plus de sa largeur ni de sa position._buildFooter. Le _cardQR devient le panneau « Diffusion » du rail droit.showNewOrUpdate*. C'est le plus gros morceau, et le seul qui puisse s'étaler dans le temps : chaque type migré est indépendant.Aucune donnée, aucune migration, aucun contrat d'API n'est touché, et tout est réversible par git. Le risque n'est pas la gravité, c'est la portée : les lots 1 et 2 changent des composants importés par une trentaine d'écrans, et manager-app n'a que trois tests, aucun sur ces composants. La vérification est donc manuelle.
Deux pièges concrets. D'abord, retirer une pastille pleine largeur change la hauteur des lignes — et il reste des conteneurs à hauteur figée autour (height: 70, height: 100, SizedBox(height: 250)) : ce qui rentrait juste peut déborder, et un débordement Flutter est visible, pas silencieux. Ensuite, ces composants ne sont pas purement décoratifs : dans section_detail_screen.dart, MultiStringInputContainer.onGetResult déclenche un save(true, …) à la ligne 270, et la case iBeacon un save(false, …) à la ligne 381. Il y a un chemin d'écriture câblé dans le composant — à ne pas perdre ni dédoubler en le refactorisant.
Tout le reste de la refonte est codé. Il manque une seule chose, et c'est la même dans les deux écrans : poser un point ou une zone sur la carte de l'écran, au lieu d'ouvrir une carte dans une modale ouverte depuis une modale.
+C'est le geste le plus contraint que l'app propose aujourd'hui, et c'est aussi le plus fréquent sur une section Map.
+0,88 × largeur · neuf champs traduits, une image, une catégorie, une liste de contenus.On place un point en voyant les autres — impossible aujourd'hui, alors que c'est la seule chose qui compte quand on répartit des points d'intérêt sur un plan. Et la fiche reste lisible pendant qu'on dessine, donc on sait quel point on est en train de déplacer.
+Liste, fiche, canevas. C'est le gabarit B auquel on ajoute une troisième colonne — et le rail droit de l'écran (Habillage, Diffusion) repasse au-dessus, comme annoncé.
+MapGeometryPicker, déplacée telle quelle : point, ligne, zone, couleur. Le pied de carte dit ce qu'on modifie et où, pour qu'aucun clic ne soit ambigu.
+ Un point porte titre, description, site, prix, téléphone, e-mail, horaires, image et catégorie. Les quatre premiers d'un musée sont presque toujours remplis, les cinq autres presque jamais. La fiche montre donc titre, description, catégorie, image, et replie les informations pratiques derrière une ligne qui dit combien sont remplies. Rien n'est retiré, seulement rangé par fréquence d'usage réelle.
+Tentant, et je pense que ce serait une erreur en permanence : un point porte neuf champs traduits, une image, une catégorie et une liste de contenus ; la carte n'en porte qu'un seul, la géométrie. Donner en permanence le plus de place au champ qu'on modifie le moins ferait de la saisie — le vrai travail de cet écran — le parent pauvre. Le back-office est un outil de saisie ; la belle carte, le visiteur l'a déjà dans l'app.
+En revanche, 300 px ne suffisent pas pour juger d'une répartition de points. Le canevas passe donc à 420 px de haut par défaut, et gagne un bouton « agrandir » qui lui donne toute la largeur : la liste reste en rail étroit, la fiche se replie. Deux moments distincts, deux dispositions — on écrit une fiche, ou on répartit des points, rarement les deux dans la même minute.
+Le canevas sait déjà afficher une carte et recevoir un clic. Le point central se règle donc par un mode du canevas, et geoloc_input_container — repris à la coquille standard cette semaine — n'a plus d'appelant sur cet écran. Il reste utile ailleurs tant que d'autres écrans le référencent.
Un événement pose des annotations sur le plan du site plutôt que des points d'intérêt sur une carte. La mécanique ne change pas, le fond de plan si.
+Le canevas d'Événement affiche la carte de base choisie dans baseSectionMapId, et les annotations globales se posent dessus, exactement comme les points. Trois différences, et elles sont toutes dans le contenu, pas dans la forme :
| Ce qu'on pose | Gabarit | Pourquoi |
|---|---|---|
| Annotations du plan | C — canevas | Elles ont une position : elles se posent sur le plan, comme les points de Map. Même liste, même fiche, même canevas. |
| Blocs de programme | B — liste | Ils ont une heure de début et de fin, pas une position. Une liste ordonnée suffit — et c'est déjà ce qu'ils sont. |
| Parcours de l'événement | déjà fait | L'éditeur à fenêtre unique est raccordé et reste tel quel. |
Quand aucune carte de base n'est choisie (baseSectionMapId vaut null), le canevas n'a pas de fond. Deux réponses possibles : afficher une carte vide comme sur Map, ou afficher un message qui invite à choisir un plan d'abord. Je propose la seconde — poser des annotations sur un fond qui va changer, c'est du travail à refaire — mais c'est un choix produit, pas technique.
Le canevas n'ajoute pas une brique : il en absorbe quatre.
+| Fichier | Sort | Détail |
|---|---|---|
showNewOrUpdateGeoPoint.dart362 lignes | supprimé | Absorbé par la fiche du gabarit C. Ses champs ne changent pas, ils changent de contenant. |
geometry_input_container.dart | supprimé | Le champ qui ouvrait la carte de dessin n'a plus lieu d'être quand la carte est à l'écran. ⚠️ Deux autres appelants à traiter d'abord : etape_fields et showNewOrUpdateEventAgenda. |
map_geometry_picker.dart | déplacé | Le dessin existe déjà et fonctionne. Il sort de son dialogue pour devenir le canevas. C'est le cœur du lot, et c'est du déplacement, pas de l'écriture. |
showNewOrUpdateMapAnnotation.dart182 lignes | supprimé | Même chose côté Événement. |
geoloc_input_container.dart | conservé | Plus d'appelant sur Map, mais il reste référencé ailleurs. On le garde tant qu'il sert. |
Les points d'un Map vivent côté serveur : l'éditeur de collection sait déjà leur parler depuis cette semaine.
+MapCanvas est sorti du dialogue : il reçoit une géométrie, la rend modifiable, rend la main à chaque changement, et affiche les autres tracés en fond. MapGeometryPicker n'est plus qu'une coquille autour de lui, pour ses deux appelants restants.showNewOrUpdateGeoPoint, avec le repli des informations pratiques, puis supprimer la modale.GeometryInputContainer.Aucun DTO, aucun endpoint, aucune migration. GeoPointDTO garde ses neuf champs traduits, sa géométrie et sa couleur ; MapDTO garde ses catégories. Le risque est dans le comportement de la carte, pas dans les données — et il est réversible par git.
Le visiteur ne sait jamais combien il télécharge. Le dialogue de configurations_list.dart annonce « il est nécessaire de télécharger du contenu supplémentaire » sans chiffre, et l'écran de progression n'affiche qu'un pourcentage. Or une visite du Fourneau, c'est un ordre de grandeur de plusieurs centaines de Mo sur la 4G du visiteur.
Google Play exige que la taille des téléchargements complémentaires soit annoncée avant de les lancer — c'est une condition de publication, pas un confort.
+Rien à ajouter côté serveur : ResourceDTO.sizeBytes existe déjà et arrive dans ExportConfigurationDTO.resources. La somme se calcule côté client, et sur les seules ressources réellement à télécharger — isResourceOutdated filtre déjà celles qui sont à jour, donc une mise à jour de visite doit annoncer le delta, pas le total.
Deux endroits : le dialogue de confirmation (avant), et l'écran de progression (« 128 / 412 Mo » plutôt qu'un pourcentage nu). ⚠️ La taille annoncée dans le dialogue suppose d'avoir déjà appelé configurationExport pour connaître la liste — aujourd'hui cet appel n'a lieu qu'après la confirmation.
Aujourd'hui aucune carte ne fonctionne hors ligne, et ce n'est pas une question de données : MapDTO porte déjà points, centerLatitude, zoom et iconResourceId, et les icônes sont téléchargées avec la visite. C'est le fond qui manque — les tuiles sont chargées en réseau, sans cache.
Le GPS, lui, n'est pas le problème : geolocator ^13.0.0 lit le GNSS, qui est autonome. Seule réserve à annoncer au client : sans A-GPS, le premier point peut demander 30 à 60 s sous couvert forestier.
Le chemin court existe déjà dans le pubspec : mapbox_maps_flutter ^2.0.0 expose OfflineManager (style packs) et TileStore (tile regions) — on télécharge une emprise bornée au moment du téléchargement de la visite. Pour un domaine comme le Fourneau Saint-Michel, quelques dizaines de Mo. MapProvider.MapBox existe déjà côté modèle.
⛔ Google est une impasse, et pas seulement techniquement. Le SDK Maps pour Android n'expose aucune API de tuiles hors ligne — les « zones hors connexion » sont une fonction de l'app Google Maps, pas du SDK intégrable. Et le code n'utilise même pas ce SDK pour les sections carte : il passe par flutter_map sur https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}, un endpoint non documenté dont la mise en cache est explicitement interdite. Conclusion : Mapbox devient le fournisseur du mode hors ligne, Google reste en ligne seulement.
⚠️ Tant que ce chantier n'est pas fait, SectionType.Map reste volontairement hors de offlineCapableSectionTypes (downloadConfiguration.dart) : l'ajouter donnerait une carte grise, ce qui est pire que ne pas la proposer. Une fois les tuiles packagées, c'est une ligne à ajouter dans cette constante.
Un musée de plein air ne distribue pas un fond OpenStreetMap, il distribue son plan dessiné. Aujourd'hui MapProvider ne connaît que Google et MapBox : il n'existe aucun moyen de téléverser une image de plan et d'y placer les points.
Ce qu'il faut : une troisième valeur de MapProvider, une ressource image portée par la section, et un calage — deux points d'ancrage suffisent (coin haut-gauche / bas-droit en coordonnées réelles) pour projeter un GeoPoint sur l'image et y afficher la position du visiteur.
Pourquoi c'est le meilleur des trois : aucune tuile, donc hors ligne par construction — l'image part avec la visite comme n'importe quelle ressource, et le mécanisme de téléchargement n'a rien de nouveau à apprendre. Le rendu est aussi plus lisible qu'un fond routier sur un site de plusieurs dizaines de bâtiments.
+Périmètre réel : c'est le plus gros des trois chemins carto — backend (modèle + migration), manager-app (téléversement du plan et pose des ancres), mymuseum-visitapp et visitapp-web (rendu). À faire après Carte hors ligne — tile packs Mapbox, qui débloque le besoin immédiat avec un fond classique.
Lots 1 à 5 codés le 04/09 (flutter analyze sans erreur, flutter build web passe ; rien de vérifié à l'écran). Pilote select_resource_modal : coquille standard, corps souple, plus de double défilement, showValues mort supprimé, largeur 0,85 conservée. Lot 1 : text_field_container sans width: double.infinity ni rayon 29, compteur discret dans rounded_input_field. Lot 2 : libellé au-dessus et plus d'auto-centrage dans string_/multi_string_/resource_/check_input_container. Lot 3 : grille responsive de sections, réordonnancement par Draggable/DragTarget (ReorderableListView ne fait pas de grille, et pas de dépendance nouvelle), tuile « Ajouter » à la place du bouton vert. Lot 4 : les deux écrans de détail en EditorHeader + EditorColumns + Pane (nouveau Components/pane.dart), _buildFooter supprimé, le QR devient le rail « Diffusion ». Lot 5 : grille de types dans new_section_popup, modal de traduction en rail de langues + IndexedStack, et multi_input_modal réduit à une redirection isHTML: false. 24 clés i18n ajoutées en FR/EN/NL.
🐛 Le bug des catégories est confirmé et corrigé : showCreateOrUpdateCategories recevait un newValues vide de l'appelant et le renvoyait tel quel — valider sans rien toucher effaçait les catégories. Le tampon part maintenant des catégories existantes, et les libellés en dur sont passés par AppLocalizations.
Lot 6 entamé : Components/collection_editor.dart existe — liste ordonnée à gauche (réordonnancement par poignée), élément édité en place à droite, ligne « Ajouter » en fin de liste, suppression dans l'en-tête du détail. Trois types migrés : Slider et Article (via un ContentFields partagé, extrait de showNewOrUpdateContentSlider) et PDF (le bloc figé 70 × 440 disparaît). Les filtres d'envoi au backend sont conservés à l'identique (contenus sans ressource exclus, champs d'article recopiés).
⚠️ Trois fichiers sont devenus du code mort mais ne sont pas supprimés : pdf_file_input_container.dart, pdf_list.dart, new_update_pdfFile.dart. Ils portent des modifications non committées d'un autre lot — les effacer perdrait ce travail. À supprimer une fois l'arbre propre.
Lot 6 terminé pour le gabarit B. Décision prise le 04/09 (artifact 4af75171) : CollectionEditor reçoit un mode distant (RemoteCollection : create / delete / reorder asynchrones, voile de chargement, retour à l'état d'avant si l'appel échoue). Quiz l'utilise : la modale de question disparaît, les messages de score remontent en tête de carte, les réponses gardent QuizzResponseList tel quel. Menu garde la navigation vers sub_section_edit_screen — une sous-section est une section complète — et prend la grille de tuiles, extraite en SectionGrid et partagée avec les sections d'une configuration. Parcours : liste qui suit son contenu, les deux SizedBox(height: 500) de event_config et section_parcours_config retirés, éditeur à fenêtre unique inchangé.
🐛 Le rang en double est corrigé dans Quiz et Menu : ils n'écrivaient que l'order de l'élément déplacé, les autres gardaient le leur. La liste entière est renumérotée et envoyée en un Future.wait, comme le faisait déjà Parcours.
Agenda n'a pas été migré, et c'est délibéré : ses événements sont groupés par langue (_displayGroups, pastilles de langue quand un groupe en compte plusieurs) et n'ont pas d'ordre — une liste ordonnée à édition en place perdrait le groupement. Il reçoit ce qui lui revient : hauteur pilotée par le contenu (le height: 600 saute), lignes au système de tokens, modale conservée.
Gabarit C — Jeu est fait, Map et Événement sont dégrossis. game_config devient champs à gauche + canevas à droite : le canevas montre le découpage réel de l'image en rows × cols, seule décision que le formulaire ne montrait pas. Au passage, son TabBar n'en était pas un — les deux onglets construisaient le même formulaire et ne servaient qu'à écrire gameType : c'est devenu un choix à deux valeurs, et le SizedBox(height: 650) a disparu. geoloc_input_container passe à la coquille standard et affiche les coordonnées retenues au lieu d'un bouton « Changer la localisation », la carte prenant la hauteur disponible. Côté map_config : en-tête en grille de tokens, height: 700 et height: 70 retirés. Côté event_config : height: 600 retiré, la liste des blocs suit son contenu.
Reste : le canevas de Map et d'Événement — et il est maintenant maquetté : DOCS/claude design/refonte-gabarit-canevas.html (diffusion : artifact caf400b5). Liste des points, fiche, carte côte à côte ; les points non sélectionnés restent visibles en gris ; MapGeometryPicker sort de son dialogue pour devenir le canevas (déplacement, pas écriture) ; les informations pratiques d'un point (5 champs sur 9) sont repliées derrière un compteur. Absorbe showNewOrUpdateGeoPoint (362 l.), geometry_input_container et showNewOrUpdateMapAnnotation (182 l.). Une question produit reste ouverte : que montre le canevas d'Événement quand baseSectionMapId est nul — carte vide, ou invitation à choisir un plan d'abord (ma proposition).
Étape 1 du canevas faite le 04/09 : Components/map_canvas.dart — le dessin est sorti du dialogue. Il reçoit une géométrie, la rend modifiable, rend la main à chaque changement (pas de bouton « Enregistrer » : c'est l'écran qui enregistre), et affiche les autres tracés en fond via MapCanvasGhost. Barre d'outils compacte sur la carte, pied qui donne les coordonnées et ce qu'on modifie, bouton « agrandir » optionnel. MapGeometryPicker n'est plus qu'une coquille autour de lui, donc etape_fields et showNewOrUpdateEventAgenda ne bougent pas. Au passage, ses libellés « Ligne » / « Polygone » en dur sont passés par AppLocalizations. Taille tranchée : 420 px par défaut + mode agrandi, pas une interface centrée carte — un point porte neuf champs traduits, la carte n'en porte qu'un.
Étapes 2 à 4 faites : le gabarit C est en place dans Map. Map/geo_point_editor.dart — liste des points à gauche, fiche au centre, carte à droite ; les points non sélectionnés apparaissent en fond ; le mode agrandi replie la fiche et garde le rail. Les neuf champs traduits sont rangés par fréquence : titre, catégorie, description et image visibles, les cinq informations pratiques repliées derrière un compteur. map_config passe de 937 à ~510 lignes : la modale showNewOrUpdateGeoPoint n'est plus appelée, getElement et son boxDecoration sont supprimés, et recherche + catégories filtrent désormais la liste et la carte par un seul calcul. Les points sont créés, modifiés et supprimés directement par l'API (sectionMapCreate / Update / Delete), la suppression passant par la confirmation destructive.
Étape 5 faite : le canevas d'Événement. Les annotations passent dans l'éditeur de collection en mode distant, avec le canevas dans le panneau d'édition. Plan par défaut retenu (décision Thomas) : le premier plan disponible est pris quand baseSectionMapId est nul, plutôt que de bloquer. 🐛 Trouvé en chemin : MapAnnotationDTO portait déjà un champ geometry que l'ancienne modale ne remplissait jamais — elle ne réglait que geometryType. Une annotation déclarait donc une forme sans jamais recevoir de coordonnées ; le canevas les lui donne. Enfin showMultiStringInputAndResourceHTML est passé à la coquille standard : plus aucun dialogue de ce chantier sur l'ancienne.
Plan de test : DOCS/test-plan-refonte-config-section.md, 14 sections. Rien n'a été vérifié à l'écran — flutter analyze propre et flutter build web qui passe, c'est tout.
Reste : supprimer le code mort une fois l'arbre git propre — showNewOrUpdateGeoPoint, showNewOrUpdateMapAnnotation, pdf_file_input_container, pdf_list, new_update_pdfFile, listView_card_subSection, new_update_question_quizz ; et geometry_input_container quand etape_fields et showNewOrUpdateEventAgenda auront leur canevas.
showMultiStringInputAndResourceHTML sur l'ancienne coquille, et le code mort à supprimer une fois l'arbre propre : pdf_file_input_container, pdf_list, new_update_pdfFile, listView_card_subSection, new_update_question_quizz.height: 70, 100) dans les écrans non repris — showNewOrUpdateGeoPoint, showNewOrUpdateEventAgenda, sub_section_edit_screen, new_configuration_popup.
+
+ Maquette prête et complète : DOCS/claude design/refonte-configuration-sections.html (diffusion : artifact 4d0c2ad6). Elle contient le diagnostic, les deux écrans, les 3 gabarits qui couvrent les 13 types de section, la coquille de dialogue, et deux dialogues dessinés — traduction et choix du type. Rien à décider côté design : tout est tranché.
Le blanc ne vient pas des écrans mais de 4 composants partagés. text_field_container.dart impose width: double.infinity + rayon 29 ; string_input_container.dart met le label à gauche dans un Row ; multi_string_input_container et resource_input_container se centrent eux-mêmes ; section_reorderList.dart réserve 0,3 × hauteur pour une liste horizontale de cartes 150×150. On les corrige une fois, les 13 types en héritent.
⚠️ Aucune direction visuelle à inventer : constants.dart porte déjà le système (kSpace1..8, kRadiusInput 5 / Card 8 / Shell 10 / Pill, kTitleCard, kLabelField, kTextHint) et confirmation_dialog.dart + resource_picker.dart l'appliquent déjà. Ces deux écrans sont simplement antérieurs au système.
Premier pas conseillé : select_resource_modal.dart, pas les composants partagés. Un fichier, ~15 lignes, zéro ligne dans ResourcesScreen, et ça corrige un vrai bug — AlertDialog dispose de hauteur − 48 mais le contenu en demande 0,85 × hauteur plus titre plus actions, d'où un défilement dans un défilement. Ça sert de pilote avant de toucher aux composants qui irriguent 30 écrans. ⛔ Ne pas toucher à la largeur 0,85 : la règle du rail de facettes de v1-mediatheque-plan.md §3.3 est écrite sur cette hypothèse et déjà codée.
⚠️ Deux pièges qui ne sont pas du visuel. D'abord, manager-app n'a que 3 tests, aucun sur ces composants : vérification manuelle, et il reste des conteneurs à hauteur figée (height: 70, 100, SizedBox(height: 250)) autour — ce qui rentrait juste débordera. Ensuite un chemin d'écriture est câblé dans les composants : section_detail_screen.dart:270 déclenche un save(true, …) depuis MultiStringInputContainer.onGetResult, et la ligne 381 un save(false, …) depuis la case iBeacon. À ne pas perdre ni dédoubler.
⚠️ Le piège Quill (remonté de mémoire par Thomas, confirmé dans le code). Ça marche aujourd'hui parce que l'empilement vertical monte toutes les langues : _controllers est bâtie une fois dans initState (translation_input_container.dart:52) et libérée seulement dans dispose. Le rail de langues de la maquette casse cette propriété s'il reconstruit l'éditeur à chaque bascule. Règle : map construite une fois pour toutes les langues, bascule par IndexedStack/Offstage, jamais de TabBarView (construction paresseuse, enfants lâchés hors écran sans AutomaticKeepAliveClientMixin). Trace d'une tentative abandonnée : translation_tab.dart existe en TabBar, son appel est en commentaire dans multi_input_modal.dart:46-51. À retester en priorité : la traduction IA (ligne 142) et « appliquer à toutes les langues » (ligne 183), les deux chemins qui libèrent et recréent les contrôleurs.
Deux fusions et un ménage décidés dans la maquette : multi_input_modal.dart est le jumeau sans HTML du modal de traduction (même travail, deux implémentations) → une coquille, deux corps, Quill si isHTML et champ simple sinon ; les 8 showNewOrUpdate* et les 2 sélecteurs géo disparaissent dans les gabarits B et C ; showValues est mort en double (select_resource_modal.dart:64 et multi_input_modal.dart:186, appel commenté ligne 53).
⚠️ Conflit résolu avec l'audit — security/audit-manager-app.md recommandait d'extraire un FormDialogScaffold pour les 15 occurrences des showNewOrUpdate*. Or la refonte les supprime : les extraire d'abord, ce serait refactoriser du code à jeter. Ordre imposé : absorber d'abord (gabarits B et C), extraire ensuite sur ce qui survit — traduction, nouvelle section, nouvelle configuration, sélecteur de couleur, fichiers PDF, catégories. L'audit et STATUS.md ont été annotés en ce sens le 2026-09-04.
🐛 Trouvé en passant, à vérifier : pdf_file_input_container.dart et category_input_container.dart sont un copier-coller — mêmes dimensions, même structure — mais le second a ses libellés en dur (« Annuler », « Valider ») et son onGetResult(result) commenté. Une catégorie de carte modifiée ne remonte peut-être pas à la volée. Lecture de code, pas testé.
Une photo d'un bâtiment du Fourneau → sa version d'époque → un personnage en boucle de 8 s. Le but n'est pas de produire un asset, c'est de répondre à la seule question dont dépend tout le reste : est-ce que ça a l'air crédible, ou est-ce que ça décroche ? En patrimoine la crédibilité est la valeur — si le rendu est inquiétant, la fonctionnalité n'existe pas, quels que soient le prix et la facilité.
+Le principe à valider : le text-to-video pur est écarté (il invente la géométrie du lieu). Le lieu réel reste la base, l'IA n'ajoute que le mouvement — c'est « Point de départ » du Studio transposé à la vidéo, donc aucun mécanisme nouveau en base : Kind = Video et sourceFidelity existent déjà, c'est un gabarit de plus.
Respecter les contraintes pendant le test, sinon il ne prouve rien : 6-10 s en boucle, une seule personne, mouvements simples, caméra calme, plan large ou moyen, jamais de gros plan sur les mains ou le visage.
+⚠️ Deux choses déjà tranchées, à ne pas rejouer. Le motion transfer part en prestation Unov, pas dans le produit : il demande un tournage et un consentement écrit, ce qui échoue par construction le critère du §0 (« un conservateur produit sans intervention »). Et le cadrage de vente est resserré à « là où il ne reste rien » — bâtiment disparu, métier éteint, site réduit à ses fondations : partout où des archives existent, une vraie photo d'époque bat une boucle générée.
+⛔ Ne bloque rien et n'est bloqué par rien — c'est justement l'intérêt de le faire tôt. Mais la fonctionnalité, elle, reste derrière le prérequis dur du Studio : IResourceBlobService n'expose toujours que IsConfigured et DeleteAsync, le backend ne sait pas écrire dans le bucket.
Le visiteur ne sait jamais combien il télécharge. Le dialogue de configurations_list.dart annonce « il est nécessaire de télécharger du contenu supplémentaire » sans chiffre, et l'écran de progression n'affiche qu'un pourcentage. Or une visite du Fourneau, c'est un ordre de grandeur de plusieurs centaines de Mo sur la 4G du visiteur.
Google Play exige que la taille des téléchargements complémentaires soit annoncée avant de les lancer — c'est une condition de publication, pas un confort.
+Rien à ajouter côté serveur : ResourceDTO.sizeBytes existe déjà et arrive dans ExportConfigurationDTO.resources. La somme se calcule côté client, et sur les seules ressources réellement à télécharger — isResourceOutdated filtre déjà celles qui sont à jour, donc une mise à jour de visite doit annoncer le delta, pas le total.
Deux endroits : le dialogue de confirmation (avant), et l'écran de progression (« 128 / 412 Mo » plutôt qu'un pourcentage nu). ⚠️ La taille annoncée dans le dialogue suppose d'avoir déjà appelé configurationExport pour connaître la liste — aujourd'hui cet appel n'a lieu qu'après la confirmation.
Aujourd'hui aucune carte ne fonctionne hors ligne, et ce n'est pas une question de données : MapDTO porte déjà points, centerLatitude, zoom et iconResourceId, et les icônes sont téléchargées avec la visite. C'est le fond qui manque — les tuiles sont chargées en réseau, sans cache.
Le GPS, lui, n'est pas le problème : geolocator ^13.0.0 lit le GNSS, qui est autonome. Seule réserve à annoncer au client : sans A-GPS, le premier point peut demander 30 à 60 s sous couvert forestier.
Le chemin court existe déjà dans le pubspec : mapbox_maps_flutter ^2.0.0 expose OfflineManager (style packs) et TileStore (tile regions) — on télécharge une emprise bornée au moment du téléchargement de la visite. Pour un domaine comme le Fourneau Saint-Michel, quelques dizaines de Mo. MapProvider.MapBox existe déjà côté modèle.
⛔ Google est une impasse, et pas seulement techniquement. Le SDK Maps pour Android n'expose aucune API de tuiles hors ligne — les « zones hors connexion » sont une fonction de l'app Google Maps, pas du SDK intégrable. Et le code n'utilise même pas ce SDK pour les sections carte : il passe par flutter_map sur https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}, un endpoint non documenté dont la mise en cache est explicitement interdite. Conclusion : Mapbox devient le fournisseur du mode hors ligne, Google reste en ligne seulement.
⚠️ Tant que ce chantier n'est pas fait, SectionType.Map reste volontairement hors de offlineCapableSectionTypes (downloadConfiguration.dart) : l'ajouter donnerait une carte grise, ce qui est pire que ne pas la proposer. Une fois les tuiles packagées, c'est une ligne à ajouter dans cette constante.
Un musée de plein air ne distribue pas un fond OpenStreetMap, il distribue son plan dessiné. Aujourd'hui MapProvider ne connaît que Google et MapBox : il n'existe aucun moyen de téléverser une image de plan et d'y placer les points.
Ce qu'il faut : une troisième valeur de MapProvider, une ressource image portée par la section, et un calage — deux points d'ancrage suffisent (coin haut-gauche / bas-droit en coordonnées réelles) pour projeter un GeoPoint sur l'image et y afficher la position du visiteur.
Pourquoi c'est le meilleur des trois : aucune tuile, donc hors ligne par construction — l'image part avec la visite comme n'importe quelle ressource, et le mécanisme de téléchargement n'a rien de nouveau à apprendre. Le rendu est aussi plus lisible qu'un fond routier sur un site de plusieurs dizaines de bâtiments.
+Périmètre réel : c'est le plus gros des trois chemins carto — backend (modèle + migration), manager-app (téléversement du plan et pose des ancres), mymuseum-visitapp et visitapp-web (rendu). À faire après Carte hors ligne — tile packs Mapbox, qui débloque le besoin immédiat avec un fond classique.
Lots 1 à 5 codés le 04/09 (flutter analyze sans erreur, flutter build web passe ; rien de vérifié à l'écran). Pilote select_resource_modal : coquille standard, corps souple, plus de double défilement, showValues mort supprimé, largeur 0,85 conservée. Lot 1 : text_field_container sans width: double.infinity ni rayon 29, compteur discret dans rounded_input_field. Lot 2 : libellé au-dessus et plus d'auto-centrage dans string_/multi_string_/resource_/check_input_container. Lot 3 : grille responsive de sections, réordonnancement par Draggable/DragTarget (ReorderableListView ne fait pas de grille, et pas de dépendance nouvelle), tuile « Ajouter » à la place du bouton vert. Lot 4 : les deux écrans de détail en EditorHeader + EditorColumns + Pane (nouveau Components/pane.dart), _buildFooter supprimé, le QR devient le rail « Diffusion ». Lot 5 : grille de types dans new_section_popup, modal de traduction en rail de langues + IndexedStack, et multi_input_modal réduit à une redirection isHTML: false. 24 clés i18n ajoutées en FR/EN/NL.
🐛 Le bug des catégories est confirmé et corrigé : showCreateOrUpdateCategories recevait un newValues vide de l'appelant et le renvoyait tel quel — valider sans rien toucher effaçait les catégories. Le tampon part maintenant des catégories existantes, et les libellés en dur sont passés par AppLocalizations.
Lot 6 entamé : Components/collection_editor.dart existe — liste ordonnée à gauche (réordonnancement par poignée), élément édité en place à droite, ligne « Ajouter » en fin de liste, suppression dans l'en-tête du détail. Trois types migrés : Slider et Article (via un ContentFields partagé, extrait de showNewOrUpdateContentSlider) et PDF (le bloc figé 70 × 440 disparaît). Les filtres d'envoi au backend sont conservés à l'identique (contenus sans ressource exclus, champs d'article recopiés).
⚠️ Trois fichiers sont devenus du code mort mais ne sont pas supprimés : pdf_file_input_container.dart, pdf_list.dart, new_update_pdfFile.dart. Ils portent des modifications non committées d'un autre lot — les effacer perdrait ce travail. À supprimer une fois l'arbre propre.
Lot 6 terminé pour le gabarit B. Décision prise le 04/09 (artifact 4af75171) : CollectionEditor reçoit un mode distant (RemoteCollection : create / delete / reorder asynchrones, voile de chargement, retour à l'état d'avant si l'appel échoue). Quiz l'utilise : la modale de question disparaît, les messages de score remontent en tête de carte, les réponses gardent QuizzResponseList tel quel. Menu garde la navigation vers sub_section_edit_screen — une sous-section est une section complète — et prend la grille de tuiles, extraite en SectionGrid et partagée avec les sections d'une configuration. Parcours : liste qui suit son contenu, les deux SizedBox(height: 500) de event_config et section_parcours_config retirés, éditeur à fenêtre unique inchangé.
🐛 Le rang en double est corrigé dans Quiz et Menu : ils n'écrivaient que l'order de l'élément déplacé, les autres gardaient le leur. La liste entière est renumérotée et envoyée en un Future.wait, comme le faisait déjà Parcours.
Agenda n'a pas été migré, et c'est délibéré : ses événements sont groupés par langue (_displayGroups, pastilles de langue quand un groupe en compte plusieurs) et n'ont pas d'ordre — une liste ordonnée à édition en place perdrait le groupement. Il reçoit ce qui lui revient : hauteur pilotée par le contenu (le height: 600 saute), lignes au système de tokens, modale conservée.
Gabarit C — Jeu est fait, Map et Événement sont dégrossis. game_config devient champs à gauche + canevas à droite : le canevas montre le découpage réel de l'image en rows × cols, seule décision que le formulaire ne montrait pas. Au passage, son TabBar n'en était pas un — les deux onglets construisaient le même formulaire et ne servaient qu'à écrire gameType : c'est devenu un choix à deux valeurs, et le SizedBox(height: 650) a disparu. geoloc_input_container passe à la coquille standard et affiche les coordonnées retenues au lieu d'un bouton « Changer la localisation », la carte prenant la hauteur disponible. Côté map_config : en-tête en grille de tokens, height: 700 et height: 70 retirés. Côté event_config : height: 600 retiré, la liste des blocs suit son contenu.
Reste : le canevas de Map et d'Événement — et il est maintenant maquetté : DOCS/claude design/refonte-gabarit-canevas.html (diffusion : artifact caf400b5). Liste des points, fiche, carte côte à côte ; les points non sélectionnés restent visibles en gris ; MapGeometryPicker sort de son dialogue pour devenir le canevas (déplacement, pas écriture) ; les informations pratiques d'un point (5 champs sur 9) sont repliées derrière un compteur. Absorbe showNewOrUpdateGeoPoint (362 l.), geometry_input_container et showNewOrUpdateMapAnnotation (182 l.). Une question produit reste ouverte : que montre le canevas d'Événement quand baseSectionMapId est nul — carte vide, ou invitation à choisir un plan d'abord (ma proposition).
Étape 1 du canevas faite le 04/09 : Components/map_canvas.dart — le dessin est sorti du dialogue. Il reçoit une géométrie, la rend modifiable, rend la main à chaque changement (pas de bouton « Enregistrer » : c'est l'écran qui enregistre), et affiche les autres tracés en fond via MapCanvasGhost. Barre d'outils compacte sur la carte, pied qui donne les coordonnées et ce qu'on modifie, bouton « agrandir » optionnel. MapGeometryPicker n'est plus qu'une coquille autour de lui, donc etape_fields et showNewOrUpdateEventAgenda ne bougent pas. Au passage, ses libellés « Ligne » / « Polygone » en dur sont passés par AppLocalizations. Taille tranchée : 420 px par défaut + mode agrandi, pas une interface centrée carte — un point porte neuf champs traduits, la carte n'en porte qu'un.
Étapes 2 à 4 faites : le gabarit C est en place dans Map. Map/geo_point_editor.dart — liste des points à gauche, fiche au centre, carte à droite ; les points non sélectionnés apparaissent en fond ; le mode agrandi replie la fiche et garde le rail. Les neuf champs traduits sont rangés par fréquence : titre, catégorie, description et image visibles, les cinq informations pratiques repliées derrière un compteur. map_config passe de 937 à ~510 lignes : la modale showNewOrUpdateGeoPoint n'est plus appelée, getElement et son boxDecoration sont supprimés, et recherche + catégories filtrent désormais la liste et la carte par un seul calcul. Les points sont créés, modifiés et supprimés directement par l'API (sectionMapCreate / Update / Delete), la suppression passant par la confirmation destructive.
Étape 5 faite : le canevas d'Événement. Les annotations passent dans l'éditeur de collection en mode distant, avec le canevas dans le panneau d'édition. Plan par défaut retenu (décision Thomas) : le premier plan disponible est pris quand baseSectionMapId est nul, plutôt que de bloquer. 🐛 Trouvé en chemin : MapAnnotationDTO portait déjà un champ geometry que l'ancienne modale ne remplissait jamais — elle ne réglait que geometryType. Une annotation déclarait donc une forme sans jamais recevoir de coordonnées ; le canevas les lui donne. Enfin showMultiStringInputAndResourceHTML est passé à la coquille standard : plus aucun dialogue de ce chantier sur l'ancienne.
Plan de test : DOCS/test-plan-refonte-config-section.md, 14 sections. Rien n'a été vérifié à l'écran — flutter analyze propre et flutter build web qui passe, c'est tout.
Reste : supprimer le code mort une fois l'arbre git propre — showNewOrUpdateGeoPoint, showNewOrUpdateMapAnnotation, pdf_file_input_container, pdf_list, new_update_pdfFile, listView_card_subSection, new_update_question_quizz ; et geometry_input_container quand etape_fields et showNewOrUpdateEventAgenda auront leur canevas.
showMultiStringInputAndResourceHTML sur l'ancienne coquille, et le code mort à supprimer une fois l'arbre propre : pdf_file_input_container, pdf_list, new_update_pdfFile, listView_card_subSection, new_update_question_quizz.height: 70, 100) dans les écrans non repris — showNewOrUpdateGeoPoint, showNewOrUpdateEventAgenda, sub_section_edit_screen, new_configuration_popup.
+
+Maquette prête et complète : DOCS/claude design/refonte-configuration-sections.html (diffusion : artifact 4d0c2ad6). Elle contient le diagnostic, les deux écrans, les 3 gabarits qui couvrent les 13 types de section, la coquille de dialogue, et deux dialogues dessinés — traduction et choix du type. Rien à décider côté design : tout est tranché.
Le blanc ne vient pas des écrans mais de 4 composants partagés. text_field_container.dart impose width: double.infinity + rayon 29 ; string_input_container.dart met le label à gauche dans un Row ; multi_string_input_container et resource_input_container se centrent eux-mêmes ; section_reorderList.dart réserve 0,3 × hauteur pour une liste horizontale de cartes 150×150. On les corrige une fois, les 13 types en héritent.
⚠️ Aucune direction visuelle à inventer : constants.dart porte déjà le système (kSpace1..8, kRadiusInput 5 / Card 8 / Shell 10 / Pill, kTitleCard, kLabelField, kTextHint) et confirmation_dialog.dart + resource_picker.dart l'appliquent déjà. Ces deux écrans sont simplement antérieurs au système.
Premier pas conseillé : select_resource_modal.dart, pas les composants partagés. Un fichier, ~15 lignes, zéro ligne dans ResourcesScreen, et ça corrige un vrai bug — AlertDialog dispose de hauteur − 48 mais le contenu en demande 0,85 × hauteur plus titre plus actions, d'où un défilement dans un défilement. Ça sert de pilote avant de toucher aux composants qui irriguent 30 écrans. ⛔ Ne pas toucher à la largeur 0,85 : la règle du rail de facettes de v1-mediatheque-plan.md §3.3 est écrite sur cette hypothèse et déjà codée.
⚠️ Deux pièges qui ne sont pas du visuel. D'abord, manager-app n'a que 3 tests, aucun sur ces composants : vérification manuelle, et il reste des conteneurs à hauteur figée (height: 70, 100, SizedBox(height: 250)) autour — ce qui rentrait juste débordera. Ensuite un chemin d'écriture est câblé dans les composants : section_detail_screen.dart:270 déclenche un save(true, …) depuis MultiStringInputContainer.onGetResult, et la ligne 381 un save(false, …) depuis la case iBeacon. À ne pas perdre ni dédoubler.
⚠️ Le piège Quill (remonté de mémoire par Thomas, confirmé dans le code). Ça marche aujourd'hui parce que l'empilement vertical monte toutes les langues : _controllers est bâtie une fois dans initState (translation_input_container.dart:52) et libérée seulement dans dispose. Le rail de langues de la maquette casse cette propriété s'il reconstruit l'éditeur à chaque bascule. Règle : map construite une fois pour toutes les langues, bascule par IndexedStack/Offstage, jamais de TabBarView (construction paresseuse, enfants lâchés hors écran sans AutomaticKeepAliveClientMixin). Trace d'une tentative abandonnée : translation_tab.dart existe en TabBar, son appel est en commentaire dans multi_input_modal.dart:46-51. À retester en priorité : la traduction IA (ligne 142) et « appliquer à toutes les langues » (ligne 183), les deux chemins qui libèrent et recréent les contrôleurs.
Deux fusions et un ménage décidés dans la maquette : multi_input_modal.dart est le jumeau sans HTML du modal de traduction (même travail, deux implémentations) → une coquille, deux corps, Quill si isHTML et champ simple sinon ; les 8 showNewOrUpdate* et les 2 sélecteurs géo disparaissent dans les gabarits B et C ; showValues est mort en double (select_resource_modal.dart:64 et multi_input_modal.dart:186, appel commenté ligne 53).
⚠️ Conflit résolu avec l'audit — security/audit-manager-app.md recommandait d'extraire un FormDialogScaffold pour les 15 occurrences des showNewOrUpdate*. Or la refonte les supprime : les extraire d'abord, ce serait refactoriser du code à jeter. Ordre imposé : absorber d'abord (gabarits B et C), extraire ensuite sur ce qui survit — traduction, nouvelle section, nouvelle configuration, sélecteur de couleur, fichiers PDF, catégories. L'audit et STATUS.md ont été annotés en ce sens le 2026-09-04.
🐛 Trouvé en passant, à vérifier : pdf_file_input_container.dart et category_input_container.dart sont un copier-coller — mêmes dimensions, même structure — mais le second a ses libellés en dur (« Annuler », « Valider ») et son onGetResult(result) commenté. Une catégorie de carte modifiée ne remonte peut-être pas à la volée. Lecture de code, pas testé.
Une photo d'un bâtiment du Fourneau → sa version d'époque → un personnage en boucle de 8 s. Le but n'est pas de produire un asset, c'est de répondre à la seule question dont dépend tout le reste : est-ce que ça a l'air crédible, ou est-ce que ça décroche ? En patrimoine la crédibilité est la valeur — si le rendu est inquiétant, la fonctionnalité n'existe pas, quels que soient le prix et la facilité.
+Le principe à valider : le text-to-video pur est écarté (il invente la géométrie du lieu). Le lieu réel reste la base, l'IA n'ajoute que le mouvement — c'est « Point de départ » du Studio transposé à la vidéo, donc aucun mécanisme nouveau en base : Kind = Video et sourceFidelity existent déjà, c'est un gabarit de plus.
Respecter les contraintes pendant le test, sinon il ne prouve rien : 6-10 s en boucle, une seule personne, mouvements simples, caméra calme, plan large ou moyen, jamais de gros plan sur les mains ou le visage.
+⚠️ Deux choses déjà tranchées, à ne pas rejouer. Le motion transfer part en prestation Unov, pas dans le produit : il demande un tournage et un consentement écrit, ce qui échoue par construction le critère du §0 (« un conservateur produit sans intervention »). Et le cadrage de vente est resserré à « là où il ne reste rien » — bâtiment disparu, métier éteint, site réduit à ses fondations : partout où des archives existent, une vraie photo d'époque bat une boucle générée.
+⛔ Ne bloque rien et n'est bloqué par rien — c'est justement l'intérêt de le faire tôt. Mais la fonctionnalité, elle, reste derrière le prérequis dur du Studio : IResourceBlobService n'expose toujours que IsConfigured et DeleteAsync, le backend ne sait pas écrire dans le bucket.