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>
234 lines
11 KiB
Markdown
234 lines
11 KiB
Markdown
# 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.
|