Tags NFC : le plan arrêté après lecture du code

Le tag porte l'URL du QR, rien d'autre : aucune entité, aucune colonne sur
Section, aucun code NFC pour le cas nominal — l'OS lit le tag et c'est le
lien universel qui aiguille. Ce qui reste à écrire, c'est la colle
(assetlinks.json / AASA, routage du deep link), et elle répare au passage le
QR code, qui n'ouvre jamais l'app depuis l'appareil photo.

Prérequis dur : app.myinfomate.be doit être déployé pour servir les
.well-known.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thomas Fransolet 2026-09-17 11:36:34 +02:00
parent ad73aefe38
commit 488cd501f4
4 changed files with 142 additions and 54 deletions

View File

@ -975,15 +975,22 @@
<span class="src">todo-features.md — AR</span> <span class="src">todo-features.md — AR</span>
</article> </article>
<article class="card" data-area="backend manager visitapp" data-horizon="v2"> <article class="card" data-area="visitapp manager backend" data-horizon="v1">
<div class="card-meta"><span class="tag">visitapp</span><span class="tag">matériel</span><span class="flag f-warn">Nouveau 16/09 · le gros du coût est les Universal/App Links, partagés avec le QR</span></div> <div class="card-meta"><span class="tag">visitapp</span><span class="tag">matériel</span><span class="flag f-warn">Arrêté 16/09 · 4,5 j · prérequis dur = déployer app.myinfomate.be</span></div>
<h3>Tags NFC comme déclencheurs de section</h3> <h3>Tags NFC — la colle des liens universels (et le QR en profite)</h3>
<p><strong>Troisième déclencheur physique, à côté du QR et du beacon.</strong> Analyse et faisabilité : <a href="../v2/nfc-triggers-plan.md">v2/nfc-triggers-plan.md</a>.</p> <p><strong>Comportement retenu</strong> : le tag porte <strong>l'URL du QR</strong> ; app installée → elle s'ouvre sur la section, app absente → la page « installez l'application », conservée telle quelle. Pas d'entité <code>NfcTag</code>, pas de code opaque, pas de colonne sur <code>Section</code>. Analyse : <a href="../v2/nfc-triggers-plan.md">v2/nfc-triggers-plan.md</a>.</p>
<p>⚠️ <strong>Deux recadrages par rapport à la formulation initiale.</strong> (1) Le déclencheur n'est pas un « POI » : dans MyInfoMate le POI est un point posé sur un GLB (VR/3D, <code>immersif-frontiere-plan.md</code>). Le déclencheur physique est une <strong>Section</strong><code>Section.IsBeacon</code> / <code>BeaconId</code>, lu par <code>geo_beacon_trigger_service.dart:177</code>. Le tag NFC se branche au même endroit que le beacon. (2) L'URL <code>/t/{instanceId}/{code}</code> et sa table de mapping <strong>n'ont pas d'équivalent existant à réutiliser</strong> : les QR d'aujourd'hui portent l'id de section directement dans l'URL (trois formats coexistants, <code>ScannerDialog.dart:31-38</code>), il n'y a aucun endpoint de résolution <code>code → section</code>. <strong>v1 = le tag porte la même URL que le QR</strong>, donc zéro entité, zéro endpoint, zéro CRUD ; l'indirection ne se justifie que si un client demande un parc de tags réassignables — et elle vaut bien moins qu'en QR, puisque réécrire un NTAG prend deux secondes alors qu'un QR se réimprime.</p> <p><strong>Aucun code NFC à écrire pour le cas nominal</strong> : l'OS lit le tag, affiche une bannière et ouvre l'URL — c'est le <strong>lien universel</strong> qui aiguille vers l'app. <code>nfc_manager</code> ne sert qu'au bouton « Scanner un tag » in-app.</p>
<p><strong>Le vrai coût est ailleurs : les Universal Links (iOS) et App Links (Android) n'existent pas.</strong> Aucun <code>assetlinks.json</code> ni <code>apple-app-site-association</code> sur nos domaines (le seul du workspace est dans <code>OpenGlasses</code>, qui n'est pas à nous), et <code>app_links</code> n'est branché que sur le retour de deep link Meta AI (<code>main.dart:69</code>). ✅ <strong>Ce lot bénéficie autant au QR</strong> : aujourd'hui un QR scanné par l'appareil photo natif n'ouvre pas l'app.</p> <p><strong>La colle, c'est ça</strong> — et les deux premiers points <strong>réparent aussi le QR</strong>, qui aujourd'hui n'ouvre jamais l'app quand on le scanne à l'appareil photo :</p>
<p><strong>Dépendance</strong> : le fallback web d'un tag scanné sans app installée, c'est <code>visitapp-web</code><strong>pas encore déployé</strong> (carte « Déployer le visiteur web »). Sans lui, un tag scanné par un visiteur sans l'app tombe dans le vide.</p> <ul>
<p><strong>Ce qui ne marchera pas, à dire au commercial</strong> : pas de lecture NFC depuis <code>visitapp-web</code> (WebNFC est Chrome/Android uniquement, jamais Safari), et l'écriture de tags depuis iOS est trop contrainte — <strong>l'écran admin de programmation sera Android-only</strong>. En contrepartie, iPhone XS+ lit un tag NDEF <strong>sans aucune app</strong>, ce qui est l'argument réel du canal.</p> <li><strong>Liens universels — 1 j.</strong> Aucun <code>assetlinks.json</code> ni <code>apple-app-site-association</code> n'existe. ⚠️ Ils doivent être servis par le <strong>domaine de l'URL du tag</strong> : <code>app.myinfomate.be</code>, <strong>absent du compose</strong> → la carte « Déployer le visiteur web » est un <strong>prérequis dur</strong>. Réunir avant : SHA-256 de la clé de release (<code>tool/build_apk.sh</code>) et Team ID iOS — une empreinte fausse ne lève aucune erreur, le lien part juste dans le navigateur.</li>
<span class="src">nfc-triggers-plan.md · ScannerDialog.dart:31-38 · Section.cs:57 · main.dart:69</span> <li><strong>Router le deep link entrant — 0,5 j.</strong> <code>main.dart:68</code> envoie tout à <code>MetaGlassesService</code>, et le listener est <strong>coupé sur iOS</strong> (<code>!Platform.isAndroid</code>, <code>kEnableGlasses</code>). Router par motif sans casser le retour de registration Meta.</li>
<li><strong>Extraire la résolution du scanner — 1,5 j.</strong> <code>ScannerDialog._onDetect</code> sait déjà tout faire (3 formats de QR, section hors visite en cours, proposition d'une autre visite, télémétrie) mais c'est un <code>State</code> qui manipule <code>context</code>/<code>Navigator</code> et capture le navigator avant le <code>pop</code>. Sortir ça dans un service <strong>sans perdre les cas limites</strong>, qui sont tous des corrections de bugs réels.</li>
<li><strong><code>IsNFCEnabled</code> — 1 j.</strong> Calqué sur <code>ApplicationInstance.IsQRCodeEnabled</code> (<code>ApplicationInstance.cs:57</code>, défaut <code>true</code>, switch <code>app_configuration_link_screen.dart:400</code>, consommé en 4 points + <code>visitapp-web</code>). ⚠️ Il masque le <strong>bouton</strong> « Scanner un tag », il <strong>n'empêche pas</strong> un tag posé au mur d'ouvrir l'app — le deep link vient de l'OS. ⚠️ Ne pas le poser sur <code>Configuration</code> : <code>Configuration.IsQRCode</code> y est <strong>code mort</strong>, lu par aucune app.</li>
<li><strong>Distinguer le canal — 0,5 j.</strong> Sans <code>?src=nfc</code> sur l'URL encodée + <code>VisitEventType.NfcScan</code>, le NFC est compté en <code>qrScan</code> : on ne saura jamais s'il sert. L'enum passe par le client généré de <code>manager_api_new</code>, <strong>à éditer à la main</strong>.</li>
<li><strong>Encoder les tags — 0 j de dev.</strong> NFC Tools, 2 s par tag, une URL par point.</li>
</ul>
<p>À savoir avant de vendre : <strong>iPhone XS+</strong> lisent un tag sans app (7/8/X non) ; l'OS affiche une <strong>bannière à taper</strong>, pas d'ouverture magique ; un tag à plat sur du métal ne se lit pas ; pas de WebNFC sur Safari, donc rien sur <code>visitapp-web</code> ; et le NFC <strong>ne remplace pas le beacon</strong> (geste volontaire à 1-2 cm).</p>
<span class="src">nfc-triggers-plan.md · main.dart:68 · ScannerDialog.dart:107 · ApplicationInstance.cs:57</span>
</article> </article>
<article class="card" data-area="manager" data-horizon="v2"> <article class="card" data-area="manager" data-horizon="v2">

View File

@ -1,13 +1,20 @@
--- ---
title: Tags NFC comme déclencheurs de section title: Tags NFC — la colle des liens universels (et le QR en profite)
area: backend manager visitapp area: visitapp manager backend
horizon: v2 horizon: v1
tags: visitapp, matériel tags: visitapp, matériel
flag: warn | Nouveau 16/09 · le gros du coût est les Universal/App Links, partagés avec le QR flag: warn | Arrêté 16/09 · 4,5 j · prérequis dur = déployer app.myinfomate.be
src: nfc-triggers-plan.md · ScannerDialog.dart:31-38 · Section.cs:57 · main.dart:69 src: nfc-triggers-plan.md · main.dart:68 · ScannerDialog.dart:107 · ApplicationInstance.cs:57
--- ---
<p><strong>Troisième déclencheur physique, à côté du QR et du beacon.</strong> Analyse et faisabilité : <a href="../v2/nfc-triggers-plan.md">v2/nfc-triggers-plan.md</a>.</p> <p><strong>Comportement retenu</strong> : le tag porte <strong>l'URL du QR</strong> ; app installée → elle s'ouvre sur la section, app absente → la page « installez l'application », conservée telle quelle. Pas d'entité <code>NfcTag</code>, pas de code opaque, pas de colonne sur <code>Section</code>. Analyse : <a href="../v2/nfc-triggers-plan.md">v2/nfc-triggers-plan.md</a>.</p>
<p>⚠️ <strong>Deux recadrages par rapport à la formulation initiale.</strong> (1) Le déclencheur n'est pas un « POI » : dans MyInfoMate le POI est un point posé sur un GLB (VR/3D, <code>immersif-frontiere-plan.md</code>). Le déclencheur physique est une <strong>Section</strong><code>Section.IsBeacon</code> / <code>BeaconId</code>, lu par <code>geo_beacon_trigger_service.dart:177</code>. Le tag NFC se branche au même endroit que le beacon. (2) L'URL <code>/t/{instanceId}/{code}</code> et sa table de mapping <strong>n'ont pas d'équivalent existant à réutiliser</strong> : les QR d'aujourd'hui portent l'id de section directement dans l'URL (trois formats coexistants, <code>ScannerDialog.dart:31-38</code>), il n'y a aucun endpoint de résolution <code>code → section</code>. <strong>v1 = le tag porte la même URL que le QR</strong>, donc zéro entité, zéro endpoint, zéro CRUD ; l'indirection ne se justifie que si un client demande un parc de tags réassignables — et elle vaut bien moins qu'en QR, puisque réécrire un NTAG prend deux secondes alors qu'un QR se réimprime.</p> <p><strong>Aucun code NFC à écrire pour le cas nominal</strong> : l'OS lit le tag, affiche une bannière et ouvre l'URL — c'est le <strong>lien universel</strong> qui aiguille vers l'app. <code>nfc_manager</code> ne sert qu'au bouton « Scanner un tag » in-app.</p>
<p><strong>Le vrai coût est ailleurs : les Universal Links (iOS) et App Links (Android) n'existent pas.</strong> Aucun <code>assetlinks.json</code> ni <code>apple-app-site-association</code> sur nos domaines (le seul du workspace est dans <code>OpenGlasses</code>, qui n'est pas à nous), et <code>app_links</code> n'est branché que sur le retour de deep link Meta AI (<code>main.dart:69</code>). ✅ <strong>Ce lot bénéficie autant au QR</strong> : aujourd'hui un QR scanné par l'appareil photo natif n'ouvre pas l'app.</p> <p><strong>La colle, c'est ça</strong> — et les deux premiers points <strong>réparent aussi le QR</strong>, qui aujourd'hui n'ouvre jamais l'app quand on le scanne à l'appareil photo :</p>
<p><strong>Dépendance</strong> : le fallback web d'un tag scanné sans app installée, c'est <code>visitapp-web</code><strong>pas encore déployé</strong> (carte « Déployer le visiteur web »). Sans lui, un tag scanné par un visiteur sans l'app tombe dans le vide.</p> <ul>
<p><strong>Ce qui ne marchera pas, à dire au commercial</strong> : pas de lecture NFC depuis <code>visitapp-web</code> (WebNFC est Chrome/Android uniquement, jamais Safari), et l'écriture de tags depuis iOS est trop contrainte — <strong>l'écran admin de programmation sera Android-only</strong>. En contrepartie, iPhone XS+ lit un tag NDEF <strong>sans aucune app</strong>, ce qui est l'argument réel du canal.</p> <li><strong>Liens universels — 1 j.</strong> Aucun <code>assetlinks.json</code> ni <code>apple-app-site-association</code> n'existe. ⚠️ Ils doivent être servis par le <strong>domaine de l'URL du tag</strong> : <code>app.myinfomate.be</code>, <strong>absent du compose</strong> → la carte « Déployer le visiteur web » est un <strong>prérequis dur</strong>. Réunir avant : SHA-256 de la clé de release (<code>tool/build_apk.sh</code>) et Team ID iOS — une empreinte fausse ne lève aucune erreur, le lien part juste dans le navigateur.</li>
<li><strong>Router le deep link entrant — 0,5 j.</strong> <code>main.dart:68</code> envoie tout à <code>MetaGlassesService</code>, et le listener est <strong>coupé sur iOS</strong> (<code>!Platform.isAndroid</code>, <code>kEnableGlasses</code>). Router par motif sans casser le retour de registration Meta.</li>
<li><strong>Extraire la résolution du scanner — 1,5 j.</strong> <code>ScannerDialog._onDetect</code> sait déjà tout faire (3 formats de QR, section hors visite en cours, proposition d'une autre visite, télémétrie) mais c'est un <code>State</code> qui manipule <code>context</code>/<code>Navigator</code> et capture le navigator avant le <code>pop</code>. Sortir ça dans un service <strong>sans perdre les cas limites</strong>, qui sont tous des corrections de bugs réels.</li>
<li><strong><code>IsNFCEnabled</code> — 1 j.</strong> Calqué sur <code>ApplicationInstance.IsQRCodeEnabled</code> (<code>ApplicationInstance.cs:57</code>, défaut <code>true</code>, switch <code>app_configuration_link_screen.dart:400</code>, consommé en 4 points + <code>visitapp-web</code>). ⚠️ Il masque le <strong>bouton</strong> « Scanner un tag », il <strong>n'empêche pas</strong> un tag posé au mur d'ouvrir l'app — le deep link vient de l'OS. ⚠️ Ne pas le poser sur <code>Configuration</code> : <code>Configuration.IsQRCode</code> y est <strong>code mort</strong>, lu par aucune app.</li>
<li><strong>Distinguer le canal — 0,5 j.</strong> Sans <code>?src=nfc</code> sur l'URL encodée + <code>VisitEventType.NfcScan</code>, le NFC est compté en <code>qrScan</code> : on ne saura jamais s'il sert. L'enum passe par le client généré de <code>manager_api_new</code>, <strong>à éditer à la main</strong>.</li>
<li><strong>Encoder les tags — 0 j de dev.</strong> NFC Tools, 2 s par tag, une URL par point.</li>
</ul>
<p>À savoir avant de vendre : <strong>iPhone XS+</strong> lisent un tag sans app (7/8/X non) ; l'OS affiche une <strong>bannière à taper</strong>, pas d'ouverture magique ; un tag à plat sur du métal ne se lit pas ; pas de WebNFC sur Safari, donc rien sur <code>visitapp-web</code> ; et le NFC <strong>ne remplace pas le beacon</strong> (geste volontaire à 1-2 cm).</p>

View File

@ -24,7 +24,7 @@ Plans détaillés, pas encore démarrés en dev (sauf mention contraire) :
| AI Persona par venue (guide IA personnalisable) | [v2/myinfomate-ai-persona-analysis.md](../v2/myinfomate-ai-persona-analysis.md) | Architecture de référence pour les chantiers IA ci-dessus | | AI Persona par venue (guide IA personnalisable) | [v2/myinfomate-ai-persona-analysis.md](../v2/myinfomate-ai-persona-analysis.md) | Architecture de référence pour les chantiers IA ci-dessus |
| Lunettes Ray-Ban Meta | [roadmap.md](../roadmap.md) (section XR), [rayban-meta-integration.md](../rayban-meta-integration.md) | 🔨 **POC fonctionnel — bien plus avancé que ce que cette ligne disait jusqu'au 2026-08-09.** Voir §5bis | | Lunettes Ray-Ban Meta | [roadmap.md](../roadmap.md) (section XR), [rayban-meta-integration.md](../rayban-meta-integration.md) | 🔨 **POC fonctionnel — bien plus avancé que ce que cette ligne disait jusqu'au 2026-08-09.** Voir §5bis |
| VR Meta Quest (borne office tourisme) | [v2/vr-quest-unity-plan.md](../v2/vr-quest-unity-plan.md) · [roadmap.md](../roadmap.md) (section XR) | 📦 **V2** — ⚠️ **« zéro ligne de code » était faux** (corrigé le 2026-08-31, relevé dans le code). Le canal VR est déjà dans le modèle : `AppType.VR`, `Instance.IsVR` en base depuis juillet 2025, sous-menu « VR » branché dans `main_screen.dart:65`, filtre stats + i18n `statsChannelVR` prêts. **Ce qui manque** : l'écran de flotte (`main_screen.dart:704` = `Text("TODO vr")`), un `Device.AppType` (le `DeviceController` est hardcodé sur `Tablet`), et l'app Unity. Back-office XR : **8-12 j**. App Unity : **3-4 mois**. La landing annonce « Meta Quest — Bientôt » en 4 langues (`translations.ts`, clé `mode4Desc`) | | VR Meta Quest (borne office tourisme) | [v2/vr-quest-unity-plan.md](../v2/vr-quest-unity-plan.md) · [roadmap.md](../roadmap.md) (section XR) | 📦 **V2** — ⚠️ **« zéro ligne de code » était faux** (corrigé le 2026-08-31, relevé dans le code). Le canal VR est déjà dans le modèle : `AppType.VR`, `Instance.IsVR` en base depuis juillet 2025, sous-menu « VR » branché dans `main_screen.dart:65`, filtre stats + i18n `statsChannelVR` prêts. **Ce qui manque** : l'écran de flotte (`main_screen.dart:704` = `Text("TODO vr")`), un `Device.AppType` (le `DeviceController` est hardcodé sur `Tablet`), et l'app Unity. Back-office XR : **8-12 j**. App Unity : **3-4 mois**. La landing annonce « Meta Quest — Bientôt » en 4 langues (`translations.ts`, clé `mode4Desc`) |
| Tags NFC (déclencheur de section) | [v2/nfc-triggers-plan.md](../v2/nfc-triggers-plan.md) | 📦 **V2** — analysé dans le code le 2026-09-16. Troisième déclencheur physique à côté du QR et du beacon. ⚠️ **Le coût n'est pas le NFC** : les **Universal Links / App Links n'existent pas** (aucun `assetlinks.json` ni `apple-app-site-association`, `app_links` branché seulement sur le retour Meta AI, `main.dart:69`) — ce lot-là **sert aussi au QR**, qui aujourd'hui n'ouvre pas l'app quand il est scanné par l'appareil photo natif. Le tag porte **la même URL que le QR** : aucune entité, aucun endpoint de résolution en v1. Dépend du **déploiement de `visitapp-web`** pour le fallback sans app. 4-7 j | | Tags NFC (déclencheur de contenu) | [v2/nfc-triggers-plan.md](../v2/nfc-triggers-plan.md) | ⚠️ **V1 — 4,5 j** (arrêté le 2026-09-16). Le tag porte **l'URL du QR** ; app installée → elle s'ouvre sur la section, app absente → la page « installez l'application », conservée. **Aucune entité, aucune colonne sur `Section`, et aucun code NFC pour le cas nominal** : c'est le **lien universel** qui aiguille. ✅ Le travail réel (`assetlinks.json`/AASA, routage du deep link entrant, extraction de `ScannerDialog._onDetect`) **répare aussi le QR**, qui aujourd'hui n'ouvre jamais l'app depuis l'appareil photo. Plus `IsNFCEnabled` calqué sur `IsQRCodeEnabled`. ⚠️ **Prérequis dur : déployer `app.myinfomate.be`**, qui doit servir les `.well-known` |
| Canal subsides | [plan-import-ia-stats-subsides.md](../plan-import-ia-stats-subsides.md) | Actions administratives/réseau, pas de dev | | Canal subsides | [plan-import-ia-stats-subsides.md](../plan-import-ia-stats-subsides.md) | Actions administratives/réseau, pas de dev |
--- ---

View File

@ -1,43 +1,104 @@
# Tags NFC comme déclencheurs de section # Tags NFC — déclencher le contenu depuis un tag
> Chantier **V2**, **analysé dans le code le 2026-09-16** à partir d'une spec d'intention. > **Analysé dans le code le 2026-09-16.** Carte kanban : `cards/5-planifie/265-…`
> Carte kanban : `cards/5-planifie/265-…` > ⚠️ **Document retourné trois fois** par les précisions de Thomas. Version arrêtée ci-dessous :
> Ce document **corrige la spec d'origine sur deux points de réalité** (le mot « POI », et > le tag ouvre **l'app mobile sur le bon contenu**, et la page de téléchargement web reste
> le schéma d'URL avec table de mapping), puis dit comment on le fait. > le fallback quand l'app n'est pas installée.
--- ---
## Ce que la spec d'origine supposait, et ce que dit le code ## Le comportement retenu
| Hypothèse de la spec | Réalité relevée | ```
|---|---| tag NFC (NDEF : URL du QR)
| « Déclencher un **POI** » | Dans MyInfoMate, un **POI est un point posé sur un GLB** (3D/VR — voir `immersif-frontiere-plan.md` §9). Le déclencheur physique d'un contenu, c'est une **`Section`** : `Section.IsBeacon` / `Section.BeaconId` (`Section.cs:57`), lue côté visiteur par `geo_beacon_trigger_service.dart:177` et `configuration_page.dart:205`. Le NFC se branche **au même endroit que le beacon**, pas ailleurs |
| « Réutiliser l'endpoint de résolution des QR, s'ils utilisent déjà une URL de ce format » | **Il n'y en a aucun.** Les QR portent l'id de section **directement dans l'URL**, et trois formats coexistent sur le terrain (`ScannerDialog.dart:31-38`) : `app.myinfomate.be/download/{instanceId}/{configId}/{sectionId}`, `web.mymuseum.be/{instanceId}/{configId}/{sectionId}` (déjà imprimé, MDLF), et un **id de section brut** (déjà imprimé, Fort Saint-Héribert). Il n'y a donc rien à réutiliser : l'entité « tag », le code opaque et l'endpoint `code → section` seraient **du neuf intégral** | ├─ app installée → App Link / Universal Link → mymuseum-visitapp s'ouvre sur la section
| « Déclarer les URLs en Universal Links / App Links » | **Rien n'existe.** Aucun `assetlinks.json` ni `apple-app-site-association` sur nos domaines (le seul du workspace est dans `OpenGlasses`, qui n'est pas à nous). `app_links` est bien au `pubspec.yaml:89` mais branché **uniquement sur le retour de deep link Meta AI** (`main.dart:69``MetaGlassesService.handleDeepLink`), et `AndroidManifest.xml` n'a pas d'intent-filter `autoVerify` sur un domaine http |
| « Si l'app n'est pas installée, l'URL affiche une page web utile » | `visitapp-web` fait exactement ça (`app.myinfomate.be/[slug]`), mais **il n'est pas déployé** — il manque le Dockerfile et l'entrée dans le compose (carte « Déployer le visiteur web »). Le fallback est une **dépendance**, pas un acquis | └─ app absente → app.myinfomate.be/download/{instanceId}/{configId}/{sectionId}
→ page « installez l'application » ← comportement actuel, conservé
```
**Le tag porte exactement l'URL du QR.** Pas d'entité `NfcTag`, pas de code opaque, pas
d'endpoint de résolution, pas de colonne sur `Section`. Un tag NFC est un QR code sans caméra —
c'est ce qui rend ce chantier petit.
**Et il n'y a aucun code NFC à écrire pour le cas nominal.** Sur iOS (iPhone XS+) comme sur
Android, l'OS lit le tag NDEF, affiche une bannière et **ouvre l'URL** ; c'est le lien universel
qui aiguille vers l'app. `nfc_manager`, la permission NFC et l'entitlement Core NFC ne servent
qu'à la lecture **in-app** — utile seulement pour le bouton « Scanner un tag » du lot 4.
--- ---
## Décisions ## Ce qui manque vraiment : la colle
| Décision | Conséquence | ### 1. Les liens universels n'existent pas — c'est le cœur du chantier
|---|---|
| **1. Le tag porte la même URL que le QR** | Pas d'entité `NfcTag`, pas de code opaque, pas d'endpoint de résolution, pas de CRUD manager. Le mapping tag → section, c'est l'URL écrite sur le tag. **Zéro ligne de backend pour la v1** |
| **2. L'indirection `code → section` est reportée, pas refusée** | L'argument « réassigner sans réécrire le tag » est réel, mais il vaut **beaucoup moins en NFC qu'en QR** : réécrire un NTAG prend deux secondes avec un téléphone, alors qu'un QR se réimprime, se replastifie et se recolle. On ouvrira l'indirection le jour où un client gère un parc de tags — et ce jour-là elle devra couvrir **QR et NFC ensemble**, pas le NFC seul |
| **3. Le lot utile est celui des liens, pas celui du NFC** | Universal Links + App Links + routage du deep link entrant vers la section. ✅ **Le QR en bénéficie autant** : aujourd'hui un QR scanné par l'appareil photo natif n'ouvre pas l'app, il ouvre le navigateur. C'est ce lot qui porte la valeur, le NFC n'en est qu'un second émetteur |
| **4. Lecture in-app en fallback seulement** | `nfc_manager` derrière le bouton Scanner existant (`ScannerBouton.dart`), à côté du QR. Le cas nominal reste **le tag lu sans ouvrir l'app** — c'est tout l'intérêt du canal |
| **5. Écran de programmation des tags : Android-only** | L'écriture NDEF depuis iOS est trop contrainte pour un outil de terrain, et le public visé est **l'admin qui pose les tags**, pas le visiteur. Un seul appareil de programmation suffit à un lieu |
| **6. Verrouillage en écriture : hors v1** | Irréversible, donc une erreur de pose coûte le tag. À ouvrir après un vrai déploiement, avec double confirmation |
| **7. Pas de lecture par UID** | Confirmé : l'UID n'est pas réassignable et impose une table de correspondance — donc tous les inconvénients de la décision 2 sans son bénéfice |
--- Aucun `assetlinks.json` (Android) ni `apple-app-site-association` (iOS) n'est servi sur nos
domaines. Le seul du workspace est dans `OpenGlasses`, qui n'est pas à nous.
## Ce qui ne marchera pas — à savoir avant de le vendre ⚠️ **Ils doivent être servis par le domaine de l'URL du tag**, donc par `app.myinfomate.be`
qui **n'est pas déployé** (absent de `docker-compose-myinfomate.yml`, qui route `myinfomate.be`,
`api.`, `manager.` et `monitor.`). La carte « Déployer le visiteur web », déjà `critical`, reste
un **prérequis dur**.
- **Aucune lecture NFC sur `visitapp-web`.** WebNFC est Chrome/Android uniquement, jamais Safari. Le plan **Essentiel étant web-only**, le NFC ne s'y vend pas comme « scan dans l'app » — il s'y vend comme « on approche le téléphone, une page s'ouvre », ce qui est vrai et ne demande rien de plus que la décision 1. À réunir avant d'écrire les fichiers : le **SHA-256 de la clé de signature de release** Android
- **iPhone 7 / 8 / X** ne lisent pas les tags en arrière-plan : il leur faut l'app ouverte. iPhone **XS et plus** lisent un NDEF sans aucune app installée — c'est l'argument commercial réel, et il est bon. (l'APK se construit par `tool/build_apk.sh`) et le **Team ID + bundle id** iOS. Une empreinte
- **Un tag à plat sur du métal ne se lit pas.** Prévoir des tags sur mousse ou des NTAG « on-metal » ; le raccourci du bois transparent au BLE (carte beacons) n'a pas d'équivalent ici. fausse ne produit aucune erreur visible — le lien s'ouvre simplement dans le navigateur.
- **Le NFC ne remplace pas le beacon** : il demande un geste volontaire à 1-2 cm, il ne déclenche rien à l'approche. Les trois déclencheurs se cumulent, ils ne se substituent pas.
### 2. L'app n'écoute pas les deep links entrants
`app_links` est bien au `pubspec.yaml:89`, mais :
```dart
void _listenGlassesDeepLinks() {
if (!kEnableGlasses || !Platform.isAndroid) return; // ← main.dart:68
_deepLinkSub = AppLinks().uriLinkStream.listen(
(uri) => MetaGlassesService.instance.handleDeepLink(uri.toString()),
);
}
```
Tout part vers `MetaGlassesService`, le listener est **coupé sur iOS** et conditionné à
`kEnableGlasses`. Il faut router **par motif d'URL**, sans casser le retour de registration Meta.
### 3. La résolution d'un code est enfermée dans le dialogue du scanner
`ScannerDialog._onDetect` (`ScannerDialog.dart:107`) fait déjà **tout** ce qu'il faut : parsing
des trois formats de QR du terrain, section appartenant ou non à la visite en cours,
`_findConfigurationIdOfSection` quand l'id est brut, proposition d'ouvrir une **autre visite** du
site, et télémétrie. Mais c'est un `State` : la méthode manipule `context`, `Navigator`,
`ScaffoldMessenger`, et capture le navigator **avant** le `pop` pour survivre au démontage.
**Extraire cette résolution dans un service** est le vrai morceau de travail — et il **profite
au QR autant qu'au NFC**. À faire sans perdre les cas limites, qui sont tous des corrections de
bugs réels documentées en commentaires dans le fichier.
### 4. `IsNFCEnabled`, sur le modèle exact de `IsQRCodeEnabled`
`ApplicationInstance.IsQRCodeEnabled` (`ApplicationInstance.cs:57`, défaut `true`) est piloté par
un switch dans `app_configuration_link_screen.dart:400` et consommé là où le bouton scanner
s'affiche : `home_3.0.dart:651`, `configuration_page.dart:433`, `menu_page.dart:302`, et
`visitapp-web` (`[slug]/page.tsx:44`). On ajoute `IsNFCEnabled` au même endroit, même défaut,
même switch.
⚠️ **Ce que le flag pilote, et ce qu'il ne pilote pas.** Il masque le **bouton « Scanner un
tag »** in-app et sert de déclaration commerciale. Il **ne peut pas** empêcher un tag posé au mur
d'ouvrir l'app : le deep link vient de l'OS, et l'app le reçoit sans savoir s'il vient d'un tag
ou d'un QR. Ne pas lui prêter un rôle de verrou qu'il n'aura pas.
⚠️ `Configuration.IsQRCode` (`Configuration.cs:52`) existe aussi mais **n'est lu par aucune app**
code mort. Ne pas dupliquer l'erreur en posant un flag NFC au niveau `Configuration`.
### 5. Sans quoi le NFC sera compté comme du QR dans les stats
`_onDetect` émet `VisitEventType.qrScan`. Un tag qui ouvre l'app par deep link n'a aucun moyen de
se distinguer — sauf à ajouter un paramètre à l'URL encodée sur le tag (`?src=nfc`) et une valeur
`NfcScan` à l'enum (`VisitEvent.cs:48`). ⚠️ L'enum passe par le client généré de
`manager_api_new`, **à éditer à la main**. Sans ça, le canal NFC est invisible dans les stats et
on ne saura jamais s'il sert.
*(À traiter avec :* la page de téléchargement dit « scannez à nouveau le QR code depuis
l'application » — faux pour un tag. Le `?src=nfc` permet aussi d'adapter ce texte.*)*
--- ---
@ -45,10 +106,23 @@
| Lot | Contenu | Estimation | | Lot | Contenu | Estimation |
|---|---|---| |---|---|---|
| **A — Liens universels** (le seul indispensable) | `assetlinks.json` + `apple-app-site-association` servis sur le domaine, intent-filter `autoVerify` + capability Associated Domains, routage du deep link entrant vers la section (logique partagée avec le parsing de `ScannerDialog`, à extraire du dialogue). **Bénéficie au QR dès la livraison** | 2-3 j | | **0 — Déployer `app.myinfomate.be`** | Carte existante, déjà `critical`. **Prérequis dur** : c'est ce domaine qui sert les `.well-known` | (carte 210) |
| **B — NFC** | Permission et intent-filter NDEF Android, capability NFC Tag Reading + `NFCReaderUsageDescription` + entitlement iOS, `nfc_manager` derrière le bouton Scanner | 1-2 j | | **1 — Liens universels** | `assetlinks.json` + `apple-app-site-association` servis sur `app.myinfomate.be`, intent-filter `autoVerify` Android, Associated Domains iOS. Demande le SHA-256 de la clé de release et le Team ID | **1 j** |
| **C — Écran de programmation admin** | Choisir une section → approcher un tag → écrire l'URL NDEF. Android-only, NTAG213/215/216 | 1-2 j | | **2 — Router le deep link entrant** | Découpler `main.dart:68` de `MetaGlassesService`, activer le listener sur iOS, router par motif | **0,5 j** |
| **D — Fallback web** | Rien à écrire : c'est le déploiement de `visitapp-web`. **Dépendance externe** | — | | **3 — Extraire la résolution du scanner** | Sortir `_onDetect` de `ScannerDialog` vers un service, sans perdre les cas limites. ✅ **Profite au QR** | **1,5 j** |
| *(reporté)* **E — Indirection `code → section`** | Entité, CRUD manager, endpoint de résolution — et alors **pour QR et NFC ensemble** | 3-4 j | | **4 — `IsNFCEnabled`** | Colonne + DTO + client généré à la main + switch manager + i18n FR/EN/NL, et le bouton « Scanner un tag » qu'il pilote (`nfc_manager`, permission Android, entitlement iOS `NDEF`) | **1 j** |
| **5 — Distinguer le canal** | `?src=nfc` sur l'URL encodée, `VisitEventType.NfcScan`, texte de la page de téléchargement | **0,5 j** |
| **6 — Encoder les tags** | **NFC Tools**, 2 s par tag, une URL par point. Geste d'exploitation | **0 j de dev** |
**Total v1 (A+B+C) : 4-7 jours**, dont A porte à lui seul la moitié de la valeur et sert déjà au QR. **Total : 4,5 j**, dont les lots 2 et 3 **réparent aussi le QR** — aujourd'hui un QR scanné à
l'appareil photo n'ouvre jamais l'app, même installée.
---
## À savoir avant de le vendre
- **iPhone XS et plus** lisent un tag sans aucune app. Les **iPhone 7 / 8 / X**, non : il leur faut une app ouverte. Android : le NFC doit être activé dans les réglages, ce qui n'est pas universel.
- **L'ouverture n'est pas automatique** : l'OS affiche une **bannière que le visiteur tape**. C'est l'expérience de l'appareil photo sur un QR, pas mieux.
- **Un tag à plat sur du métal ne se lit pas** — tags sur mousse ou NTAG « on-metal ». NTAG213 (144 o) suffit largement pour l'URL.
- **Aucune lecture NFC sur `visitapp-web`** : WebNFC est Chrome/Android uniquement, jamais Safari. Le plan **Essentiel étant web-only**, le NFC ne s'y vend pas — sauf comme « le tag ouvre la page web », qui reste vrai et gratuit.
- **Le NFC ne remplace pas le beacon** : geste volontaire à 1-2 cm, aucun déclenchement à l'approche. Les déclencheurs se cumulent.