Files
nav-carte/V2-cadrage/E-spec-frontend.md
2026-04-28 14:00:05 +02:00

50 KiB
Raw Permalink Blame History

NAV V2 — Spec front-end

Date : 2026-04-14 Auteur : ATIS agent Version : 1.0


Sommaire

1. Wireframes ASCII
   1.1 Page accueil desktop
   1.2 Page accueil mobile
   1.3 Page fiche /fiche/[id] desktop
   1.4 Page fiche /fiche/[id] mobile
   1.5 Bottom-sheet chatbot mobile (ouvert)
   1.6 Bottom-sheet chatbot desktop (ouvert)
   1.7 Modale formulaire "Ajouter une fiche"
   1.8 Bandeau bas
2. Routes Nuxt
3. Composants à créer
4. States & data flow
5. Responsive breakpoints
6. Détails chatbot (bottom-sheet)
7. Bandeau bas : données affichées
8. Page fiche détaillée
9. Formulaire "Ajouter une fiche"
10. Accessibilité & sobriété

1. Wireframes ASCII

1.1 Page accueil — desktop (> 1024px)

┌──────────────────────────────────────────────────────────────────────────┐
│  NAV                                            [Contribuer une fiche +] │
│  Navigateur Architecture                                                  │
├───────────────────┬──────────────────────────────────────────────────────┤
│  SIDEBAR (320px)  │  CARTE CENTRALE                                      │
│                   │                                                       │
│  [  Recherche  🔍]│  ┌─────────────────────────────────────────────────┐ │
│                   │  │  [Métropole] [Outre-mer ▼]          Carte Leaflet│ │
│  ── ÉCHELLE ──    │  │                                                   │ │
│  ○ National       │  │   · point carte     · point                      │ │
│  ● Régional       │  │        · point   ·                                │ │
│  ○ Départemental  │  │                        · point sélectionné ●     │ │
│  ○ Local          │  │   ·          ·                                    │ │
│                   │  │        ·           ·                              │ │
│  ── FONCTION ──   │  │                                                   │ │
│  [Juridique    ×] │  │   [  +  ]   [-]   [⟳]           (OSM © contrib.) │ │
│  [Technique    ×] │  └─────────────────────────────────────────────────┘ │
│  [Économique    ] │                                                       │
│  [Administratif ] │  Résultats : 23 fiches                               │
│  [Chantier      ] │  ┌──────────────────┐  ┌──────────────────┐         │
│  [Comptabilité  ] │  │ FicheCard        │  │ FicheCard        │         │
│  [Prospection   ] │  │ Nom organisation │  │ Nom organisation │         │
│  [RH            ] │  │ Échelle · Fonct. │  │ Échelle · Fonct. │         │
│  [Santé mentale ] │  │ Ville            │  │ Ville            │         │
│                   │  └──────────────────┘  └──────────────────┘         │
│  23 résultats     │                                                       │
├───────────────────┴──────────────────────────────────────────────────────┤
│  BandeauBas                                                               │
│  Ce mois-ci : 0,12€ / 8K tokens / ~0,001 kWh   [♥ Soutenir NAV]   Semaine : 3 fiches / 12 requêtes │
└──────────────────────────────────────────────────────────────────────────┘
                                                     [🤖 Aide IA]  ← BD

1.2 Page accueil — mobile (< 768px)

┌─────────────────────────────┐
│  NAV                   [≡]  │
├─────────────────────────────┤
│  [Métropole] [Outre-mer ▼]  │
├─────────────────────────────┤
│                             │
│  Carte Leaflet              │
│  (plein écran, ~60vh)       │
│                             │
│  · point   · point          │
│       ·  ●sélect.           │
│  ·                 ·        │
│           ·                 │
│  [+] [-]           (OSM ©)  │
│                             │
├─────────────────────────────┤
│  [Filtres ▼]  23 résultats  │
├─────────────────────────────┤
│  ┌─────────────────────────┐│
│  │ FicheCard               ││
│  │ Nom organisation        ││
│  │ [Régional] [Juridique]  ││
│  │ Paris                   ││
│  └─────────────────────────┘│
│  ┌─────────────────────────┐│
│  │ FicheCard               ││
│  └─────────────────────────┘│
│  ┌─────────────────────────┐│
│  │ FicheCard               ││
│  └─────────────────────────┘│
│                             │
├─────────────────────────────┤
│ Ce mois-ci : 0,12€ [♥ Don] │
└─────────────────────────────┘
                  [🤖] ← BD

Drawer sidebar mobile — déclenché par [≡] :

┌─────────────────────────────┐
│  ✕  Filtres                 │
│                             │
│  Recherche : [            ] │
│                             │
│  ÉCHELLE                    │
│  ○ National                 │
│  ● Régional                 │
│  ○ Départemental            │
│  ○ Local                    │
│                             │
│  FONCTION (15)             │
│  ☑ Juridique    ☐ Technique │
│  ☐ Économique   ☐ Admin     │
│  ☐ Chantier     ☐ Compta    │
│  ☐ Prospection  ☐ RH        │
│  ☐ Santé mentale            │
│                             │
│  [  Appliquer les filtres ] │
└─────────────────────────────┘

