Files
nav-carte/JOURNAL-V2.md
2026-04-28 14:00:05 +02:00

49 KiB
Raw Blame History

type, project, created, status
type project created status
journal NAV V2 2026-04-14 actif

NAV V2 — Journal de développement

Journal technique de la V2. Décisions, anomalies, points bloquants, TODOs.


2026-04-27 — Session V3 : Finition mobile + Blog Liberapay + 3 deploys

Commit : a02a555 — feat(mobile): accordéon outremer, hamburger nav, logo AEP, fiches cliquables, chatbot fullscreen Pattern : agents parallèles (3 × Sonnet) pour les 3 SSH indépendants — ~90s total vs ~20min séquentiel

Changements implémentés

B — OutremerMap.vue : accordéon vertical DOM-TOM

  • Template : row horizontale → accordéon <button> + v-show par territoire
  • Lazy-init Leaflet : initSingleMap(domName) appelé au 1er clic (plus de initMaps() en onMounted)
  • invalidateSize() sur ré-ouverture d'une carte déjà initialisée

E — app.vue : hamburger mobile

  • Bouton lg:hidden tout à droite de <!-- Actions droite -->
  • Dropdown v-if : 5 liens (/, /agences, /rag, /a-propos, /signaler)
  • z-index: 9999 en inline style sur le dropdown
  • watch(() => route.path) → ferme le menu à chaque navigation
  • Fix stacking context : header → relative z-[9999] (sans position, le z-index Tailwind n'avait pas d'effet → dropdown passait sous Leaflet)

F — app.vue : badge "A" → "AEP"

  • w-7 h-7h-7 px-2, text-smtext-xs tracking-tight

G — pages/index.vue : fiches ouvrables mobile

  • onSelectOrgMobile : ajout storeFiltersForBack() + router.push('/fiche/${id}')
  • NB : router.push était déjà là (modif pilote antérieure) — seul storeFiltersForBack ajouté

H — ChatbotSheet.vue : fullscreen + scroll lock iOS

  • 92dvh → 100dvh, border-radius: 16px 16px 0 0 → 0
  • definePropsconst props = defineProps (fix "can't find variable: props" en prod)
  • watch(props.modelValue) → lock document.body.style.overflow + document.documentElement.style.overflow
  • onUnmounted → cleanup overflow

A — Blog trans-former.fr : Liberapay retiré

  • VPS /opt/astro-site/src/layouts/PostLayout.astro : import + <DonateButton /> supprimés
  • Docker rebuild → 31 pages, HTTP 200

C — Website pro deploy

  • index.astro SCP → /opt/astro-pro/, Docker rebuild → 13 pages, HTTP 200

TODOs ouverts AEP

  • Valider visuellement les 8 fixes sur tel (Jules)
  • Pousser nav-carte sur Gitea (git.trans-former.fr/jules/nav-carte)

2026-04-15 — Session 5 : Corrections post Phase 2 (11 retours Jules)

Exécutant : Sonnet (agent autonome full auto) Durée : ~45min Commits : 7 commits atomiques (8ae4be3 → d30ee2c)

Retours implémentés

#1 — Barre de recherche

  • Desktop : supprimée du header (doublon avec sidebar NavSidebar.vue)
  • Mobile : ajoutée dans le header (app.vue, visible <lg)

#2 — Sheet swipable mobile

  • Nouveau composant MobileSheet.vue — 3 états : collapsed (56px) / half (50dvh) / full (92dvh)
  • Touch events natifs (pas de @vueuse/core — implémentation vanilla pour 0 dépendance)
  • Drag handle + cycle d'états au clic header + snap-back si delta < 60px
  • Vue mobile index.vue : carte pleine hauteur en fond + sheet en overlay
  • Décision d'exécution : vanilla touch events choisis vs useSwipe (pas installé)

#3 — Onglets header desktop

  • 3 onglets centrés remplacent la barre de recherche desktop : Écosystème / Agences Inspirantes / RAG
  • Active state via route.path + underline --nav-primary-solid
  • Badge "en construction" sous les 2 onglets inactifs

#4 — Supprimer "+ Ajouter carte"

  • Bouton retiré du header app.vue
  • Route pages/ajouter-carte.vue supprimée

#5 — Report/modif participatif via Resend

  • server/api/report.post.ts : rate limit 5/IP/jour, validation email + message, envoi Resend API
  • FicheDetail.vue : bouton "Signaler une erreur" → form inline dépliable (message + email, compteur 500 chars)
  • Email envoyé à jules@trans-former.fr (fallback Resend, pas NocoDB)

#6 — Texte intro commentaires

  • CommentSection.vue : intro italique avant la liste

#7 — DOM-TOM row horizontale pleine largeur (Option A)

  • OutremerMap.vue : layout row flex 5 colonnes égales, hauteur 140px
  • Desktop index.vue : layout vertical (Métropole flex-1 + DOM-TOM row 140px fixe en bas)
  • Suppression de l'encart 1/3 droit

#8 — Supprimer layer control carte

  • NavMap.vue : L.control.layers(...) supprimé, seul CartoDB Positron reste

#9 — Dark mode switch tuile

  • NavMap.vue + OutremerMap.vue : MutationObserver sur html.classListsetUrl() light_all/dark_all
  • Initialisation correcte dès le premier rendu (lire document.documentElement.classList)

#10 — Bandeau bas : logique inversée

  • BandeauBas.vue : isCollapsed = ref(true) par défaut
  • onMouseLeave : repli immédiat (suppression du timer 3s)
  • Opacité bandeau déployé : 70%

#11 — Bouton Soutenir recentré

  • bandeau-col--center : padding-top: 8px pour décaler vers le bas dans la hauteur du bandeau

Cleanup

  • Supprimés : TerritoireTabs.vue, TerritoireToggle.vue (composants morts)
  • Supprimée : pages/ajouter-carte.vue
  • Créées : pages/agences.vue, pages/rag.vue (placeholders)
  • Créé : components/MobileSheet.vue

Décisions d'exécution S5

  • #2 useSwipe : @vueuse/core absent du package.json — vanilla touch events choisis pour éviter d'ajouter une dépendance. Même résultat, 0 dép supplémentaire.
  • #5 NocoDB vs Resend : fallback Resend appliqué conformément aux décisions Jules pré-tranchées. Pas de table NocoDB créée.
  • DOM-TOM desktop : layout du index.vue passé de flex row (2/3 + 1/3) à flex col (Métropole flex-1 + DOM-TOM row 140px) pour intégrer l'Option A cohéremment.

Build & deploy

  • Build : npx nuxt build (223 modules client, 134 modules serveur)
  • Deploy : tar → SSH → systemctl restart nav-carte
  • Vérif : curl https://aep.trans-former.fr/ → 200, service active

2026-04-15 — Session 4 : Phase 2 UX (10 features)

