docs: capitalize the mairie form prefill and config solution (T1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Pierre Martin
2026-07-17 17:36:56 +02:00
co-authored by Claude Opus 4.8
parent 23e58b17ee
commit ee79ca0757
@@ -0,0 +1,161 @@
# 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 `<input type="number">`. `poserSelect`
faisait `[...champ.options]` sur un `<input>` → `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]