1.3 Page fiche /fiche/[id] — desktop

┌──────────────────────────────────────────────────────────────────────────┐
│  NAV                                            [Contribuer une fiche +] │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                           │
│  ← Retour carte          nav.trans-former.fr/fiche/[id]                  │
│                                                                           │
│  ┌──────────────────────────────────────────┬──────────────────────────┐ │
│  │  HEADER FICHE                            │  Mini-carte (250px)      │ │
│  │  Nom de l'organisation                   │                          │ │
│  │  [Régional] [Juridique] [Technique]      │  · localisation          │ │
│  │  ↳ Paris | Site : architectes-idf.org   │                          │ │
│  │                                          │  (Leaflet, zoom 10,      │ │
│  │  Description courte (contributeur)       │   non interactif)        │ │
│  │  ──────────────────────────────────      │                          │ │
│  │  Description enrichie IA                 └──────────────────────────┘ │
│  │  (scraping + synthèse Mistral)                                        │ │
│  │                                                                       │ │
│  │  Points clés :                                                        │ │
│  │  • [point clé 1]                                                      │ │
│  │  • [point clé 2]                                                      │ │
│  │  • [point clé 3]                                                      │ │
│  │                                                                       │ │
│  │  [→ Visiter le site]                                                  │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│                                                                           │
│  COMMENTAIRES (3)                                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │  [Avatar] Prénom A. — 2026-03-01                                     │ │
│  │  "Très utile pour les questions de droit du travail en IDF."        │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │  [Avatar] Prénom B. — 2026-03-14                                     │ │
│  │  "Permanences juridiques réactives, réponse en 48h."               │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│                                                                           │
│  Ajouter un commentaire :                                                 │
│  [                                              ]  (max 500 car.)        │
│  Pseudo (optionnel) : [            ]    [Envoyer]                        │
│  → Vos commentaires sont filtrés par une IA avant publication.           │
│                                                                           │
├──────────────────────────────────────────────────────────────────────────┤
│  BandeauBas                                                               │
│  Ce mois-ci : 0,12€ / 8K tokens   [♥ Soutenir NAV]   3 fiches / 12 req │
└──────────────────────────────────────────────────────────────────────────┘
                                                     [🤖 Aide IA]

1.4 Page fiche /fiche/[id] — mobile

┌─────────────────────────────┐
│  ← Retour        NAV   [≡] │
├─────────────────────────────┤
│  Nom de l'organisation      │
│  [Régional] [Juridique]     │
│  [Technique]                │
│  Paris | architectes-idf.fr │
├─────────────────────────────┤
│  Mini-carte (200px height)  │
│  · localisation             │
├─────────────────────────────┤
│  Description courte         │
│  ─────────────────────────  │
│  Description enrichie IA    │
│                             │
│  Points clés :              │
│  • point clé 1              │
│  • point clé 2              │
│  • point clé 3              │
│                             │
│  [→ Visiter le site]        │
├─────────────────────────────┤
│  COMMENTAIRES (3)           │
│  ─────────────────────────  │
│  Prénom A. — 2026-03-01     │
│  "Très utile pour les..."   │
│  ─────────────────────────  │
│  Ajouter un commentaire :   │
│  [                        ] │
│  [Envoyer]                  │
├─────────────────────────────┤
│ Ce mois-ci : 0,12€  [♥ Don]│
└─────────────────────────────┘
                  [🤖] ← BD

1.5 Bottom-sheet chatbot — mobile (ouvert)

┌─────────────────────────────┐
│  (carte visible en fond)    │
│  (opacifiée 30%)            │
│                             │
│                             │
├─────────────────────────────┤  ← drag handle
│  🤖 Aide IA     ✕ Fermer   │
│                             │
│  ┌─────────────────────────┐│
│  │ Ce chatbot fonctionne   ││
│  │ sur un serveur EU       ││
│  │ souverain (Mistral FR,  ││
│  │ zéro rétention).        ││
│  │                         ││
│  │ Formule ta requête :    ││
│  │ • Besoin : [ce que...]  ││
│  │ • Thématique : [...]    ││
│  │ • Lieu : [région/ville] ││
│  │                         ││
│  │ Exemple : "Salarié      ││
│  │ d'agence, litige        ││
│  │ employeur, juridique    ││
│  │ droit du travail, IDF." ││
│  └─────────────────────────┘│
│                             │
│  ┌─────────────────────────┐│
│  │ (historique messages)   ││
│  │                         ││
│  │                         ││
│  └─────────────────────────┘│
│                             │
│  [  Écris ta question...  ] │
│  [               Envoyer →] │
└─────────────────────────────┘

1.6 Bottom-sheet chatbot — desktop (ouvert)

