9.2 KiB
Solution : [T1] Content script : pré-remplir le formulaire mairie (+ config)
Problème résolu
Au clic sur « Créer sur le site mairie », l'extension déposait l'événement dans
storage.local puis ouvrait un formulaire vide : le bénévole devait recopier
à la main titre, dates, heures, adresse, organisateur, email — exactement le
geste que l'extension existe pour supprimer. La boucle n'était pas fermée.
Objectif tenu : le formulaire s'ouvre déjà rempli de ce que la machine sait, laisse vide ce qu'elle ignore (jamais de valeur inventée), et dit dans un bandeau ce qui reste à compléter. Le bénévole relit, complète (image, thème, enrichissement) et envoie lui-même — pas d'auto-submit.
Ce ticket a aussi introduit la config de l'extension.
Approche choisie
Deux content scripts classiques partageant un scope + namespace globalThis
(approche 3 du brainstorm), + config à défauts committés et surcharge locale
optionnelle (option (a)).
Le vrai risque du ticket n'était pas « poser des valeurs dans un DOM » : c'était
le calcul des dates (DTEND exclusif, dates flottantes de journée entière,
franchissement de minuit) — invisible sous TZ=Europe/Paris, la famille de bugs
qui avait déjà coûté deux passes au ticket précédent. Il fallait donc que ce
calcul reste sous bun test.
Trois alternatives écartées, et pourquoi :
- Content script monolithique : logique de dates intestable (rien à importer
en
bun test) → on coderait à l'aveugle les deux bugs les plus chers. - Module ESM en import dynamique (
import(runtime.getURL(...))+web_accessible_resources) : s'appuie sur un bug ouvert MV3 de Firefox (bugzilla 1803950) — parier le risque sur le point le plus fragile de la plateforme. - Formater en amont, content script bête : réviserait une couture figée trois jours plus tôt et coupleraient la page liste au formulaire mairie.
L'approche retenue garde le calcul testable sans parier sur un bug plateforme ni rouvrir une couture fraîche.
Décisions clés
-
config.jssansimportniexport, communique parglobalThis. Un fichier sansimport/exportest valide à la fois comme script classique (content script, background) et comme module ESM (import "./config.js"depuispresentation.jsetbun test). C'est ce qui permet d'absorberLIEU_PAR_DEFAUT(fin d'une duplication) sans build. ⚠️ Le jour où quelqu'un ajoute unexport, tous les content scripts cassent → acté dansDECISIONS.md, avec un commentaire en tête de fichier. -
Le background lit
config.local.json, pas le content script. Un content script ne peutfetch(runtime.getURL(...))que si la ressource est enweb_accessible_resources— ce qui exposerait l'email à la page de la mairie. Le background lit sans WAR et transmet viastorage.local. -
Config optionnelle, dégradation propre. Fichier absent (clone frais), HTTP non-ok ou JSON invalide →
{}→ organisateur/email vides signalés dans le bandeau, tout le reste rempli. Unconfig.local.jsonrequis (énoncé littéral) aurait cassé tout clone frais : rejeté. Le fichier n'étant pas garanti présent, il ne peut pas être listé dans le manifest (Firefox refuse d'installer si un fichierjsmanque) :fetch+ repli est la seule voie. -
CLE_EVENEMENT_EN_ATTENTEdupliquée + test de dérive. La constante vit danstransfert.js(ESM), inatteignable depuis un content script classique. Dupliquée volontairement, avec un test qui garantit l'égalité avec l'originale (plutôt que rouvrir la couture pour convertirtransfert.js). -
Honnêteté machine/humain. Jamais de « 00:00 » sur une journée entière (heures laissées vides), jamais de
lat|lnginventé sur un lieu hors café,input_6_35(accessibilité) laissé intact, honeypots intacts. Tout manque remonte dansaCompleter(bandeau). -
config.local.json.exampledansextension/, pas à la racine. Le background résoutruntime.getURL("config.local.json")dansextension/: l'exemple doit être à côté de sa cible de copie.
Patterns à réutiliser
-
Fichier « bilingue » ESM/classique : ne poser
import/exportnulle part, publier l'API surglobalThis.MonNamespace. Chargeable partout dans une extension MV3 sans build. Le prix : une convention à documenter pour qu'un futur agent ne la « corrige » pas enexport. -
Séparer calcul pur (testé) / effet de bord (fin, non testé).
calculerValeurs(payload, config)est pur et renvoie un objet plat, prêt à poser (dateDebut: "18/07/2026",heureDebut: ["19","30"] | null, …) ;formulaire-mairie.jsne fait que lire le storage et écrire le DOM. Le bug des heures (voir Pièges) a validé ce découpage : confiné au poseur, diagnostiqué en une pile d'appels, corrigé en quelques lignes, cœur testé jamais touché. -
Merge tolérant pour config éditée à la main :
fusionner(defauts, locale)traitenull/ non-objet / tableau comme « pas de surcharge » et ne mute pas les défauts. Une config optionnelle ne doit jamais lever. -
Dates de calendrier sans fuseau. Journée entière = date flottante (RFC 5545) → lire uniquement les composantes locales (
getFullYear/getMonth/getDate), jamais l'heure, jamais un fuseau. DTEND est exclusif → reculer la date de fin d'un jour (en composantes locales, jamais « −24h »). Événement horodaté → tout àEurope/ParisviaIntl.DateTimeFormat, heures zéro-paddées. ÉpinglerTZ=Europe/Parisdans les tests ET rejouer sous plusieurs fuseaux (le bug de fuseau est invisible sous le fuseau de dev). -
Poser une valeur dans un champ piloté par un framework : après
.value, dispatchernew Event("input", {bubbles:true})puisnew Event("change", {bubbles:true})(Gravity Forms écoutechange). Le dispatch est gratuit — ne pas supposer que.valuesuffit. -
Isoler chaque effet de bord dans son propre
try/catch. Untryglobal autour du remplissage laissait la première exception emporter tous les champs suivants. Untrypar champ → le champ fautif part dansmanquants, les autres sont posés. Ferme la classe entière de panne, pas le cas vu. -
Rendre tout geste automatique visible. Un bandeau injecté dit ce qui a été fait et ce qui reste ; storage vide (visite directe) → rien. Un préremplissage silencieusement partiel soumis à une mairie qui modère est le pire échec possible.
Pièges à éviter
-
🔴 Ne jamais déduire le type d'un champ de son intitulé — le HTML est la seule source de vérité. L'énoncé du ticket affirmait « Heures = 2 selects input_6_38_1/_2 ». Le HTML réel dit
<input type="number">.poserSelectfaisait[...champ.options]sur un<input>→champ.optionsundefined →TypeError, qui (avant l'isolation par champ) vidait aussi Tarifs, adresse et coordonnées. Corrigé : heures viaposerTexte(le zéro-padding"09"/"00"reste un valid floating-point number au sens HTML).RECHERCHE.md§2 a été corrigée à la racine (colonne « balise réelle » + avertissement) pour qu'un futur agent ne repaie pas ce bug. C'est la cause racine du seul bug du ticket, et il venait d'un doc/énoncé faux cru sur parole. -
Toucher au
manifest.jsonimpose Décharger + Charger, pas « Recharger » ni F5. Piège déjà payé plusieurs fois. Fermer aussi les anciens onglets de la liste (page orpheline = clics inertes). -
DTEND exclusif : sans recul d'un jour, la date de fin d'un événement multi-jours est fausse — et soumise à une mairie qui modère (personne ne relit une date qui a l'air juste). Le bug le plus cher du ticket.
-
Course background ↔ content script (P3 ouvert, non corrigé) :
chargerConfigLocale()est async ; au tout premier chargement post-install, si le formulaire s'ouvrait avant la fin dufetch, organisateur/email seraient vides à tort. Improbable (la valeur persiste ensuite), noté pour mémoire. -
chargerConfigLocale()s'exécute à chaque réveil de l'event page et écrit{}si le fichier est absent. Toute future page d'options qui écrirait dansstorage.localverrait ses valeurs écrasées par{}au prochain réveil. Trancher « qui fait autorité, fichier ou UI » fait partie du futur ticket — ce n'est pas « juste ajouter un écran ». -
Le poseur DOM n'est pas testé unitairement (aucun harnais DOM dans le repo ; happy-dom n'émule pas le JS de Gravity Forms / Leaflet). C'est le test humain qui tranche le rendu réel (datepicker, marqueur carte, bandeau non écrasé par le CSS de la mairie). Le bug des heures l'a prouvé : seul le DOM réel tranche.
-
Config
cafepartielle interdite :fusionnerest shallow → une locale avec uncafeincomplet produirait"undefined|adresse". Le README précise que seulsorganisateuretemailsont surchargeables.
Tags
tags: [firefox-extension, manifest-v3, content-script, globalthis, esm, gravity-forms, dates, timezone, rfc5545, config, bun-test, tdd, vanilla-js]