Exécutant : Sonnet (agent délégué Opus) Durée : ~1h30

Réalisé

Feature 1 — Modal fiche sidebar desktop

  • Composant FicheModal.vue : overlay centré max-w-3xl (768px), 90vh scroll interne
  • Backdrop semi-transparent, fermeture Esc / clic backdrop / bouton croix
  • Lien "Ouvrir" → /fiche/[id] (URL partageable préservée)
  • Fetch $fetch('/api/fiche/X') avec watch sur orgId
  • onSelectOrg desktop (≥1024px) → ouvre modal
  • Mobile : comportement inchangé (navigation vers /fiche/[id])

Feature 2 — Fusion Outre-mer (suppression TerritoireTabs)

  • Desktop : layout flex flex:2 Métropole + flex:1 bandeau DOM-TOM (max 340px, OutremerMap réutilisé)
  • Mobile : section DOM-TOM scroll horizontal avec compteurs par territoire
  • TerritoireTabs retiré du template (composant conservé, non utilisé)

Feature 3 — CartoDB Positron + layer control + maxBounds

  • NavMap.vue : fond par défaut CartoDB Positron (light_all), layer control 3 fonds (Clair / Schématique Stamen / Standard OSM)
  • maxBounds France métr. [41-51.5°N, -5.5-10°E], minZoom: 5, maxZoom: 18
  • OutremerMap.vue : fond CartoDB Positron par défaut (sans layer control)

Feature 4 — Bandeau bas rétractable desktop

  • bg-opacity-80 (rgba 0.8)
  • Auto-collapse après 3s, déploie au hover ou mouvement souris < 80px du bas
  • Barre fine 32px en état rétracté avec label "AEP · Transparence IA"

Features 5+6 — Textes bandeau corrigés + lien Liberapay

  • "Coût IA ce mois : X.XX €" + "Tokens : X" (sans ratio budget)
  • Tooltip "1 € = 30 fiches mises en ligne" au hover bouton Soutenir
  • Lien Liberapay → https://liberapay.com/trans-former.fr/donate
  • Mobile : BandeauBas absent, remplacé par FAB coeur (gauche) → bottom sheet

Feature 7 — Logo AEP tooltip

  • Sous-titre "Architecture d'Écologie Politique" visible en lg:
  • Tooltip au hover pour sm: (sans le sous-titre)
  • Attribut title sur le lien pour accessibilité

Feature 8 — Header desktop refonte + barre de recherche

  • Barre de recherche ~500px centrée dans le header desktop (≥1024px)
  • Sync URL ?q= via watch, fonctionne depuis toutes les pages
  • Boutons "+ Proposer" et "+ Ajouter carte" en haut à droite
  • Route /ajouter-carte créée (placeholder)

Feature 9 — Dark mode

  • Toggle soleil/lune en top nav, persistance localStorage clé aep_theme
  • Variables CSS .dark dans main.css : fonds sombres, texte clair
  • Overrides Leaflet (popup, control layers)
  • Note : tuile CartoDB dark (dark_all) non switchée automatiquement — à améliorer en S5

Feature 10 — /a-propos CTA Contribuer + lien Liberapay

  • Section "Contribuer" existante améliorée (lien /donate)
  • Liens Liberapay unifiés sur /donate

Commits

Hash Message
46b6051 feat(aep-s4): CartoDB Positron + layer control + maxBounds France
3c036db feat(aep-s4): logo AEP + tooltip nom complet + dark mode toggle
775ec64 feat(aep-s4): dark mode CSS variables + Leaflet overrides
9736ba7 feat(aep-s4): bandeau bas — rétractable desktop + FAB mobile + textes corrigés
d092fd0 feat(aep-s4): /a-propos — liens Liberapay corrigés (/donate)
955a561 feat(aep-s4): page /ajouter-carte (placeholder bientôt disponible)
b406cc9 feat(aep-s4): fusion outremer — suppression TerritoireTabs + vue 2/3+1/3
2b43e90 feat(aep-s4): modal fiche sidebar desktop

Build & Deploy

  • npm run build : ✓ 2.84 MB bundle
  • Deploy VPS : ✓ systemctl is-active = active
  • curl -sI https://aep.trans-former.fr/ : HTTP/2 200

Points d'attention pour S5

  • Dark mode tuile Leaflet : switcher automatiquement vers dark_all CartoDB quand .dark est actif
  • BandeauBas détection mobile avec isMobile ref côté client : SSR safe (montage) mais risque blink. Envisager useWindowSize ou classe CSS média
  • TerritoireTabs et TerritoireToggle non utilisés → à archiver ou supprimer en S5
  • Barre de recherche header : sur les pages /fiche/[id] et /a-propos, la recherche navigue vers /?q=X — comportement acceptable mais pas élégant
  • Modal fiche : pas de CommentForm affiché côté SSR (correctement client-only via watch)

2026-04-14 — Session 3a : Chatbot API (Étape 5bis)

Exécutant : Sonnet (agent chatbot) Durée : ~30 min

Réalisé

ChatbotSheet.vue — Déjà complet (S2) : onboarding exact E-spec §Détails chatbot, bulles user/assistant, fiches recommandées avec lien, erreurs 429/503, animation slide-up mobile.

API POST /api/chatbot (nouveau) :

  • Rate limit : 10 req/IP/jour via rateLimitJson.ts (JSON + SHA-256 RGPD, spec F §8)
  • Circuit breaker : vérification budget 20€/mois via circuitBreaker.ts → HTTP 503 si dépassé
  • Keyword scoring : extraction mots-clés question → score sur nom+description+tags → top 20 fiches
  • Filtrage optionnel par fonction et echelle si fournis dans le body
  • Appel Mistral Small (mistral-small-latest, temp 0.3, max 600 tokens, json_object)
  • Parse JSON → { reponse_texte, fiches_recommandees: [{ id, nom, explication }] }
  • Log asynchrone stats_usage (non bloquant)

Helpers nouveaux :

  • server/utils/rateLimitJson.ts — fichier JSON par IP hashée SHA-256 dans /tmp/nav-ratelimit/
  • server/utils/circuitBreaker.ts — budget check NocoDB stats_usage + calcul coût Mistral Small/Nemo
  • nuxt.config.ts — ajout statsTableId (défaut mbbq7n47ixy19mc)

Décisions

Décision Détail
rateLimitJson vs Redis existant JSON fichier créé séparément (spec F §8 + RGPD SHA-256). Redis existant conservé pour submit/comment. Les deux coexistent.
Prompt Mistral Small Construit depuis E-spec-frontend.md §Détails chatbot — pas de prompt verbatim dans F-spec §3 (qui est Nemo enrichissement). Checkpoint Jules requis avant deploy prod.
circuitBreaker.ts Worker absent → créé dans server/utils/ (source unique, réutilisable par le futur worker)
statsTableId Défaut hardcodé mbbq7n47ixy19mc + env var STATS_TABLE_ID en override