┌──────────────────────────────────────────────────────────────────────────┐
│  (carte + sidebar en fond, opacifiée 20%)                                │
│                                                                           │
│                                                                           │
├──────────────────────────────────────────┬───────────────────────────────┤
│                                          │  🤖 Aide IA          ✕        │
│                                          │                               │
│                                          │  ┌─────────────────────────┐ │
│                                          │  │ Ce chatbot fonctionne   │ │
│                                          │  │ sur un serveur EU       │ │
│                                          │  │ souverain (Mistral FR,  │ │
│                                          │  │ zéro rétention).        │ │
│                                          │  │                         │ │
│                                          │  │ Formule ta requête :    │ │
│                                          │  │ • Besoin :              │ │
│                                          │  │ • Thématique :          │ │
│                                          │  │ • Lieu :                │ │
│                                          │  └─────────────────────────┘ │
│                                          │                               │
│                                          │  (historique messages)        │
│                                          │                               │
│                                          │  [ Écris ta question...     ] │
│                                          │  [                  Envoyer →] │
├──────────────────────────────────────────┴───────────────────────────────┤
│  BandeauBas                                                               │
└──────────────────────────────────────────────────────────────────────────┘

Note : sur desktop, le chatbot s'ouvre en panneau latéral droit (400px) plutôt qu'en bottom-sheet plein écran. Même logique UX, adaptation de surface.

1.7 Modale formulaire "Ajouter une fiche"

