7.1 KiB
Solution : [T2] Vérifier la publication sur l'agenda mairie (+ lien direct)
Problème résolu
Un bénévole qui avait poussé un événement vers le formulaire mairie n'avait aucun retour : le formulaire est modéré, la publication arrive plus tard, et rien dans l'Écho du Huit ne disait « c'est en ligne ». On ne savait pas si un événement était à faire ou déjà publié, et pour le retrouver sur le site il fallait le chercher à la main — au risque de le re-soumettre.
Objectif tenu : l'extension détecte qu'un événement Nextcloud est réellement
publié sur l'agenda public de la mairie, affiche un badge « Publié » + le
lien direct vers la fiche, et retire le bouton « Créer » (anti-doublon).
Ce statut publié est dérivé de l'agenda à la volée (décision 2026-06-29),
jamais un tag écrit dans Nextcloud : c'est la mairie qui fait foi, pas notre
intention. Aucun stockage, aucun credentials (agenda public).
Approche choisie
Scraping HTML de l'agenda + match strict date locale Cugnaux + titre normalisé, biais précision v1 (approche 1 du brainstorm, variante
conservatrice).
Il n'y a pas d'API mairie : la seule source est le HTML public. Le vrai risque n'était donc pas « aller chercher la page » mais la fiabilité du match — et il est gouverné par une asymétrie de risque structurante :
- Faux positif (marquer « publié » à tort) → le bénévole croit que c'est fait, l'événement ne sera jamais publié. Grave.
- Faux négatif (rater un match réel) → au pire il revérifie, et le formulaire modéré rattrape un doublon. Bénin.
→ On privilégie la précision : match strict (date identique + titre normalisé identique), et dans le doute on n'affirme pas « publié ».
Alternatives écartées :
- Match par slug dérivé : la slugification WordPress de la mairie n'est pas
connue et ajoute des suffixes (
atelier-dessin-2) → égalité stricte cassée, faux négatifs. On matche surdate + titre; le lien est lu dans la carte, jamais reconstruit. - Score de confiance + confirmation humaine des cas ambigus (approche 3) : meilleur rappel mais 3ᵉ état d'affichage et un état à mémoriser — or publié n'a pas de tag et l'état local est proscrit. Reporté hors v1 ; la porte reste ouverte si les faux négatifs se voient à l'usage.
Décisions clés
- Seam extraction impure / matching pur.
DOMParserest absent de Bun : impossible de tester l'extraction DOM enbun test. On isole doncextraireCartes(html)(impur, DOMParser, non testé unitairement — validé par le test humain) du matching purpublication.js, testé à fond car c'est le vrai risque produit. Même logique queformulaire-valeurs.js(pur, testé) vsformulaire-mairie.js(impur, DOM). La boucle de pagination reste testable en injectantfetchImpl/extraireCartesImpl. - L'agenda est paginé (
.../agenda/page/N/?f=1&…). Un seul fetch « période » ne suffit pas : on boucle jusqu'à une page vide, avec un plafond anti-boucle (12) au cas où un changement HTML masquerait la page vide. - Fuseau : jamais de conversion. Le
<time datetime="YYYY-MM-DD">est une date nue. On la compare à la date locale de Cugnaux dudebut:Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Paris" })pour l'horodaté, composantes locales (date flottante, sans fuseau) pour la journée entière — sinon la date glisse d'un jour (piège déjà documenté). - Enrichissement additif. La liste est rendue immédiatement ; le badge est
posé après, en tâche de fond (
annoterPublications(...).catch(...), fire-and-forget). Un agenda injoignable, un HTTP ≠ 200, unDOMParserqui lève ou une structure changée →{ ok:false }→ liste intacte, sans badge. Dégradation gracieuse, jamais d'exception, aucun faux positif. - Périmètre v1 = badge « Publié » + lien seulement. On ne pose pas encore la colonne de statut soumis/ignoré (tâche sœur) ; l'ossature reste extensible.
- Aucune modif de
manifest.json:host_permissionscouvre déjàville-cugnaux.fr, et les nouveaux modules sont des ESM importés par la page d'extension (DOMParsery est disponible), pas des content scripts.
Patterns à réutiliser
- Seam « impur en périphérie, pur au centre ». Dès qu'une capacité est
indisponible sous Bun (DOMParser, DOM, Gravity Forms), extraire une fonction
fine impure non testée et pousser toute la logique décidante dans un module pur
testé. Injecter les impls (
fetchImpl,extraireCartesImpl) pour tester l'orchestration sans réseau ni DOM. - Normalisation de titre robuste, en une regex.
NFD+ suppression des diacritiques, puisreplace(/[^\p{Letter}\p{Number}\s]/gu, "")retire ponctuation ET emojis d'un coup (ni lettre ni chiffre), casse basse, espaces compressés. La ponctuation est retirée sans espace pour que « Atelier B.D. » == « Atelier BD ». - Enrichissement DOM additif via
dataset.uid. Poseritem.dataset.uidau rendu, puis retrouver le<li>en phase 2 parliste.querySelector([data-uid="${CSS.escape(uid)}"]). La donnée lente ne bloque jamais l'affichage. - Lien externe défensif. Résoudre le
hrefscrapé vianew URL(href, BASE)(gère relatif et absolu), rejeter tout schéma non-http(s), injecter entextContent+target="_blank"rel="noopener". Href absurde → carte ignorée. aujourdhui/fetchImplinjectés obligatoires pour des tests déterministes (pas denew Date()ni defetchcaché dans la logique).
Pièges à éviter
- Ne pas présumer la balise du scraping. L'agenda sert des
<article class="… card-event">, pas des<div>. Un sélecteurdiv.card-eventextrait 0 carte alors que l'agenda répond 200 : panne totale et silencieuse (pas rattrapée par la dégradation gracieuse, qui ne se déclenche que sur erreur réseau/HTTP). Trouvée par le test humain sur l'agenda réel (45 cartes attendues, 0 obtenue). Leçon : sélecteur agnostique de balise (.card-event), et une hypothèse de structure HTML n'est vérifiée que confrontée au HTML réel. C'est exactement la zone que le plan réservait au test humain — le process a joué son rôle. - Ne pas reconstruire le lien depuis le slug : suffixes WordPress imprévisibles
(
-2). Le lien se lit dans la carte. - Ne pas convertir le fuseau d'une date nue ou d'une journée entière → dérive
d'un jour. Invisible sous
TZ=Europe/Paris, d'où la variable portée par le script de test. - Ne pas laisser flotter la promesse d'annotation :
.catchexplicite, sinon une erreur d'indexation devient une unhandled rejection. - Le
console.error "agenda mairie injoignable"en sortie de test est attendu (test de dégradation gracieuse), pas un échec.
Tags
tags: [firefox-extension, mv3, javascript, web-scraping, dom-parsing, bun-test, pure-impure-seam, timezone, string-normalization, graceful-degradation, nextcloud, wordpress]