Commits

  • 718e9f6 — feat(chatbot): API /api/chatbot + rate limit JSON SHA-256 + circuit breaker

Build

  • npm run build : ✓ sans erreur, 2.78 MB bundle

TODO avant deploy (S3b)

  • STATS_TABLE_ID=mbbq7n47ixy19mc/opt/nav-carte/.env (confirmer ou corriger l'ID)
  • Vérifier que /tmp/nav-ratelimit/ est accessible en écriture sur VPS (droits node)
  • Cron journalier reset /tmp/nav-ratelimit/ (0h UTC) → à ajouter en S3b
  • Tester chatbot 2-3 requêtes réelles sur VPS après deploy
  • Checkpoint Jules : valider prompt Mistral Small (construit, pas copié-collé de F-spec)

2026-04-14 — Session 2 Ajustements UX v3

Pilote : Sonnet
Durée : ~30 min

Réalisé

A) Search desktop déplacé top nav → sidebar (haut)

  • TopSearchBar retiré du header app.vue — top nav épuré (logo + Contribuer + Aléatoire)
  • Barre recherche inline ajoutée en haut de NavSidebar.vue : toujours visible, pleine largeur sidebar, styles scoped sidebar-search-*
  • Focus ring var(--nav-primary), bouton clear intégré
  • Mobile inchangé (search sticky bandeau entre filtres et liste)
  • URL sync ?q= conservé via pages/index.vue
  • Commit : 3f88e86

B) Chatbot desktop cliquable + expand/collapse

  • ChatbotPlaceholder.vue refactorisé : état replié (56px) → étendu (45vh) au clic
  • Header entier cliquable + chevron dédié avec rotation SVG 180° animée
  • transition: max-height 0.3s ease — animation fluide
  • Zone étendue : placeholder conversation S3, overflow-y auto
  • Mobile ChatbotSheet.vue inchangé
  • Commit : 88d0319

Build

  • npm run build clean (0 erreurs, 0 warnings TS)

2026-04-14 — Session 1 : Setup + Fondations

Pilote : Opus
Exécutants : Sonnet-1 (VPS/NocoDB), Sonnet-2 (seed parsing)
Durée estimée session : 4-5h

Décisions validées

Décision Détail Source
Palette A (sobre institutionnel) + bleu nuit #1a2238 à 60% opacité partout (bandeau, pins, chips, surlignages) Jules 2026-04-14
Texte Bleu plein pour titres/courant (lisibilité > esthétique) Déduction palette
Liberapay liberapay.com/trans-former.fr (pas nav-archi) Jules
Seed Bypass modération pour 94 fiches, 2-3 réservées pour test pipe IA (doc de la pipe → skill Mistral Nemo futur) Jules
Liberapay transparence Section dédiée dans /a-propos (étape 8), pas dans le bandeau Jules
Circuit breaker dépassé Bandeau "manque de fonds" + CTA Liberapay + pédagogie "1€ = N requêtes" + transparence origine Liberapay Jules
From email modération contact@trans-former.fr (existant Resend) Jules
Session stratégie 3 sessions dédiées (S1 fondations, S2 front, S3 IA+deploy) — préserve jauge Opus, use Sonnet pour exécution Jules
Tokens Mistral key stockée dans /opt/nav-carte/.env, jamais committée Standard

Réalisé

Sonnet-1 — Setup VPS + schéma NocoDB :

  • Token NocoDB nav-v2-worker créé : R-Yhd_0KgfW0ZjFxIl5iNyLS1ca7VpP8dNbo4OOa
  • /opt/nav-carte/.env créé (chmod 600) avec MISTRAL_API_KEY, NOCODB_TOKEN, NOCODB_BASE, NOCODB_TABLE_*, RESEND_FROM, NOCODB_URL
  • crawl4ai 0.8.6 installé via pip (--ignore-installed pour conflit lib rich)
  • Table organisations (m08t7g5v4wch6wb) étendue : 19 champs ajoutés
    • Contenus : url, description_user, description_enrichie, points_cles, localisation_ville, submitted_by_email, moderator_note, ai_raw_output, scrape_content
    • Taxonomie : echelle (SingleSelect), territoire (SingleSelect), tags_fonction (MultiSelect)
    • État : scrape_status, moderation_status, ai_processed (Checkbox)
    • Géo : latitude, longitude
    • Meta : submitted_at, moderated_at
  • Table stats_usage créée : mbbq7n47ixy19mc (model, endpoint, tokens_in/out, cout_eur, timestamp, orga_id)
  • Table scrape_queue skippée : F§2 n'en définit pas, la pipe utilise scrape_status sur organisations
  • Tests insert/update OK sur les deux tables avec nouveau token

Sonnet-2 — Parsing biblio + géocodage seed :

  • 93 entités parsées (A-biblio annonçait 94, erreur de comptage source)
  • 72 géocodées via Nominatim, 21 sans ville (lat/lon null, pas de fallback centre France pour éviter bruit carte)
  • Correction manuelle Saint-Ouen (93) vs Saint-Ouen-l'Aumône (Picardie)
  • 3 fiches réservées test pipe IA : CNOA, Archireport, Collectif Fil

Sonnet-3 — Re-tag + CROA + import NocoDB :

  • Re-tag : 32 Prospection → Développement, 17 RH → Gestion d'agence
  • Formation détecté et ajouté à 9 fiches (Réseau des MA, Cité de l'Archi, MAJ, CFAA, REFC'A, DU Dauphine, UNAID, FFP, Cité Archi Formation continue)
  • 13 CROA régionaux : 6 créés + 7 existants enrichis (tous avec "Gestion d'agence")
  • CNOA National isolé et réservé aux 3 fiches test pipe IA
  • 96 fiches importées dans NocoDB (99 v2 3 test pipe)
  • Apostrophe typographique U+2019 appliquée pour compat NocoDB
  • Anomalie : 8 anciens records V1 présents (IDs 1-8, moderation_status null) → à purger avant mise en prod