┌──────────────────────────────────────────────────────────────┐
│  Proposer une ressource              ✕ Fermer                │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  Nom de l'organisation *                                     │
│  [                                                      ]    │
│                                                              │
│  URL du site (optionnel, recommandé)                         │
│  [  https://...                                         ]    │
│                                                              │
│  Description courte * (max 200 caractères)                   │
│  [                                                           │
│                                                         ]    │
│  0/200                                                       │
│                                                              │
│  Échelle * (une seule)                                       │
│  ○ National   ○ Régional   ○ Départemental   ○ Local         │
│                                                              │
│  Fonctions * (1 à 5, l'ordre de clic = priorité)            │
│  ☐ Juridique       ☐ Technique     ☐ Économique              │
│  ☐ Administratif   ☐ Chantier      ☐ Comptabilité            │
│  ☐ Prospection     ☐ RH            ☐ Santé mentale           │
│                                                              │
│  Territoire *                                                │
│  ☑ Métropole   ☐ Guadeloupe   ☐ Martinique                   │
│  ☐ Guyane      ☐ Réunion      ☐ Mayotte                      │
│                                                              │
│  Ville (pour géolocalisation) *                              │
│  [                          ]                                │
│                                                              │
│  Votre email (optionnel — pour le suivi de modération)       │
│  [                          ]                                │
│                                                              │
│  ────────────────────────────────────────────────────────    │
│  [         Annuler         ]  [   Proposer la fiche →   ]    │
└──────────────────────────────────────────────────────────────┘

Message post-submit (remplace le contenu de la modale) :

┌──────────────────────────────────────────────────────────────┐
│  Merci !                                  ✕ Fermer           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  Ta fiche est en cours de traitement.                        │
│                                                              │
│  Une IA va scraper le site et enrichir la description.       │
│  Jules (et bientôt une équipe de modération) valide          │
│  sous 7 jours.                                               │
│                                                              │
│  Tu peux suivre l'avancement ici :                           │
│  nav.trans-former.fr/suivi/[token-temp]                     │
│                                                              │
│  [   Fermer   ]                                              │
└──────────────────────────────────────────────────────────────┘

1.8 Bandeau bas

┌──────────────────────────────────────────────────────────────────────────┐
│  Ce mois-ci : 0,12€ consommés       [♥ Soutenir NAV]    Cette semaine : │
│  8 423 tokens · ~0,001 kWh                               3 nouvelles fiches │
│                                                          12 requêtes chatbot │
└──────────────────────────────────────────────────────────────────────────┘

Sur mobile (< 768px) : bandeau réduit à 1 ligne, seul le bouton don et le résumé très court.

┌─────────────────────────────┐
│  0,12€ · 8K tok   [♥ Don]  │
└─────────────────────────────┘

2. Routes Nuxt

/                       → page accueil (carte + sidebar + liste fiches)
/fiche/[id]             → page fiche détaillée (SSR, OG meta, partage)
/a-propos               → page souveraineté + gouvernance + crédits
/contribuer             → formulaire soumission fiche (page dédiée mobile) ou
                          redirige vers / avec ouverture modale (desktop)
/suivi/[token]          → page de suivi statut modération (token temp)

/api/search             → POST : recherche IA (chatbot) → réponse Mistral Small
/api/submit             → POST : soumission nouvelle fiche → NocoDB + worker
/api/comment            → POST : ajout commentaire → filtre éthique → NocoDB
/api/stats              → GET  : récupération données bandeau bas (stats_usage)
/api/geocode            → POST : ville → coordonnées (via Nominatim, gratuit)

Notes :

  • /contribuer route duale : en mobile elle rend une page dédiée pour l'UX formulaire ; en desktop elle redirige vers / avec un query param ?contribute=1 qui ouvre la modale automatiquement.
  • /suivi/[token] : page légère affichant le statut de la fiche soumise (en attente / enrichie IA / validée / rejetée).
  • Toutes les routes /api/* sont des server routes Nuxt (dossier server/api/).

3. Composants à créer

3.1 Composants de layout

AppHeader.vue

  • Rôle : barre de navigation principale (logo + bouton "Contribuer")
  • Props : aucune (état global géré par store)
  • Slots : aucun
  • Note : sticky, z-index au-dessus de la sidebar

BandeauBas.vue

  • Rôle : bandeau bas fixe avec stats coûts/tokens/carbone, bouton don, stats activité
  • Props : stats: StatsUsage (objet issu de /api/stats)
  • Émettre : rien (lecture seule)
  • Note : hauteur fixe 48px desktop, 36px mobile ; position: fixed; bottom: 0

3.2 Sidebar & filtres

NavSidebar.vue

  • Rôle : sidebar gauche avec recherche texte + sélection ÉCHELLE + sélection FONCTION
  • Props :
    • modelValue: Filters (v-model)
    • resultCount: number
  • Émet : update:modelValue à chaque changement de filtre
  • Contient : SearchInput, EchelleFilter, FonctionFilter
  • Note : sur mobile, ce composant est rendu dans DrawerSidebar

DrawerSidebar.vue

  • Rôle : enveloppe modale/drawer pour la sidebar en mobile
  • Props : open: boolean
  • Émet : close
  • Contient : NavSidebar

EchelleFilter.vue

  • Rôle : ligne de filtres radio ÉCHELLE (mono-sélection obligatoire)
  • Props :
    • modelValue: string | null (valeur sélectionnée)
    • options: string[] (National / Régional / Départemental / Local)
  • Émet : update:modelValue
  • Note : le premier click sur une valeur déjà sélectionnée la désélectionne (équivalent "tout")

FonctionFilter.vue

  • Rôle : grille de tags FONCTION (multi-sélection 05, ordre de clic = priorité)
  • Props :
    • modelValue: string[] (tableau ordonné par priorité)
    • options: string[]
    • maxSelected: number (défaut: 5)
  • Émet : update:modelValue
  • Note : afficher le rang (1, 2, 3...) sur les tags sélectionnés

SearchInput.vue

  • Rôle : champ de recherche textuelle (filtre côté client ou appel API)
  • Props :
    • modelValue: string
    • placeholder: string
  • Émet : update:modelValue, search (sur Enter ou debounce 300ms)

3.3 Carte

NavMap.vue

  • Rôle : carte Leaflet avec markers, clustering, synchronisation avec filtres sidebar
  • Props :
    • orgs: Organisation[] (fiches filtrées)
    • territoireActif: string (Métropole | Guadeloupe | Martinique | Guyane | Réunion | Mayotte)
    • highlightedIds: string[] (fiches à mettre en avant, émises par chatbot)
    • selectedId: string | null
  • Émet :
    • selectOrg(id: string) — clic sur un marker
    • mapMoved(bounds: LatLngBounds) — si on veut filtrer par bbox à terme
  • Note : utiliser leaflet.markercluster pour le clustering des points proches
  • Note SSR : Leaflet est client-only (<ClientOnly> ou plugin avec process.client)

TerritoireToggle.vue

  • Rôle : toggle Métropole / Outre-mer avec sous-onglets DOM-TOM si Outre-mer actif
  • Props :
    • modelValue: string (territoire actif)
    • territoires: string[] (Métropole + 5 DOM-TOM)
  • Émet : update:modelValue
  • Note : quand Outre-mer sélectionné, afficher un second rang de sous-onglets (Guadeloupe, Martinique, Guyane, Réunion, Mayotte). La carte recentre automatiquement sur le territoire sélectionné.

MarkerPopup.vue

  • Rôle : popup Leaflet affichée au clic sur un marker (mini-fiche)
  • Props : org: Organisation
  • Contient : nom, tags échelle/fonction, lien vers /fiche/[id]
  • Note : composant Vue rendu dans le popup Leaflet via teleport ou mounting manuel

3.4 Fiches

FicheCard.vue

  • Rôle : carte de résumé d'une fiche dans la liste sidebar
  • Props :
    • org: Organisation
    • selected: boolean
  • Émet : select(id: string)
  • Contient : TagBadge pour échelle et fonctions

TagBadge.vue

  • Rôle : badge visuel pour un tag (échelle ou fonction)
  • Props :
    • label: string
    • type: 'echelle' | 'fonction'
    • priority?: number (rang si fonction prioritaire)
    • active?: boolean
  • Note : couleurs distinctes entre les deux types de tags

FicheHeader.vue

  • Rôle : header de la page fiche (nom + tags + lien retour + meta)
  • Props : org: Organisation

FicheDescription.vue

  • Rôle : corps de la page fiche (description user + description IA + points clés)
  • Props :
    • descriptionUser: string
    • descriptionIA: string | null
    • pointsCles: string[]
    • siteUrl: string | null
  • Note : si descriptionIA est null (fiche pas encore traitée), afficher un placeholder "Enrichissement IA en cours..."

FicheMiniMap.vue

  • Rôle : mini-carte Leaflet centrée sur la localisation d'une fiche
  • Props :
    • lat: number
    • lng: number
    • nom: string
  • Note : non interactif (scrollWheelZoom: false, dragging: false), zoom fixe 10

CommentsList.vue

  • Rôle : liste des commentaires validés d'une fiche
  • Props : comments: Comment[]
  • Contient : CommentItem

CommentItem.vue

  • Rôle : affichage d'un commentaire individuel
  • Props : comment: Comment (pseudo, date, texte)

CommentForm.vue

  • Rôle : formulaire d'ajout de commentaire
  • Props : ficheId: string
  • Émet : submitted
  • Note : affiche un message de confirmation post-soumission avec mention du filtre IA

3.5 Chatbot

ChatbotBubble.vue

  • Rôle : bubble fixe bottom-right déclenchant l'ouverture du chatbot
  • Props : open: boolean
  • Émet : toggle
  • Note : icône robot + label "Aide IA" sur desktop, icône seule sur mobile

ChatbotSheet.vue

  • Rôle : bottom-sheet chatbot (mobile) / panneau latéral droit (desktop)
  • Props :
    • open: boolean
    • messages: ChatMessage[]
    • loading: boolean
  • Émet :
    • close
    • sendMessage(text: string)
    • highlightOrgs(ids: string[]) — si la réponse IA mentionne des fiches
  • Contient : ChatOnboarding, ChatMessageList, ChatInput

ChatOnboarding.vue

  • Rôle : message initial affiché avant la première interaction
  • Props : aucune
  • Note : texte souveraineté + format de requête recommandé (voir section 6)

ChatMessageList.vue

  • Rôle : liste scrollable des messages échangés
  • Props : messages: ChatMessage[]
  • Note : scroll automatique vers le bas à chaque nouveau message

ChatInput.vue

  • Rôle : champ de saisie + bouton envoi
  • Props : loading: boolean
  • Émet : send(text: string)
  • Note : Enter pour envoyer, Shift+Enter pour saut de ligne

3.6 Formulaire soumission

SubmitModal.vue

  • Rôle : modale d'ajout d'une fiche (desktop) ou page /contribuer (mobile)
  • Props :
    • open: boolean (uniquement en mode modale)
  • Émet : close, submitted
  • Contient : SubmitForm

SubmitForm.vue

  • Rôle : formulaire de soumission d'une nouvelle fiche
  • Props : aucune
  • Émet : submitted(submissionId: string)
  • Note : validation côté client avant envoi (champs obligatoires) ; appel /api/submit

SubmitSuccess.vue

  • Rôle : message de confirmation post-soumission
  • Props : trackingUrl: string
  • Note : remplace SubmitForm dans la modale après succès

3.7 Divers

OrgList.vue

  • Rôle : liste des fiches filtrées sous la carte (desktop) ou sous la carte (mobile)
  • Props :
    • orgs: Organisation[]
    • loading: boolean
    • selectedId: string | null
  • Émet : select(id: string)
  • Contient : FicheCard

4. States & data flow

4.1 Architecture du store

Trois stores Pinia :

useFiltersStore       → état des filtres sidebar
useOrgsStore          → données fiches (fetch NocoDB + réactivité)
useChatbotStore       → état chatbot + messages

useFiltersStore :

{
  searchText: string,
  echelle: string | null,        // National | Régional | Départemental | Local | null
  fonctions: string[],           // tableau ordonné (ordre de clic = priorité)
  territoire: string,            // Métropole | Guadeloupe | ...
}

useOrgsStore :

{
  orgs: Organisation[],          // toutes les fiches validées
  filteredOrgs: Organisation[],  // calculé (computed) via getters
  selectedId: string | null,
  loading: boolean,
  highlightedIds: string[],      // fiches mises en avant par le chatbot
}

useChatbotStore :

{
  open: boolean,
  messages: ChatMessage[],
  loading: boolean,
}

4.2 URL comme source de vérité pour les filtres

Les filtres actifs sont reflétés dans l'URL via query params. Cela permet :

  • de partager un lien filtré (ex : /?echelle=Regional&fonctions=Juridique,Technique)
  • de conserver les filtres au rechargement de page
  • de naviguer avec le bouton "retour" du navigateur

Mapping :

?q=           → searchText
?echelle=     → echelle (National | Regional | Departemental | Local)
?fonctions=   → fonctions séparées par virgule, ordre = priorité
?territoire=  → territoire actif

Implémentation : useRoute + useRouter de Nuxt dans useFiltersStore, watchers bidirectionnels store ↔ URL.

4.3 Interaction filtres → carte

Sidebar (EchelleFilter | FonctionFilter | SearchInput)
    ↓ update:modelValue
useFiltersStore.update(filters)
    ↓ watch (computed)
useOrgsStore.filteredOrgs (getter réactif)
    ↓ prop :orgs
NavMap.vue → re-render markers
OrgList.vue → re-render liste

Le filtrage est côté client (toutes les fiches sont chargées au démarrage). Pour des volumes > 500 fiches, envisager un filtrage serveur via /api/search.

4.4 Chatbot → highlight carte

Quand la réponse du chatbot mentionne des organisations (détection par ID ou nom), l'API /api/search renvoie une liste d'IDs. Le chatbot store propage ces IDs :

ChatbotSheet émet highlightOrgs(ids)
    ↓
useChatbotStore → useOrgsStore.setHighlightedIds(ids)
    ↓ prop :highlightedIds
NavMap.vue → markers concernés passent en surbrillance (icône colorée différente)
OrgList.vue → fiches concernées remontées en haut de liste

4.5 Sélection d'une fiche

Clic marker (NavMap)    ──┐
Clic FicheCard (OrgList) ─┤→ useOrgsStore.setSelected(id)
                          │
                          ↓
                    MarkerPopup visible + FicheCard en surbrillance
                    + bouton "Voir la fiche" → navigate('/fiche/[id]')

5. Responsive breakpoints

Breakpoint Taille Comportement
Mobile < 768px Sidebar en drawer (hors-canvas), carte plein écran (~60vh), liste fiches sous la carte, chatbot en bottom-sheet plein largeur, bandeau bas réduit (1 ligne)
Tablet 7681024px Sidebar réduite (220px), carte occupe le reste, chatbot en bottom-sheet 80% largeur centrée, même layout que desktop mais compressé
Desktop > 1024px Sidebar fixe 320px, carte prend tout l'espace restant, chatbot en panneau latéral droit 400px (remonte depuis le bas), bandeau bas 3 colonnes

Grille Tailwind utilisée

/* Mobile first */
.sidebar   { @apply hidden }                          /* < 768 : drawer */
.map       { @apply h-[60vh] w-full }
.org-list  { @apply w-full }

/* Tablet */
@media (min-width: 768px) {
  .sidebar { @apply block w-[220px] flex-shrink-0 }
  .map     { @apply flex-1 h-full }
}

/* Desktop */
@media (min-width: 1024px) {
  .sidebar { @apply w-[320px] }
}

Hauteur disponible

Le layout global est 100dvh (dynamic viewport height) pour gérer les barres mobiles. Structure :

AppHeader (48px) + [Sidebar | Carte + Liste] (flex-1) + BandeauBas (48px)

La carte et la liste scrollent indépendamment dans leur conteneur (overflow-y: auto).


6. Détails chatbot (bottom-sheet)

États

fermé     → ChatbotBubble visible en bas à droite (fixed, z-50)
           icône robot + label "Aide IA" (desktop) ou icône seule (mobile)

ouvert    → ChatbotSheet monte à 70% de la hauteur écran (mobile)
           ou panneau latéral droit 400px (desktop)
           ChatbotBubble disparaît (remplacé par le bouton ✕ dans le sheet)

chargement → spinner dans ChatInput pendant la requête Mistral Small

Animation

/* Mobile : slide-up depuis le bas */
.chatbot-sheet {
  transition: transform 300ms ease-out;
}
.chatbot-sheet[data-open="false"] {
  transform: translateY(100%);
}
.chatbot-sheet[data-open="true"] {
  transform: translateY(0);
}

Respecter prefers-reduced-motion : si activé, supprimer la transition (affichage/masquage instantané).

Drag handle mobile

Le sheet mobile expose un drag handle en haut (barre grise 40px de large, 4px de haut). Le glisser vers le bas ferme le sheet si le déplacement dépasse 30% de la hauteur du sheet.

Message d'onboarding

Affiché avant la première question, disparaît après envoi du premier message :

Ce chatbot fonctionne sur un serveur européen souverain
(Mistral FR, zéro rétention), conçu sobre en énergie.

Pour m'aider à te répondre efficacement,
formule ta requête ainsi :

• Besoin : [ce que tu cherches]
• Thématique : [juridique / technique / économique / ...]
• Lieu : [région ou ville]

Exemple : "Je suis salarié d'agence, litige avec mon
employeur, besoin conseil juridique droit du travail,
Île-de-France."

Prompt système envoyé à Mistral Small (/api/search)

Le prompt système doit :

  1. Présenter le contexte (base de ressources pour architectes FR)
  2. Injecter les fiches filtrées correspondant à la requête (retrieved via Nemo)
  3. Demander de répondre en citant les organisations par ID ou nom exact
  4. Indiquer de ne pas inventer d'organisations absentes de la base

La réponse de l'API inclut les IDs des organisations mentionnées dans un champ structuré (JSON) pour alimenter le highlight de la carte.

Stratégie deux modèles

  • Mistral Nemo (filtre/worker) : enrichissement de fiches post-soumission, filtre éthique commentaires → coût $0,02/$0,04 par 1M tokens
  • Mistral Small (chatbot) : réponses conversationnelles → coût $0,20/$0,60 par 1M tokens

7. Bandeau bas : données affichées

Table NocoDB à créer : stats_usage

Colonne Type Description
id AutoNumber clé primaire
period_start DateTime début de la période (mois ou semaine)
period_type SingleLineText monthly ou weekly
tokens_input Number tokens d'entrée consommés
tokens_output Number tokens de sortie consommés
cost_eur Decimal coût estimé en euros
kwh_estimated Decimal consommation estimée kWh (calcul : ~0,001 kWh / 1000 tokens, à affiner)
chatbot_requests Number nombre de requêtes chatbot
new_fiches Number nouvelles fiches validées
new_comments Number nouveaux commentaires validés
donations_count Number nombre de dons reçus ce mois (Liberapay webhook si disponible)

Calcul kWh et carbone

Formule approximative (à afficher avec mention "estimé") :

kWh = (tokens_input + tokens_output) / 1 000 000 × 0,5 kWh/1M tokens
     (Mistral Nemo sur infrastructure FR, estimation conservative)

CO₂e = kWh × 40 gCO₂e/kWh (mix électrique France, données RTE 2024)

Mise à jour des stats

Chaque appel à /api/search, /api/submit, /api/comment incrémente la table stats_usage via un helper serveur. La route /api/stats retourne l'agrégat du mois courant et de la semaine courante.

Affichage desktop (3 colonnes)

[Gauche]                    [Milieu]              [Droite]
Ce mois-ci :                                      Cette semaine :
0,12 € consommés            [♥ Soutenir NAV]      3 nouvelles fiches
8 423 tokens                (→ Liberapay)         12 req. chatbot
~0,004 kWh · ~0,16 gCO₂e                         2 commentaires

Affichage mobile (1 ligne compressée)

0,12€ · 8K tok    [♥ Soutenir]    3 fiches · 12 req

8. Page fiche détaillée (/fiche/[id])

Données chargées (SSR)

La page est rendue en SSR (Nuxt useFetch côté serveur) pour le SEO et le partage.

Données récupérées depuis NocoDB :

interface Organisation {
  id: string
  nom: string
  url?: string
  description_user: string
  description_ia?: string        // null si pas encore traité
  points_cles?: string[]         // array JSON
  echelle: string                // National | Régional | Départemental | Local
  fonctions: string[]            // tableau ordonné
  territoire: string[]           // Métropole + DOM-TOM
  ville: string
  lat?: number
  lng?: number
  statut: 'pending' | 'enrichi' | 'valide' | 'rejete'
  created_at: string
}

SEO

Balises useHead dans la page /fiche/[id].vue :

useHead({
  title: `${org.nom} — NAV, Navigateur Architecture`,
  meta: [
    { name: 'description', content: org.description_user.slice(0, 160) },
    { property: 'og:title', content: org.nom },
    { property: 'og:description', content: org.description_user.slice(0, 160) },
    { property: 'og:url', content: `https://nav.trans-former.fr/fiche/${org.id}` },
    { property: 'og:type', content: 'article' },
  ]
})

Fil d'Ariane / retour carte

Le bouton "← Retour" utilise router.back() si l'utilisateur vient de la carte, sinon navigateTo('/'). Détecter via document.referrer ou un query param ?from=carte.

Commentaires

Les commentaires sont chargés côté client (pas SSR) pour ne pas bloquer le rendu initial.

Filtre éthique avant publication :

  1. POST /api/comment avec le texte
  2. Appel Mistral Nemo : vérifier absence de contenu offensant, spam, non-pertinent
  3. Si OK → insert NocoDB avec statut: 'pending' (Jules valide manuellement dans un premier temps)
  4. Si KO → message d'erreur explicatif côté client

Message de confirmation post-commentaire :

Merci pour ton commentaire. Il sera publié après validation
(généralement sous 48h).

9. Formulaire "Ajouter une fiche"

Champs et validation

Champ Type Requis Validation
nom text oui min 3 chars, max 150 chars
url url non format URL valide si renseigné
description_user textarea oui min 20 chars, max 200 chars
echelle radio oui une des 4 valeurs
fonctions checkbox oui 1 à 5 sélectionnées
territoire checkbox oui au moins 1
ville text oui min 2 chars (géocodage côté serveur)
email email non format email valide si renseigné

Flow post-submit

SubmitForm → POST /api/submit
    ↓
Server : geocode ville → NocoDB insert (statut: 'pending')
    ↓
Server : trigger worker async → scraping url + enrichissement Mistral Nemo
    ↓
Réponse : { submissionId, trackingUrl }
    ↓
SubmitForm → SubmitSuccess (afficher trackingUrl)

Géocodage

L'API /api/geocode appelle Nominatim (OpenStreetMap, gratuit, usage raisonnable) :

GET https://nominatim.openstreetmap.org/search?q={ville}&format=json&limit=1

Header User-Agent obligatoire : NAV/2.0 contact@trans-former.fr

Fallback : si géocodage échoue, la fiche est stockée sans coordonnées et Jules complète manuellement dans NocoDB.

Worker d'enrichissement IA

Processus asynchrone déclenché après insert NocoDB :

  1. Scraping URL avec node-fetch (ou cheerio pour extraction propre)
  2. Prompt Mistral Nemo :
    Tu enrichis une fiche d'une ressource pour architectes FR.
    Contenu scraped : {contenu}
    Description courte saisie : {description_user}
    
    Produis :
    1. Description enrichie (max 400 caractères, neutre, factuelle)
    2. Points clés : liste de 3 à 5 items (chacun < 80 caractères)
    
    Format JSON :
    { "description_ia": "...", "points_cles": ["...", "..."] }
    
  3. Update NocoDB : description_ia, points_cles, statut: 'enrichi'
  4. Jules valide dans NocoDB → statut: 'valide'

10. Accessibilité & sobriété

Accessibilité

Contraste AA minimum :

  • Tous les textes > 14px : ratio 4,5:1 minimum
  • Textes larges (> 18px bold) : ratio 3:1 minimum
  • Les tags colorés (TagBadge) : vérifier le contraste texte/fond avec un outil (ex. Colour Contrast Analyser)

Navigation clavier :

  • Tous les éléments interactifs ont un tabindex naturel (pas de tabindex > 0)
  • Focus visible sur tous les éléments (ne pas supprimer outline, styliser avec ring Tailwind)
  • Le drawer sidebar (mobile) et la modale SubmitModal implémentent le focus trap : Tab reste dans le composant ouvert
  • Fermeture des modales et drawers sur Escape
  • Le chatbot est accessible au clavier : Tab pour naviguer dans les messages, Enter pour envoyer

Semantic HTML :

  • <nav> pour AppHeader et NavSidebar
  • <main> pour le contenu principal
  • <aside> pour la sidebar
  • <section> pour les grandes zones de la page fiche
  • <h1> unique par page, hiérarchie h2/h3 cohérente

ARIA :

  • aria-label sur les boutons icône (ChatbotBubble, boutons fermeture modales)
  • aria-expanded sur les filtres accordéon (mobile)
  • aria-live="polite" sur la zone de résultats (nombre de fiches) et les messages chatbot
  • role="dialog" + aria-modal="true" sur SubmitModal et DrawerSidebar

Sobriété numérique

Polices :

  • Utiliser les system fonts : font-family: system-ui, -apple-system, sans-serif
  • Pas de Google Fonts, pas de chargement de police externe
  • Si une police spécifique est requise : self-host avec font-display: swap

Images :

  • Pas d'image décorative hero
  • Si logos d'organisations ajoutés à terme : WebP, lazy loading (loading="lazy"), dimensions explicites pour éviter le CLS
  • Pas d'icônes SVG externes : utiliser une bibliothèque locale (Heroicons via @heroicons/vue ou SVG inline)

JavaScript :

  • Leaflet est lourd (~150KB gzippé) : importer uniquement côté client (<ClientOnly> ou import('leaflet') dynamique)
  • Pas de bibliothèques d'animation lourdes (GSAP, etc.) — transitions CSS suffisent

prefers-reduced-motion :

@media (prefers-reduced-motion: reduce) {
  .chatbot-sheet,
  .drawer-sidebar,
  .submit-modal {
    transition: none;
  }
}

Lazy loading des composants lourds :

  • NavMap.vue : importé dynamiquement (defineAsyncComponent) — ne bloque pas le First Contentful Paint
  • ChatbotSheet.vue : importé dynamiquement, monté uniquement au premier clic sur la bubble

Cache des données :

  • Les fiches sont chargées une fois au mount de la page d'accueil, mises en cache dans useOrgsStore
  • Revalidation : au rechargement de page ou après soumission d'une nouvelle fiche
  • Pas de polling — les stats du bandeau se rechargent au mount (pas en temps réel)

Tuiles cartographiques :

  • Utiliser OpenStreetMap (Raster Tiles) via le CDN OpenStreetMap, avec attribution obligatoire
  • Envisager à terme un tile server auto-hébergé (Hetzner) si volumes importants — pas nécessaire au lancement

Types TypeScript partagés

À définir dans types/nav.ts (utilisé par composants et API routes) :

interface Organisation {
  id: string
  nom: string
  url?: string
  description_user: string
  description_ia?: string
  points_cles?: string[]
  echelle: 'National' | 'Régional' | 'Départemental' | 'Local'
  fonctions: string[]
  territoire: string[]
  ville: string
  lat?: number
  lng?: number
  statut: 'pending' | 'enrichi' | 'valide' | 'rejete'
  created_at: string
}

interface Filters {
  searchText: string
  echelle: Organisation['echelle'] | null
  fonctions: string[]
  territoire: string
}

interface ChatMessage {
  id: string
  role: 'user' | 'assistant'
  content: string
  timestamp: number
  highlightedOrgIds?: string[]
}

interface Comment {
  id: string
  ficheId: string
  pseudo?: string
  texte: string
  created_at: string
  statut: 'pending' | 'valide' | 'rejete'
}

interface StatsUsage {
  period_type: 'monthly' | 'weekly'
  cost_eur: number
  tokens_total: number
  kwh_estimated: number
  co2_g: number
  chatbot_requests: number
  new_fiches: number
  new_comments: number
}

Dépendances Nuxt à installer

{
  "dependencies": {
    "leaflet": "^1.9.x",
    "leaflet.markercluster": "^1.5.x",
    "@pinia/nuxt": "^0.5.x",
    "@heroicons/vue": "^2.x",
    "nuxt": "^3.x"
  },
  "devDependencies": {
    "@types/leaflet": "^1.9.x"
  }
}

Note : @mistral-ai/client n'est pas une dépendance front-end — les appels Mistral se font uniquement dans les server routes Nuxt (server/api/), jamais côté client.


Ce qui n'est pas dans cette spec (à traiter séparément)

  • Authentification / espace modération Jules (NocoDB admin ou interface dédiée)
  • Système de notifications email (soumission reçue, fiche validée)
  • Webhooks Liberapay pour mettre à jour donations_count en temps réel
  • Internationalisation (i18n) — non nécessaire au lancement
  • Tests end-to-end (Playwright) — à prévoir pour les parcours critiques (soumission, chatbot)
  • Monitoring et alertes (Sentry, UptimeRobot) — infrastructure, pas front-end
  • Migration des 94 fiches V1 vers le schéma V2 (opération de base de données)