# Menu VR — bento, et le reste de l'onglet VR > Chantier **V2**, analysé dans le code le **2026-09-16**. > Suite de `vr-quest-unity-plan.md` (lot XR-4, item E5 — le menu flottant existe et tourne). > Ce document ne rouvre pas les décisions du plan VR : il traite **quatre écarts** entre ce > que le manager laisse configurer et ce que le casque affiche réellement. --- ## Le constat L'onglet **Main / VR** du manager ([`vr_screen.dart`](../../manager-app/lib/Screens/Vr_devices/vr_screen.dart)) réutilise `AppConfigurationLinkScreen`, l'écran de configuration partagé avec le mobile, le web et le kiosk. Il hérite donc de tout son outillage — dont un **sélecteur de forme bento** (les quatre petits boutons carré / large / haut / grand) qui, côté casque, ne pilote rien. Vérifié : | Fait | Preuve | |---|---| | Les spans sont portés par le **lien de configuration**, pas par les sections | `AppConfigurationLink.cs:35` — commentaire d'origine : *« Specific Mobile & Web »* | | Ils ne pilotent que l'écran « choisis ta configuration » | `mymuseum-visitapp/lib/Screens/Home/home_3.0.dart:1179`, `visitapp-web/src/components/ConfigurationGrid.tsx:38` | | Le casque ne les lit jamais | `ConfigurationExport.cs` n'a aucun champ span ; `grep -r "Span" Assets/Scripts` → rien | | Un casque est appairé à **une seule** configuration | `MenuBootstrap.cs:80` — `pairing.ConfigurationId`. Il n'y a pas d'écran de choix de configuration en VR | | Le menu VR affiche les **sections racines**, toutes à la même taille | `FloatingMenu.cs:106-125` — arc de 5 panneaux par rangée, `0,50 × 0,34 m` fixes | Autrement dit : en VR, le bento du manager porte sur un objet (la configuration) qui n'a pas d'écran de sélection, alors que l'objet qui *a* un écran (la section) n'a pas de spans. Deux autres écarts, plus petits, de la même famille « écran partagé, canal différent » : la notification et la popup d'édition d'un casque. --- ## Décisions actées | Décision | Raison | |---|---| | **Le bento VR porte sur les sections**, pas sur les configurations | C'est le seul écran de choix qui existe dans le casque. Sans ça, les boutons resteront décoratifs quoi qu'on fasse | | **L'arc est conservé** | Ce n'est pas un défaut de rendu : un mur plat oblige à regarder ses bords de biais. La grille bento est calculée en cellules, puis chaque cellule est *mappée* sur un angle + une hauteur | | **Grille cible : 6 colonnes × 2 rangées max** | Le confort visuel en VR c'est ±60° horizontaux ; au-delà on encercle le visiteur. Format naturellement paysage, proche du rendu web | | **Une popup casque distincte de celle du kiosk** | La moitié des champs de la popup partagée est morte en VR (voir lot 4) — les masquer un par un dans un widget commun coûte plus cher que d'en écrire un | | Les spans de section **ne sont pas exposés au mobile/web en V1** | Ils ont déjà leur bento, au niveau configuration. Ouvrir un second niveau de grille sur ces fronts est un chantier produit à part | --- ## Lot 1 — La notification annonce la mauvaise app **~15 minutes, indépendant des autres.** Modifier un contenu VR affiche « application mobile mise à jour ». Le paramètre prévu pour ça existe déjà et n'est simplement pas renseigné : ``` app_configuration_link_screen.dart:144 final appUpdatedMsg = widget.appUpdatedLabel ?? AppLocalizations.of(context)!.appUpdatedSuccess; ↑ jamais passé par vr_screen.dart ``` À faire : 1. Clé `vrAppUpdatedSuccess` dans `app_fr.arb` (template), puis `app_en.arb` et `app_nl.arb`. 2. `vr_screen.dart:38` — passer `appUpdatedLabel: l.vrAppUpdatedSuccess` à `AppConfigurationLinkScreen`. 3. `vr_screen.dart:78` (`_backgroundCard`) — même remplacement, c'est un `showNotification` séparé. 4. `vr_devices_tab.dart:280` (`_edit`) — idem. ⚠️ Le template i18n est `app_fr.arb` ; les trois langues sont obligatoires (cf. `manager-app/CLAUDE.md`). --- ## Lot 2 — Des spans par section (backend + manager) **Le socle du lot 3. Rien de visible dans le casque avant le lot 3.** ### Backend ``` Data/Section.cs + int? GridColSpan, int? GridRowSpan DTOs/SectionDTO.cs + gridColSpan, gridRowSpan (après `order`, même famille) → mapping dans ToDTO / FromDTO des champs communs Migrations/ AddSectionGridSpans ``` L'export est **gratuit** côté transport : `/api/configuration/{id}/export` (`ConfigurationController.cs:437`) sérialise déjà les `SectionDTO` complets via `SectionFactory.ToDTO`. Les nouveaux champs suivent. ⚠️ `Section.ToDTO()` n'est pas virtuelle — vérifier que la copie des champs communs se fait bien au même endroit que `order`, sinon les spans sortiront nuls sur les sous-types. ⚠️ `dotnet ef` ignore `appsettings.Development.json` : passer par `MIGRATIONS_CONNECTION`. ### Client API `manager-app/manager_api_new/` — **éditer les fichiers générés à la main**, ne jamais relancer la génération OpenAPI (règle projet). ### Manager L'onglet « Contenu de l'application VR » ne doit plus lister des *configurations* avec des spans, mais les *sections racines* de la configuration, avec leurs spans et un aperçu. Deux options, à trancher en début de conversation : - **a)** Un écran VR dédié, qui reprend le sélecteur de forme et l'aperçu live de `AppConfigurationLinkScreen` mais s'alimente en sections. Plus propre, plus de code. - **b)** Paramétrer `AppConfigurationLinkScreen` pour qu'il accepte une source « sections ». Moins de code, mais l'écran sert déjà quatre canaux — il est le point de contact de tous ces écarts, l'élargir encore le rend plus difficile à corriger. Recommandation : **(a)**, c'est précisément l'accumulation de cas particuliers dans cet écran partagé qui produit les trois autres lots de ce document. À réutiliser tel quel : le debounce de 400 ms sur l'enregistrement des spans (`app_configuration_link_screen.dart:89-98`) — un slider de forme qui écrit à chaque frame sature l'API. --- ## Lot 3 — Le rendu bento dans le casque (Unity) ### Ce qui change `FloatingMenu.Place(panel, index, total)` positionne aujourd'hui par index brut : `row = index / 5`, angle = pas fixe. Il faut passer par une grille. ``` 1. Placement dense par spans → chaque section obtient (col, row, colSpan, rowSpan) 2. Mapping cellule → arc → angle = (col + colSpan/2 - cols/2) * StepDegrees hauteur = HeightMeters - row * (cellH + gap) 3. Taille du quad → width = colSpan * cellW + (colSpan-1) * gap height = rowSpan * cellH + (rowSpan-1) * gap ``` L'algorithme de placement dense existe en Dart dans `manager-app/myinfomate_layout/` (le même que le mobile). Deux voies : - **Le porter en C#** (~150 lignes) — le casque reste autonome, cohérent avec le cache-d'abord. - **Le faire calculer par le backend** dans l'export — une seule implémentation, mais le casque devient dépendant d'un champ calculé, et le cache d'un vieux contenu porte un vieux placement. Recommandation : **le porter en C#**, l'export reste une description, pas une mise en page. ### Fichiers ``` Assets/Scripts/Net/ConfigurationExport.cs + GridColSpan / GridRowSpan sur SectionSummary Assets/Scripts/Menu/FloatingMenu.cs Place() → grille ; MaxPerRow 5 → 6 Assets/Scripts/Menu/MenuItemPanel.cs Create() prend une taille au lieu des const Assets/Scripts/Menu/BentoLayout.cs (neuf) le placement dense ``` ⚠️ `MenuItemPanel.WidthMeters` / `HeightMeters` sont des `const` **publiques**, lues ailleurs (`FloatingMenu.Place`, et à vérifier dans `PagedView` / `SectionPages`). Les rendre variables d'instance sans casser ces usages — garder les const comme valeurs de cellule par défaut est le chemin le plus court. ⚠️ La cascade d'apparition (`PlayAppear(i * 0.04f)`) doit suivre l'ordre de **lecture** de la grille (rangée par rangée, gauche à droite), pas l'index de la liste : avec des spans, les deux divergent. ⚠️ Un panneau `colSpan = 2` couvre ~32° d'arc. Un quad plat reste acceptable à cette ouverture ; au-delà (un hypothétique span 3) il faudrait le subdiviser. Ne pas le faire avant d'en avoir besoin. ### Vérification Sans casque, le viewer web (`vr-app/viewer/`) est le chemin court pour valider un placement. Avec casque : `adb push` d'un `scene.json`, l'app le préfère à celui embarqué. --- ## Lot 4 — La popup d'édition d'un casque **Indépendant. ~une demi-journée.** `showChangeInfo` ([`Kiosk_devices/change_device_info_modal.dart:16`](../../manager-app/lib/Screens/Kiosk_devices/change_device_info_modal.dart)) est partagée avec le kiosk : largeur figée à 580, une colonne scrollée, et deux boutons `RoundedButton` dont les libellés **« Annuler » / « Changer » sont en dur** (violation i18n). Le vrai problème n'est pas la mise en page : **la moitié des champs ne sert à rien en VR.** Aucun script Unity ne lit ces réglages — `grep -r` sur `Assets/Scripts` : zéro occurrence. | Champ | En VR | |---|---| | `name`, `configurationId` | **gardés** | | `primaryColor`, `secondaryColor` | morts — le menu a sa propre palette de panneaux | | `loaderImageId` | mort — un casque n'a pas d'écran de chargement 2D | | `screenPercentageSectionsMainPage`, `roundedValue` | morts — ce sont des réglages d'écran plat | | `isSectionImageBackground`, `isHour`, `isDate` | morts | À faire : `Vr_devices/change_headset_modal.dart`, paysage deux colonnes (~940 px), avec identité + configuration à gauche, fond immersif à droite (il vit aujourd'hui dans le panneau latéral de l'onglet contenu — à décider : déplacé ou dupliqué). Boutons repris du langage visuel de la Médiathèque / du Studio IA plutôt que des `RoundedButton` historiques. Tout le texte passe par `AppLocalizations`, y compris les deux boutons. --- ## Ordre ``` Lot 1 ────────────────────────────── indépendant, à faire en premier (15 min) Lot 4 ────────────────────────────── indépendant Lot 2 ───→ Lot 3 le 3 n'affiche rien sans le 2 ``` --- ## Mini-prompts de lancement Un lot = une conversation. Chaque prompt suppose que ce fichier est lisible. **Lot 1** > Lis `DOCS/v2/vr-menu-bento-plan.md` § Lot 1 et applique-le : la notification de l'onglet VR > du manager annonce « application mobile ». Ajoute la clé i18n dans les trois langues et passe > `appUpdatedLabel` aux quatre endroits listés. **Lot 2** > Lis `DOCS/v2/vr-menu-bento-plan.md` § Lot 2. Ajoute `gridColSpan` / `gridRowSpan` sur > `Section` côté `manager-service` (entité, DTO, migration, client API édité à la main), puis > fais que l'onglet « Contenu de l'application VR » du manager édite les spans **des sections** > et non des configurations. Commence par me dire si tu pars sur l'option (a) ou (b) du plan. **Lot 3** > Lis `DOCS/v2/vr-menu-bento-plan.md` § Lot 3. Dans `vr-app/unity`, fais que le menu flottant > place ses panneaux sur une grille bento par spans mappée sur l'arc, au lieu de l'index brut > actuel. Le lot 2 doit être fait — vérifie que l'export porte bien les spans avant de commencer. **Lot 4** > Lis `DOCS/v2/vr-menu-bento-plan.md` § Lot 4. Écris une popup d'édition de casque dédiée en > paysage deux colonnes, sans les champs morts en VR, avec les boutons du langage visuel de la > Médiathèque, et tout le texte en i18n FR/EN/NL.