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

234 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.