# 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](https://bugzilla.mozilla.org/show_bug.cgi?id=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.js` sans `import` ni `export`, communique par `globalThis`.** Un fichier sans `import`/`export` est valide **à la fois** comme script classique (content script, background) **et** comme module ESM (`import "./config.js"` depuis `presentation.js` et `bun test`). C'est ce qui permet d'absorber `LIEU_PAR_DEFAUT` (fin d'une duplication) sans build. ⚠️ Le jour où quelqu'un ajoute un `export`, tous les content scripts cassent → acté dans `DECISIONS.md`, avec un commentaire en tête de fichier. - **Le background lit `config.local.json`, pas le content script.** Un content script ne peut `fetch(runtime.getURL(...))` que si la ressource est en `web_accessible_resources` — ce qui **exposerait l'email à la page de la mairie**. Le background lit sans WAR et transmet via `storage.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. Un `config.local.json` **requis** (é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 fichier `js` manque) : `fetch` + repli est la seule voie. - **`CLE_EVENEMENT_EN_ATTENTE` dupliquée + test de dérive.** La constante vit dans `transfert.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 convertir `transfert.js`). - **Honnêteté machine/humain.** Jamais de « 00:00 » sur une journée entière (heures laissées vides), jamais de `lat|lng` inventé sur un lieu hors café, `input_6_35` (accessibilité) laissé intact, honeypots intacts. Tout manque remonte dans `aCompleter` (bandeau). - **`config.local.json.example` dans `extension/`, pas à la racine.** Le background résout `runtime.getURL("config.local.json")` **dans `extension/`** : l'exemple doit être à côté de sa cible de copie. ## Patterns à réutiliser - **Fichier « bilingue » ESM/classique** : ne poser `import`/`export` nulle part, publier l'API sur `globalThis.MonNamespace`. Chargeable partout dans une extension MV3 sans build. Le prix : une convention à documenter pour qu'un futur agent ne la « corrige » pas en `export`. - **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.js` ne 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)` traite `null` / 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/Paris` via `Intl.DateTimeFormat`, heures zéro-paddées. **Épingler `TZ=Europe/Paris` dans 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`, dispatcher `new Event("input", {bubbles:true})` **puis** `new Event("change", {bubbles:true})` (Gravity Forms écoute `change`). Le dispatch est gratuit — ne pas supposer que `.value` suffit. - **Isoler chaque effet de bord dans son propre `try/catch`.** Un `try` global autour du remplissage laissait la première exception emporter **tous** les champs suivants. Un `try` **par champ** → le champ fautif part dans `manquants`, 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 ``. `poserSelect` faisait `[...champ.options]` sur un `` → `champ.options` undefined → `TypeError`, qui (avant l'isolation par champ) vidait aussi Tarifs, adresse et coordonnées. Corrigé : heures via `poserTexte` (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.json` impose 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 du `fetch`, 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 dans `storage.local` verrait 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 `cafe` partielle interdite** : `fusionner` est shallow → une locale avec un `cafe` incomplet produirait `"undefined|adresse"`. Le README précise que seuls `organisateur` et `email` sont surchargeables. ## Tags tags: [firefox-extension, manifest-v3, content-script, globalthis, esm, gravity-forms, dates, timezone, rfc5545, config, bun-test, tdd, vanilla-js]