Anomalies & points à surveiller

  1. Taxonomie moderation_status — divergence F/G

    • F§2 définit : approved / rejected
    • G mentionne : published / approved
    • Front V1 existant utilise : approved
    • Décision Session 2 : rester sur approved/rejected partout, retirer published du prompt G si la doc est mise à jour. Le statut pending (avant modération) existe de fait comme absence de validation.
  2. Endpoint NocoDB 0.301.5

    • L'endpoint correct pour ajouter des colonnes est /api/v1/db/meta/tables/{id}/columns
    • L'endpoint /api/v2/meta/tables/{id}/fields n'existe PAS dans cette version
    • Le MCP nocodb n'était pas configuré avec la bonne URL de base → contournement direct par SSH + curl
    • TODO : reconfigurer le MCP nocodb pour qu'il pointe correctement (utile pour sessions futures)
  3. Disque VPS : 72,4% (était 69,8% avant install crawl4ai)

    • crawl4ai a pris ~2,6 GB
    • Hypothèse : Playwright + Chromium embarqué (crawl4ai utilise Playwright pour les pages JS)
    • Marge : OK pour Sessions 2-3. À surveiller quand on commencera à scraper et stocker du contenu dans scrape_content
    • Alternative si problème disque : remplacer crawl4ai par httpx + BeautifulSoup pour le scraping statique (bien plus léger, mais perd le rendu JS)
    • TODO : vérifier via du -sh le poids exact de crawl4ai et ses deps Playwright, décider si on garde ou on allège
  4. Conflit lib Python rich

    • Résolu avec pip install --ignore-installed
    • Risque : peut casser un autre outil Python VPS qui utilise rich (lightrag, formations, etc.)
    • TODO : vérifier que lightrag et autres services Python tournent toujours

Fichiers produits

  • /opt/nav-carte/.env (VPS, chmod 600)
  • Tables NocoDB : organisations étendue + stats_usage nouvelle
  • 0 INBOX/NAV-V2-recherches/palette-nav-v2.md — palette finale CSS
  • 0 INBOX/NAV-V2-recherches/palettes-preview.html — mockup validé
  • 0 INBOX/NAV-V2-recherches/seed-94-fiches.json (à produire par Sonnet-2)
  • 0 INBOX/NAV-V2-recherches/seed-94-rapport.md (à produire par Sonnet-2)

Prochaines étapes

  • Sonnet-2 finit le parsing + géocoding
  • Import seed NocoDB (bypass modération, 2-3 fiches réservées test pipe IA)
  • Rédaction des prompts Sessions 2 et 3 (livrable fin Session 1)

Taxonomie finale NAV V2 (validée 2026-04-14)

Échelle (3 niveaux) : National / Régional (inclut Départemental) / Local

Territoire : Métropole + 5 DOM-TOM (Guadeloupe, Martinique, Guyane, La Réunion, Mayotte)

Fonctions (10 tags, multi 1-5, ordre = priorité) :

  1. Juridique
  2. Technique
  3. Économique (strictement finance : aides, fiscalité, rentabilité, tarification)
  4. Administratif (démarches externes : permis, OA, déclarations)
  5. Chantier
  6. Comptabilité
  7. Développement (ex-Prospection — AO, concours, réseaux pro, acquisition clients)
  8. Formation (NOUVEAU — école, MOOC, organisme, formation continue)
  9. Gestion d'agence (ex-RH — élargi : RH + management + pilotage + orga interne)
  10. Santé mentale

Renommages appliqués :

  • Prospection → Développement
  • RH → Gestion d'agence (élargi)
  • Ajout : Formation

Cas particuliers :

  • CNOA → éclaté en 1 fiche National (siège Paris) + 13 fiches CROA Régional (préfectures de région)
  • Option 3 (antennes pins secondaires via champ multi-coords) → backlog V3

Points ouverts (à trancher en amont des prochaines sessions)

  • Session 2 : choix rate limit chatbot (fichier JSON vs Redis)
  • Session 3 : seuil email modération (N fiches en attente → envoi)
  • Facteur CO2eq : confirmer 0.052 kg CO2/kWh (RTE FR) ou utiliser valeur plus récente (ADEME 2024 ≈ 0.055)
  • Nominatim policy : user-agent NAV-V2/1.0 (contact@trans-former.fr) → OK ?
  • UX Session 2 : options échelle vides (Local) + onglet Outre-mer vide → afficher avec compteur "0" pour inviter à contribuer (cohérent esprit collaboratif)

Enrichissement DOM-TOM (post V2)

Session d'enrichissement à lancer après déploiement V2 :

  • Recherche ciblée par territoire (Guadeloupe, Martinique, Guyane, La Réunion, Mayotte)
  • Sources : CROA locaux, maisons de l'archi outre-mer, réseaux pro insulaires
  • Agent Sonnet-research 1-2h → complément seed

Piste externe : team.archi

Contacter le fondateur de team.archi (forum entraide archi) pour :

  • Lui présenter NAV V2 (esprit, périmètre)
  • Demander si team.archi documente d'autres réseaux d'entraide qu'on pourrait intégrer
  • Proposer un échange / listing mutuel

Brouillon email à préparer (requiert nom + email fondateur de Jules).



2026-04-14 — Session 2 : Front — Carte + Sidebar Filtres

Exécutant : Sonnet (agent) Durée : ~1h

Décisions prises

Décision Détail
Leaflet sans plugin Nuxt @nuxtjs/leaflet a une compat instable avec Nuxt 3.15 — import dynamique direct dans onMounted() + <ClientOnly> wrapper. Plus fiable, zéro config SSR à gérer.
Cluster seuil 15 (spec dit 15+) disableClusteringAtZoom: 14 — au-delà de zoom 14 les pins se défiltrent, cohérent avec la granularité ville/rue
@headlessui/vue installé mais non utilisé pour le drawer Le drawer implémenté en Vue natif (Teleport + transition) est plus léger et sans dépendance. @headlessui/vue reste disponible pour l'étape 3 (modales commentaires).
Seed JSON : Id fictif 1000+ Pour éviter collision avec les IDs NocoDB réels (1-96+). Les URLs /fiche/1000+ ne seront pas résolues en prod — comportement attendu en dev.
moderation_status=approved Confirmé — retirer published du filtre comme résolu en Session 1
Compteurs calculés sur orgs non filtrées Les (0) montrent les options sans aucune fiche dans la base totale, pas dans la sélection courante. Plus honnête vis-à-vis de l'invitation à contribuer.

Composants créés

  • NavMap.vue — Leaflet + OSM + clusters + pins personnalisés
  • EchelleFilter.vue — chips exclusifs National/Régional/Local
  • FonctionFilter.vue — multi-sélect 10 fonctions (max 5)
  • TerritoireToggle.vue — Métropole + 5 DOM-TOM sous-onglets
  • NavSidebar.vue — wrapper desktop
  • FilterDrawer.vue — drawer mobile (Teleport + transition Vue native)

Fichiers modifiés

  • assets/css/main.css — tokens CSS palette NAV V2 + surcharges Leaflet/clusters
  • tailwind.config.js — couleurs nav.* ajoutées
  • nuxt.config.ts — optimizeDeps Leaflet pour Vite
  • server/routes/api/organisations.get.ts — V2 champs + fallback seed JSON
  • pages/index.vue — refonte complète layout + filtres + URL sync

