DOCS/v2/vr-menu-bento-plan.md
Thomas Fransolet a227fc9d00 VR : quatre écarts entre l'onglet du manager et le casque
Relevé dans le code le 2026-09-16. Même cause pour les quatre : XR-2 a réutilisé
AppConfigurationLinkScreen tel quel, et cet écran sert quatre canaux qui n'ont
pas les mêmes réglages. Le sélecteur bento ne pilote rien en VR (les spans sont
portés par le lien de configuration, pas par les sections), la notification
annonce « application mobile », la popup d'édition d'un casque est celle du
kiosk avec la moitié des champs morte, et un bento VR suppose des spans par
section jusque dans Unity.

Plan dans v2/vr-menu-bento-plan.md, carte kanban 340.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:18:40 +02:00

11 KiB
Raw Blame History

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) 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:80pairing.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) 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.