Pierre's field test: the agenda announces "10:30 → 12:30", the list only said "à 10:30". The end date was already parsed and already in the payload; it was purely missing from the display. Naively rendering start → end would have been wrong four ways, all measured against the real parser: - no DTEND: the parser sets fin === debut, so "de 10:30 à 10:30" - crossing midnight: "samedi de 22:00 à 02:00" hides the end date - all-day: DTEND is EXCLUSIVE (RFC 5545), so a one-day event carries the next day, and a 18→20 festival carries the 21st The last one is the dangerous one: it yields a wrong date, not just an ugly one. The end day is therefore fin minus one day, computed from local components since a floating date has no timezone. DECISIONS.md records the exclusive DTEND for the sibling task, which fills the town hall's end date field straight from DTEND. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.0 KiB
Décisions d'architecture
Choix actés et leur pourquoi. But : éviter aux futurs agents de relitiger ce qui est déjà tranché. Format léger (date — décision — pourquoi).
2026-06-29 — Extension Firefox plutôt que CLI
Première piste : un CLI (Bun) qui fetch l'ICS et auto-soumet le formulaire. Abandonnée au profit d'une extension Firefox.
Pourquoi :
- La revue manuelle avant envoi est le vrai besoin (descriptions à enrichir, formulaire modéré) → pré-remplir + envoyer soi-même bat l'auto-submit.
- L'extension réutilise la session du navigateur pour Nextcloud → aucun mot de passe à stocker.
- Pas de gestion des tokens CSRF : le formulaire les porte lui-même.
2026-06-29 — v1 = page autonome de l'extension
Plutôt que d'injecter des boutons dans le calendrier Nextcloud (SPA Vue, DOM fragile, casse à chaque mise à jour), la v1 est une page propre à l'extension. L'injection dans le calendrier reste un bonus ultérieur.
2026-06-29 — Statut dans le tag CATEGORIES (CalDAV)
L'intention (ignoré / soumis) est écrite dans l'événement Nextcloud, pas dans un stockage local.
Pourquoi : partagé entre bénévoles, durable, visible dans le calendrier.
Un state.json local serait privé à une machine et invisible des autres.
Le statut publié n'est pas un tag : il est dérivé de l'agenda public de la mairie (match titre + date), ce qui donne aussi le lien direct.
2026-06-29 — Image ajoutée manuellement
Un content script ne peut pas remplir un <input type=file> (sécurité
navigateur). L'extension facilite le geste (affiche l'image à glisser) mais ne
l'automatise pas. L'affiche vient souvent de la newsletter Brevo, hors
calendrier.
2026-06-30 — Parseur iCalendar (T1) : contrat Event figé + ical.js vendoré
Le flux brut est transformé en Event[] par une fonction pure
parserEvenements(flux, aujourdhui) (extension/evenements.js), via ical.js
v2.2.1 vendoré (approche 3 du brainstorm : pas de build, dépendance en .js).
Contrat Event figé (ne pas rouvrir sans accord) :
{ uid, titre, description, lieu, debut: Date, fin: Date, categories: string[], journeeEntiere: boolean }. Champs texte coercés en "" si absents (l'aval
suppose des chaînes). Le défaut « café » du lieu est appliqué en aval, pas
ici.
Décisions tranchées :
uidseul, pas dehref/ETag. Le transport actuel (?export) renvoie un ICS concaténé sanshref/ETagpar événement. L'UIDest la clé stable ; T2 résoudra l'adressage CalDAV par UID au moment du PUT. Le contrat n'est pas alourdi.journeeEntieredès T1 (drapeau porté, comportement « heure exigée » différé à T3).- Cas limites traités « au plus simple », différés à T3 : pas d'expansion
RRULE (récurrents pris au DTSTART maître + filtre futur → un récurrent au
maître passé disparaît, trou assumé et documenté), exceptions
d'occurrence (
RECURRENCE-ID) ignorées, journées entières au minuit local. - Tri & message « 0 événement futur » hors parseur : le parseur renvoie les événements dans l'ordre du flux, non triés (responsabilité de la liste).
2026-06-29 — Pré-remplissage par content script, pas par URL
Gravity Forms n'accepte ?input_X= que si chaque champ est configuré
« population dynamique » côté mairie (improbable). On remplit donc le DOM par
id (ids confirmés dans RECHERCHE.md).
2026-07-14 — [T1] Liste : couture page → content script
La liste dépose l'événement choisi dans storage.local puis ouvre le
formulaire mairie (extension/transfert.js). Ce que la tâche sœur
(« formater les données pour le formulaire mairie ») doit lire :
Clé unique evenement-en-attente, PAS evenement:<uid> (révise la couture
« clé = uid » annoncée par la tâche sœur, qui avait un trou). Le content script
s'exécute sur le site de la mairie : il n'a aucun canal pour apprendre un
uid, le fragment d'URL ayant été écarté (pas de pollution d'une URL tierce).
Une clé connue d'avance le rend lisible sans transporter quoi que ce soit.
- Pas de résidus : un seul slot, écrasé au clic suivant (le dernier clic gagne — limite assumée, le geste réel est séquentiel et le formulaire est modéré).
- On n'efface pas à la lecture : le formulaire survit à un rechargement.
- Le dépôt est
awaitavanttabs.create: sinon le content script peut lire avant l'écriture.
debut/fin sérialisés en ISO 8601 (le payload n'est pas un Event). On ne
parie pas sur le structured clone : le contrat reste vrai quel que soit le
moteur, le content script réhydrate avec new Date(iso).
⚠️ Si journeeEntiere est vrai, l'instant ISO n'est PAS significatif. Une
DTSTART;VALUE=DATE est une date flottante (RFC 5545) : elle n'a pas de
fuseau. ical.js la matérialise à minuit local, donc l'ISO produit dépend de
la machine (2026-07-10 devient ...T15:00:00Z depuis Tokyo). Le consommateur
doit tester journeeEntiere d'abord et n'en lire que les composantes
locales (getFullYear/getMonth/getDate) — jamais l'heure, jamais une
conversion de fuseau. C'est pour la même raison que la liste formate les
journées entières sans timeZone (presentation.js) : forcer Europe/Paris
faisait glisser la date au jour précédent depuis Tokyo ou Auckland.
⚠️ fin est un DTEND EXCLUSIF (RFC 5545) — ne pas l'afficher/soumettre tel quel.
Le payload porte la valeur brute du parseur. Pour une journée entière, un
événement du 18 porte fin = le 19, et un festival du 18 au 20 porte fin =
le 21 : le dernier jour réel est fin moins un jour (composantes locales,
jamais « -24h »). Concerne directement la tâche sœur, qui remplit #input_6_32
(Date fin) depuis DTEND : sans ce recul d'un jour, elle soumettra une date
fausse à la mairie. La liste applique déjà la règle (presentation.js,
veilleDe). Autre cas mesuré : DTEND absent → le parseur pose fin === debut
(il n'y a alors pas de plage à afficher, ni de fin à soumettre).
TZ=Europe/Paris sur bun test est PORTEUR — ne pas le retirer. Le runner
de Bun force TZ=UTC quand TZ est absente (vérifié : bun -e lit le
fuseau système, le runner non). Sans le pin, les tests ne tournent donc pas dans
le fuseau des bénévoles. Corollaire pour l'écriture des tests : un fixture de
date flottante (journée entière, DTSTART sans Z ni TZID) se construit en
composantes locales (new Date(2026, 6, 10)), jamais en instant absolu
(new Date("2026-07-10T00:00:00+02:00")) — ce dernier n'est juste que sur une
machine à +02:00 et ment partout ailleurs.
Défaut café = affichage seulement ; lieu stocké brut ("" si absent). Les
deux avals du parseur ont chacun le leur : la liste affiche l'adresse pour ne pas
montrer une ligne vide (cas majoritaire), le content script décide s'il pose
aussi les coordonnées (input_18). Écrire l'adresse dans le payload
détruirait le signal « pas de lieu » dont il a besoin.