Commits Session 2

  • 64f5b0a — feat(deps): installer leaflet, markercluster, headless UI
  • dc849ef — feat(style): palette NAV V2 — tokens CSS + tailwind
  • 799d8fc — feat(api): GET /api/organisations V2 avec fallback seed JSON
  • 450a45c — feat(components): carte Leaflet + sidebar filtres (étape 2)
  • 3f486df — feat(page): index.vue V2 — carte + sidebar + URL sync

Validation build

  • npm run build : ✓ sans erreur, 2.09 MB bundle

Notes pour Sonnet 2 (étape 3 — fiche détail)

  • @headlessui/vue installé et disponible si besoin pour la modale commentaires
  • Interface Org définie dans pages/index.vue — à extraire dans types/org.ts si partagée (étape 3 en aura besoin)
  • Route API existante : GET /api/organisations/[id] dans server/routes/api/organisations/[id].get.ts — fonctionne, utiliser tel quel
  • Champ description_enrichie prévu mais absent du seed — prévoir fallback sur description
  • Champ points_cles : chaîne ou JSON array ? Vérifier dans NocoDB avant d'afficher

Notes pour Sonnet 3 (étape 6 — formulaire contribuer)

  • Route POST /api/organisations existe déjà en V1 (server/routes/api/organisations.post.ts) — à adapter pour V2 (nouveaux champs + moderation_status: pending)
  • Zod non encore installé — à ajouter dans package.json
  • Rate limit : trancher JSON simple (recommandé pour MVP) vs Redis

Points ouverts

  • 8 anciens records V1 (IDs 1-8, moderation_status null) à purger en prod
  • Vérifier type de points_cles dans NocoDB avant rendu fiche (string ou JSON)
  • Tester carte sur VPS avec données NocoDB réelles (96 fiches)


2026-04-14 — Session 2 (suite) : Spec définitive — Top nav + chatbot + filtres

Exécutant : Sonnet 2/3
Durée : ~1h

Décisions prises

Décision Détail
app.vue = layout global Refondu avec top nav (logo + TopSearchBar + Contribuer + Aléatoire). Supprime le header V1 (vert sage). NuxtPage prend le flex-1 restant.
Recherche top nav → URL ?q= TopSearchBar émet vers app.vue → router.replace ?q=. pages/index.vue watch route.query.q. Pas de prop passée via NuxtPage (non supporté simplement).
Sidebar sans recherche Input recherche retiré de NavSidebar — redondant avec top nav.
Sidebar sans chatbot Zone chatbot déplacée dans la zone carte (bas).
ChatbotPlaceholder sous carte Input désactivé 52px, fond --nav-bg. Présent sous Métropole ET Outre-mer.
Échelle multi-select (string[]) EchelleFilter + NavSidebar + FilterDrawer + pages/index.vue tous adaptés en string[]. Plus de string | null.
Fiche aléatoire ?random=1 dans l'URL → pages/index.vue redirige vers /fiche/[id] aléatoire.
shadcn Écarté — Vue+Tailwind+CSS scoped suffit, zéro complexité d'intégration.
Bonus "ouvrir chatbot" Skippé — pas de valeur avant S3.

Composants créés

  • TopSearchBar.vue — barre recherche animée (compact 44px → 280px au focus), CSS scoped
  • ChatbotPlaceholder.vue — zone bas carte, input désactivé, réserve espace S3

Composants modifiés

  • app.vue — refonte complète layout global (V1 sage vert → V2 bleu nuit)
  • pages/index.vue — supprime header dupliqué, intègre ChatbotPlaceholder, échelle string[]
  • EchelleFilter.vue — single-select → multi-select (string[])
  • NavSidebar.vue — supprime input recherche + zone chatbot, prop echelle string[]
  • FilterDrawer.vue — prop echelle string[], activeCount adapté

Commits Session 2 (suite)

  • 33fbd3b — feat(nav): top nav global avec barre de recherche animée
  • 1d5d9ab — feat(ux): chatbot placeholder sous la carte + sidebar sans chatbot
  • 905f338 — feat(filtres): échelle multi-select + sidebar sans recherche ni chatbot

Validation build

  • npm run build : ✓ sans erreur, 2.12 MB bundle

Points en attente / non implémentés

  • TerritoireTabs dans le drawer mobile — non dupliqué (à faire si besoin)
  • Fiche aléatoire nécessite que les orgs soient chargées — si orgs vides au moment du ?random=1, pas de redirect. Acceptable pour MVP.


2026-04-14 — Session 2 (Sonnet 2) : Fiche détail + Commentaires

Exécutant : Sonnet 2 (agent NAV V2) Durée : ~1h

Décisions prises

Décision Détail
types/org.ts Interface Org canonique extraite — suppression des 2 déclarations dupliquées dans index.vue et fiche/[id].vue
Route API dédiée GET /api/fiche/[id] dans server/api/fiche/ (distinct de server/routes/api/organisations/[id]) — meilleure séparation des responsabilités
Table commentaires commentTableId = COMMENT_TABLE_ID ?? AVIS_TABLE_ID (fallback table V1 si pas de table dédiée configurée)
Mistral Nemo timeout 2s via AbortController — fallback safe_check: 'pending' si timeout ou clé absente
Retour filtres sessionStorage nav_back_filters (pas de query param _back) — plus simple, pas de fuite dans URL partagée
mini-carte Leaflet non-interactif dans FicheDetail — zoom:10, dragging:false, toutes interactions désactivées
points_cles Tente JSON.parse d'abord, fallback découpage par lignes si format texte
Rate limit Redis Posé par Sonnet 3 — server/utils/rateLimit.ts avec fallback mémoire si Redis KO

Composants créés

  • components/FicheDetail.vue — affichage complet avec mini-carte Leaflet
  • components/CommentSection.vue — liste commentaires publiés + refresh prop
  • components/CommentForm.vue — formulaire soumission + gestion 429

Routes API créées

  • server/api/fiche/[id].get.ts — proxy NocoDB champs V2 complets
  • server/api/comment/index.post.ts — filtre éthique Mistral Nemo (timeout 2s, fallback pending)
  • server/api/comment/[orgId].get.ts — commentaires publiés triés chronologiquement

Fichiers modifiés

  • pages/fiche/[id].vue — refonte complète SSR (FicheDetail + CommentSection + CommentForm + SEO)
  • pages/index.vue — ajout storeFiltersForBack() pour sessionStorage + import type Org
  • types/org.ts — CRÉÉ : interface canonique V2
  • nuxt.config.ts — ajout mistralApiKey + commentTableId

Commits Session 2 (Sonnet 2)

  • a653336 — refactor(types): extraire interface Org canonique dans types/org.ts
  • 89bd22a — feat(api): GET /api/fiche/[id] + POST/GET /api/comment avec filtre Mistral Nemo
  • 06c44cd — feat(components): FicheDetail + CommentSection + CommentForm
  • 420f534 — feat(page): /fiche/[id] SSR complète — FicheDetail + comments + SEO + retour filtres

