Files
echo-du-huit/docs/DECISIONS.md
T
Pierre MartinandClaude Opus 4.8 f994bfac30 docs: record the list to content script seam (T1)
Documents the single storage key (revising the sibling task's "key =
uid" seam, which had no way to transport the uid), the explicit ISO
serialisation, and why the café fallback is display-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:23:38 +02:00

6.3 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 :

  • uid seul, pas de href/ETag. Le transport actuel (?export) renvoie un ICS concaténé sans href/ETag par événement. L'UID est la clé stable ; T2 résoudra l'adressage CalDAV par UID au moment du PUT. Le contrat n'est pas alourdi.
  • journeeEntiere dè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 await avant tabs.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.

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.