Validation build

  • npm run build : ✓ sans erreur, 2.74 MB bundle

Coordination Sonnet 3 (rate limit Redis)

Posé par Sonnet 3 : server/utils/rateLimit.ts (ioredis + fallback mémoire) importé dans POST /api/comment. Sonnet 3 a modifié server/api/comment/index.post.ts pour brancher checkRateLimit(ip, 'comment', 5). Configuration : REDIS_URL dans .env, fallback mémoire si Redis KO (dev sans Docker).

Points ouverts pour Session 3

  • public/og-default.png à créer (logo par défaut pour og:image) — actuellement référencé, pas encore créé
  • COMMENT_TABLE_ID à ajouter dans .env VPS si table dédiée à part de AVIS_TABLE_ID
  • MISTRAL_API_KEY à vérifier dans /opt/nav-carte/.env — la clé est dans le .env VPS (Session 1), juste s'assurer du nom de variable exact
  • Vérifier type de points_cles dans NocoDB (JSON array stringifié ou texte brut ?)
  • Tester sur VPS avec données NocoDB réelles (96 fiches seed)

2026-04-14 — Session 2 (parallèle) : Étape 6 — Formulaire /contribuer + Redis rate limit

Exécutant : Sonnet 3 (agent)

Redis VPS

  • redis-server absent au départ (pas mentionné dans VPS-check.md)
  • Installé : apt-get install redis-serversystemctl enable + start
  • Bind : 127.0.0.1 -::1 uniquement (localhost, sécurisé)
  • Test : redis-cli ping → PONG ✓
  • En prod : ajouter REDIS_URL=redis://127.0.0.1:6379 dans /opt/nav-carte/.env
  • En dev local : fallback compteur en mémoire (ioredis lazyConnect — pas de crash si Redis absent)

Fichiers produits

Fichier Description
server/utils/rateLimit.ts Helper Redis générique — checkRateLimit(ip, action, maxPerDay) avec fallback mémoire
server/api/submit/index.post.ts POST /api/submit — Zod + Nominatim + NocoDB pending + rate limit 3/IP/j
server/api/comment/index.post.ts Rate limit Redis 5/IP/j ajouté en tête (base Sonnet 2)
pages/contribuer.vue Page dédiée — 8 champs, validation Zod client+serveur, 422/429, succès + trackingUrl

Décisions

Décision Détail
Page vs modal Page dédiée /contribuer — plus simple pour MVP. Route duale desktop (redirect ?contribute=1 + modale) en backlog V2.1
description_user K-prompt (50-500) > E-spec (20-200) — tranché : 50-500 chars
Champ ville Optionnel (K-prompt) — moins de friction, fallback fiche sans coords prévu
NOCODB_BASE_ID Valeur fallback p_nav_v2 dans le code. À confirmer : ssh vps-hetzner "grep -i base /opt/nav-carte/.env"

Rate limits posés (Redis)

  • /api/submit : 3 soumissions / IP / jour
  • /api/comment : 5 commentaires / IP / jour

Commits

  • d9b6a31 — feat(deps): ajouter zod + ioredis + REDIS_URL runtime config
  • e81625f — feat(server): helper rate limit Redis avec fallback mémoire
  • 5c24c06 — feat(api): POST /api/submit — validation Zod + geocoding + NocoDB pending
  • fc0c52c — feat(page): /contribuer — formulaire V2 Zod client + UX mobile-first
  • (rate limit /api/comment dans commit Sonnet 2 420f534)

Build

  • npm run build : ✓ sans erreur, 2.74 MB bundle

TODO avant prod

  • REDIS_URL=redis://127.0.0.1:6379/opt/nav-carte/.env (VPS)
  • Confirmer NOCODB_BASE_ID : ssh vps-hetzner "grep -i base /opt/nav-carte/.env"
  • Tester submit valide → fiche pending dans NocoDB
  • Tester 4ème submit → vérifier HTTP 429

2026-04-14 — Session 2 (Étape 2) : Mobile UX

Exécutant : Sonnet 1.8 (agent NAV V2 Mobile) Durée : ~1h

Décisions prises

Décision Détail
FilterDrawer supprimé L'ancien drawer filtres (bouton flottant gauche, slide depuis gauche) est supprimé. Les filtres sont désormais inline dans le flow mobile.
ChatbotSheet nouveau composant ChatbotSheet.vue — bottom sheet plein écran, Teleport + transition slide-up, 92dvh, input désactivé (S3), fermeture backdrop + bouton Retour.
Carte mobile 45dvh height: 45dvh sur mobile, min-height: 180px. dvh pour éviter le bug clavier mobile.
Tagging compact inline Bandeau entre carte et liste : échelle 3 checkboxes 16px avec labels courts (Nat/Rég/Loc) + fonctions scroll horizontal overflow-x: auto.
Tap card → centre carte onSelectOrgMobileselectedId change → NavMap.vue réagit via watch selectedId → mapInstance.panTo.
Bouton chatbot flottant 56×56px, bottom: 1.5rem; right: 1rem, opacity: 0.88, z-[1000], lg:hidden.
Multi-Leaflet Outre-mer Grille 5 mini-cartes existante conservée — build clean, à tester en conditions réelles.
Poignée draggable Skippé — estimé > 30 min, noté pour Session 3.
Interface Org Sonnet 2 a extrait types/org.ts, linter auto-appliqué l'import dans pages/index.vue.

Composants créés

  • ChatbotSheet.vue — bottom sheet chatbot plein écran (92dvh, slide-up, backdrop, poignée visuelle, input désactivé)

Composants supprimés

  • FilterDrawer.vue — drawer filtres mobile (remplacé par tagging inline + ChatbotSheet)

Fichiers modifiés

  • pages/index.vue — refonte complète zone mobile + bouton chatbot flottant + ChatbotSheet

Commits Session 2 (Mobile)

  • d39e7be — feat(mobile): ChatbotSheet — bottom sheet plein écran avec animation slide-up
  • 0843301 — feat(mobile): supprimer FilterDrawer — remplacé par tagging inline + ChatbotSheet
  • pages/index.vue — inclus dans refacto Sonnet 2 (a653336 → import types/org.ts)

Validation build

  • npm run build : ✓ sans erreur, 2.12 MB bundle

Backlog mobile S3

  • Poignée draggable entre carte et liste (drag resize)
  • Multi-Leaflet Outre-mer mobile à tester en conditions réelles → fallback dropdown si trop lourd
  • Animation léger zoom pin au tap card (spec F)
  • Chatbot IA branché (Session 3)


2026-04-14 — Session S2 Ajustements finaux v2 (correctif branding + polish)

Exécutant : Sonnet 1.9b (agent AEP V2)

Contexte

L'agent précédent avait commis une erreur de spec sur le renommage : "NAV" avait été remplacé par "Écosystème Architecture" dans les textes visibles. La bonne règle est : "NAV" (texte visible) → "AEP" (Architecture d'Écologie Politique).

Corrections branding (commit fix)

Fichier Avant Après
app.vue logo icône N A
app.vue logo texte Écosystème Archi. AEP
components/ChatbotSheet.vue aria-label="Assistant Écosystème Architecture" aria-label="Assistant AEP"
pages/index.vue SEO title Écosystème Architecture — Cartographie AEP AEP — Cartographie de l'écologie politique architecturale
pages/contribuer.vue SEO title Proposer une ressource — Écosystème Architecture Proposer une ressource — AEP
pages/ajouter.vue SEO title Proposer une fiche — Écosystème Architecture Proposer une fiche — AEP
pages/fiche/[id].vue og:description fallback Fiche organisation — NAV Architectes Fiche organisation — AEP
pages/fiche/[id].vue SEO title {nom} — NAV {nom} — AEP
pages/fiche/[id].vue og:title {nom} — NAV Architectes {nom} — AEP

Ajustements complémentaires

  • Bouton flottant mobile : icône seule → pill avec texte "Chatbot" visible (48px h, gap-2, font-weight 600)

État des 4 ajustements spec

# Ajustement État
1 Échelle inline 1 ligne ✓ Fait par agent précédent (EchelleFilter.vue flex-wrap gap-x-4)
2 NAV → AEP (textes visibles) ✓ Corrigé dans ce commit
3 Mobile search au-dessus liste ✓ Fait par agent précédent (lignes 167-203 pages/index.vue)
4 Bouton mobile "Chatbot" ✓ Fait dans ce commit (pill avec label visible)

Build

  • npm run build : ✓ sans erreur, 2.74 MB bundle

2026-04-14 — Session 3 : Chatbot IA (Étape 5bis)

Pilote : Opus Exécutant : agent Sonnet S3 Durée : ~1h

Réalisé

ChatbotSheet.vue (mobile) — refactorisé :

  • Conversation IA complète : messages user/assistant, scroll auto, loading dots
  • Message onboarding exact (texte E §6) affiché avant la première question
  • Bulles fiches recommandées : card compacte avec lien /fiche/[id] + explication
  • Erreurs typées : 429 (rate limit), 503 (budget épuisé), fallback générique
  • Emit highlightOrgs pour highlight carte
  • Animations slide-up + prefers-reduced-motion respecté

ChatbotPlaceholder.vue (desktop) — refactorisé :

  • Même conversation IA que le mobile, format panneau latéral expand/collapse
  • Même onboarding, mêmes bulles fiches, même gestion d'erreurs
  • Input en bas du panneau étendu, Enter pour envoyer

server/api/chatbot/index.post.ts — créé :

  • Rate limit : checkRateLimit(ip, 'chatbot', 10) via helper Redis S2 existant
  • Circuit breaker : lecture cumul stats_usage mois courant, 503 si ≥ 20€
  • Fetch top-20 fiches NocoDB (moderation_status=approved) avec scoring keyword
  • Contexte JSON compact injecté dans le prompt système Mistral Small
  • Appel mistral-small-latest (temperature: 0.3, max_tokens: 600, json_object)
  • Parse JSON → { reponse_texte, fiches_recommandees }
  • Log stats_usage (fire and forget)

Décisions

Décision Détail
Rate limit helper Réutilisation du helper Redis S2 (checkRateLimit) — plus robuste que JSON fichier /tmp. Comportement identique (10 req/j).
STATS_TABLE_ID env Valeur par défaut mbbq7n47ixy19mc (créé en S1) — override via env si besoin
Scoring fiches Keyword match sur nom + description + tags — suffisant pour MVP, pas de vector search
prefers-reduced-motion Animations supprimées si l'utilisateur a activé ce préfé navigateur

Build

  • npm run build : ✓ sans erreur, 2.78 MB bundle

Commits Session 3 (étape 5bis)

  • feat(chatbot): ChatbotSheet mobile + ChatbotPlaceholder desktop — conversation IA branchée
  • feat(api): POST /api/chatbot — Mistral Small + rate limit + circuit breaker

TODO avant prod

  • Vérifier STATS_TABLE_ID dans /opt/nav-carte/.env (valeur par défaut : mbbq7n47ixy19mc)
  • Tester 2-3 requêtes réelles sur VPS avec fiches NocoDB
  • Vérifier index.vue passe bien highlightOrgs au NavMap depuis ChatbotSheet et ChatbotPlaceholder
  • Tester mobile iOS Safari + Android Chrome


2026-04-14 — Session 3 (Étapes 4-5) : Worker IA + Test pipeline

Exécutant : Sonnet (agent) Durée : ~2h

Réalisé

Étape 4 — Worker enrichissement IA :

  • worker/enrich.js créé (Node.js ESM, systemd timer 5 min)
  • Pipeline complet : fetch pending → scrape crawl4ai → Mistral Nemo → update NocoDB → log stats_usage
  • Circuit breaker budget 20€ (filtre JS, car NocoDB v0.301.5 rejette les filtres datetime)
  • Email Jules via Resend si seuil 5 fiches pending en modération
  • Lock anti-overlap via /tmp/nav-worker.lock
  • Retry 2x sur erreur Mistral + flag ai_error si 3 échecs

Infrastructure :

  • worker/package.json + node_modules/dotenv installés
  • Variables RESEND_API_KEY, EMAIL_JULES, BUDGET_MAX_EUR, WORKER_LIMIT ajoutées au .env
  • nav-worker.service + nav-worker.timer systemd créés et activés

Étape 5 — Test pipeline sur 3 fiches :

  • 3 fiches injectées en NocoDB (CNOA Id=106, Archireport Id=107, Collectif Fil Id=108)
  • Checkpoint 1 fiche (CNOA) : €0.000029, extrapolé 96 fiches = €0.0028 → GO
  • 3 fiches enrichies avec succès

Métriques pipeline

Fiche tokens_in tokens_out cout_eur Temps Confiance
CNOA 1 248 167 €0.000029 3.0s haute
Archireport 4 376 261 €0.000091 3.9s haute
Collectif Fil 2 628 221 €0.000057 4.3s haute
Total 8 252 649 €0.000177 11.2s

Extrapolation 96 fiches : €0.0057 (budget 20€ = 3 500× la consommation réelle)

Problèmes résolus

Problème Solution
Playwright absent → crawl4ai crash AsyncHTTPCrawlerStrategy (mode statique)
NocoDB rejette filtres datetime Filtre JS sur tous les records stats_usage
Apostrophe U+0027 dans tags refusée U+2019 (typographique) partout dans VALID_FONCTIONS
import dynamique ESM incompatible spawnSync importé statiquement

Décisions prises en autonomie

Décision Raison
AsyncHTTPCrawlerStrategy au lieu d'AsyncWebCrawler standard Playwright non installé sur VPS, mode statique suffisant pour sites archi
Filtre budget JS (pas NocoDB) API NocoDB 0.301.5 ne supporte pas les filtres de date en mode gte
spawnSync pour appel Python Évite les complications d'import dynamique en ESM

Fichiers produits

  • /opt/nav-carte/worker/enrich.js — Worker IA principal
  • /opt/nav-carte/worker/package.json — Dépendances (dotenv)
  • /etc/systemd/system/nav-worker.service — Service oneshot
  • /etc/systemd/system/nav-worker.timer — Timer 5 min
  • nav-carte/PIPE-IA-DOC.md — Documentation complète pipeline + résultats

TODO avant deploy prod (Étape 9)

  • Chatbot (Étape 5bis) — non implémenté dans cette session
  • Bandeau bas + Liberapay (Étape 7) — non implémenté
  • Page /a-propos (Étape 8) — non implémenté
  • Deploy git sur VPS + build Nuxt production
  • Purger 8 anciens records V1 (IDs 1-8, moderation_status null) avant prod
  • Tester worker sur soumission réelle via /contribuer
  • Vérifier que les 3 fiches test (Ids 106-108) sont modérées ou purgées avant prod (sont en mode test)


Session 3b — Deploy final AEP (2026-04-14 soir)

Durée : ~3h · Résultat : AEP V2 live sur aep.trans-former.fr + nav.trans-former.fr (alias)

Dispatch

# Action Modèle Status
A Prompt système chatbot Mistral Small Opus + Jules
B Composant BandeauBas.vue + endpoint /api/stats Sonnet
C Page /a-propos (brouillon — texte à réécrire par Jules) Sonnet
D Cron purge /tmp/nav-ratelimit/ (systemd timer quotidien 03:00 UTC) Sonnet
E Enrichissement rétro 96 fiches (worker IA Mistral Nemo) Sonnet
F DNS OVH + Caddy alias + build + deploy + tests HTTPS Opus + Jules

Décisions tranchées

Décision Choix Raison
Domaine V2 aep.trans-former.fr nouveau + alias nav.* Pas de V1 figée réelle à préserver : le VPS servait déjà la V2
Structure Caddy Alias simple (2 domaines → même service :3333) Éviter 2 instances Node pour un code identique
Prompt chatbot "AEP — Écosystème Entraide", posture engagée, règle hors-scope Cohérent avec positionnement AEP vs terminologie NAV résiduelle
Rate limit chatbot JSON SHA-256 (RGPD) — Redis retiré Volume borné par circuit breaker, pas besoin de Redis
Enrichissement rétro Bulk patch pending → worker → bulk approve API Spot-check qualité accepté, tags à corriger en modération manuelle

Commits

74c9722 feat(aep-s3b): bandeau transparence + /a-propos + cron purge rate-limit
68e1e53 fix(chatbot): purge version Redis + prompt système AEP

Enrichissement IA — bilan qualité

  • Coût réel : €0.006 pour 96 fiches (×3 300 sous le seuil €20/mois)
  • Descriptions : factuellement correctes (spot-check 10 fiches OK)
  • Tags : 6/10 vides sur Maisons de l'Architecture (mapping ne matche pas "expositions/conférences"), 1 sur-tagging MAOP Id 11 (10/10 tags — site généraliste scraped), MAF Id 15 manque "Juridique"
  • Action Jules : correction manuelle tags dans NocoDB UI (non bloquant pour deploy)

Découverte imprévue — bug deploy.sh

Incohérence entre deploy.sh (rsync vers /opt/nav-carte/) et nav-carte.service (exec /opt/nav-carte/.output/server/index.mjs). Le deploy.sh écrit au mauvais endroit — le service tourne sur .output/ qui n'est jamais mis à jour par le script. Contournement cette session : tar + ssh extract manuel dans .output/. À patcher en V3 : aligner deploy.sh sur la structure .output/.

État à la clôture

✓ aep.trans-former.fr — HTTPS 200 (/, /a-propos, /api/stats)
✓ nav.trans-former.fr — alias OK
✓ 99 fiches approved (96 enrichies + 3 S3a)
✓ Bandeau bas + chatbot + lien À propos intégrés
✓ Cron purge rate-limit actif (prochaine exécution 2026-04-15 03:00 UTC)
✓ V1 Redis chatbot purgée

TODO post-session (Jules, async)

  • Réécrire le texte de pages/a-propos.vue (placeholders <!-- TODO Jules --> en tête de chaque section)
  • Corriger tags Maisons de l'Architecture + MAOP + MAF dans NocoDB UI
  • Tests manuels : submit /contribuer, chatbot mobile (iOS + Android), feedback UX
  • Patcher deploy.sh pour cibler /opt/nav-carte/.output/ (voir découverte ci-dessus)
  • Vérifier widget Liberapay à liberapay.com/trans-former.fr — compte existe ?

Backlog V3 (hors scope V2)

Infra / deploy

  • Fix deploy.sh — cible /opt/nav-carte/.output/ pas /opt/nav-carte/ (incohérence découverte en S3b)
  • Migration /opt/nav-carte//opt/aep/ — alignement nom projet (dette terminologique NAV)
  • Rebrand service systemd nav-carte.serviceaep.service (low-priority)
  • Monitoring — sonde Uptime Kuma (status.trans-former.fr) : ajouter aep.*/api/stats
  • Backup NocoDB — dump quotidien base pipilvsi7dibo80 vers stockage externe

Produit / UX

  • Tags auto — améliorer le mapping worker pour matcher "expositions/conférences/culture" → Développement + Formation (évite tags vides sur MA)
  • Modération UI custom — interface dédiée pour batch approve/reject (au lieu de cliquer 96 rows)
  • Sidebar "Fiches récemment ajoutées" — boost engagement
  • Recherche sémantique chatbot — passer de keyword match à embeddings (Mistral Embed, volume à estimer)
  • Page /transparence publique — détail coûts, CO2, donateurs Liberapay

Éditorial

  • Contenu /a-propos — texte politique écrit par Jules seul (hors scope IA)
  • Badge IA souveraine — vérifier formulation (Hetzner = Falkenstein DE, pas FR — dire "Hébergé en Europe" ?)
  • Page Contribuer — modération visible — expliquer pipeline "formulaire → worker IA → modération humaine"

Monétisation / association

  • Bascule Liberapay → HelloAsso quand ASO créée (scénario 2 du doc C-systeme-dons.md)
  • Widget Liberapay "total collecté" — actuellement juste CTA, ajouter feedback

Technique

  • Caching API organisations — actuellement re-fetch NocoDB à chaque render
  • Full-text search côté client — Fuse.js sur descriptions enrichies
  • Mode offline / PWA — manifest + service worker pour usage terrain