commit 328df2d181e12806f2eb2751586ef95e250c36ae Author: Pierre Martin Date: Mon Jun 29 23:40:13 2026 +0200 Initialise le projet : documentation et squelette Extension Firefox pour publier une sélection d'événements du calendrier Nextcloud de L'Atelier du Huit vers l'agenda de la mairie de Cugnaux. - README : vue d'ensemble et installation - CLAUDE.md : guide agent et contraintes structurantes - docs/RECHERCHE.md : faits techniques vérifiés (CalDAV, formulaire, agenda, ICS) - docs/DECISIONS.md : choix d'architecture et leur pourquoi - package.json : Bun comme outil de dev minimaliste (pas de build au runtime) Co-Authored-By: Claude Sonnet 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52f36e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Dépendances de dev (Bun) +node_modules/ + +# Config locale (organisateur, email) +config.local.json + +# Secrets +.env +.env.local + +# Packaging de l'extension +web-ext-artifacts/ +*.zip +*.xpi + +# OS / IDE +.DS_Store +.idea/ + +# Logs +*.log diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..466e3a5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,46 @@ +# Guide agent — Écho du Huit + +Extension **Firefox** (Manifest V3) qui pousse une sélection d'événements du +calendrier Nextcloud de L'Atelier du Huit vers le formulaire de l'agenda mairie +de Cugnaux. Voir [`README.md`](README.md) pour la vue d'ensemble. + +## Avant de coder + +1. Lis [`docs/RECHERCHE.md`](docs/RECHERCHE.md) : faits **vérifiés** (URLs, ids + de champs, valeurs du thème, structure de l'agenda, cas limites de l'ICS). + Ne redécouvre pas ce qui y est déjà confirmé. +2. Lis [`docs/DECISIONS.md`](docs/DECISIONS.md) : choix actés et leur pourquoi. + Ne les relitige pas sans raison nouvelle. +3. Le backlog vit dans **Piaire** (tenant `perso`, projet `echo-du-huit`). + Le code est la seule source de vérité du comportement réel. + +## Contraintes structurantes (ne pas violer sans accord) + +- **JS vanilla, pas de build, pas de runtime.** L'extension tourne dans le + navigateur. Pas de Bun/Node au runtime, pas de bundler. Dépendance externe + uniquement si vendorée en `.js` (ex. `ical.js` de Mozilla pour le parsing). + Bun est autorisé **uniquement comme outil de dev** (`bun test`, vendoring) — + jamais embarqué dans l'extension livrée. +- **Aucun secret stocké.** L'accès Nextcloud passe par la session du navigateur + (`credentials: 'include'` + `host_permissions`). Ne jamais écrire de mot de + passe dans le repo ou la config. +- **Pas d'auto-submit du formulaire mairie.** On pré-remplit le DOM ; l'humain + relit, complète et envoie. Le formulaire est modéré et les descriptions + méritent souvent un enrichissement. +- **Image = manuelle.** Un content script ne peut pas remplir un ``. L'extension aide à glisser l'affiche, ne l'injecte pas. +- **Statut partagé dans Nextcloud.** *ignoré* / *soumis* = tag `CATEGORIES` + écrit en CalDAV (visible par tous les bénévoles, durable). *publié* = dérivé + de l'agenda mairie. Pas d'état privé local pour ces statuts. + +## Conventions + +- Code simple et expressif ; commentaires seulement pour le *pourquoi*. +- Nouveaux paramètres/fonctions : obligatoires par défaut (optionnels seulement + si la rétrocompat l'exige). +- Messages et UI en français (public = bénévoles associatifs). +- TDD quand c'est pertinent. + +## Ne pas committer + +`config.local.json` (organisateur/email), artefacts de packaging, `.env`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..435d7ba --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Écho du Huit + +Extension Firefox qui aide les bénévoles de **L'Atelier du Huit** (café associatif, +Cugnaux) à publier une **sélection** de leurs événements sur l'agenda de la mairie. + +Source des événements : le calendrier Nextcloud du café. +Cible : le formulaire « Proposer un événement » du site de la mairie. + +## Le besoin + +Tous les événements du café ne vont pas sur le site de la mairie — seulement +certains. L'extension sert de **TODO list** : pour chaque événement à venir, on +voit s'il est *à faire*, *soumis*, *publié* (avec le lien direct) ou *ignoré*, +et on peut le pousser vers le formulaire mairie en un clic (pré-rempli, à +relire et envoyer soi-même). + +## Comment ça marche + +1. L'extension lit le calendrier Nextcloud via la **session du navigateur** + (aucun mot de passe stocké). +2. Sa page affiche les événements à venir et leur statut. +3. Bouton **Créer** → ouvre le formulaire mairie pré-rempli ; tu relis, + complètes (image à glisser à la main), puis envoies. +4. Bouton **Ignorer** → marque l'événement (tag partagé, visible dans Nextcloud). +5. Le statut **publié** est vérifié sur l'agenda public de la mairie. + +## Installation (dev) + +Extension non signée, chargée comme module temporaire : +`about:debugging` → *Ce Firefox* → *Charger un module complémentaire temporaire* +→ sélectionner `manifest.json`. Pas de build, pas de runtime à installer. + +## Documentation + +- [`docs/RECHERCHE.md`](docs/RECHERCHE.md) — faits techniques vérifiés (CalDAV, + champs du formulaire, valeurs du dropdown thème, structure de l'agenda, cas + limites de l'ICS). +- [`docs/DECISIONS.md`](docs/DECISIONS.md) — choix d'architecture et leur pourquoi. +- [`CLAUDE.md`](CLAUDE.md) — guide pour les agents qui implémentent. + +## Suivi + +Backlog géré dans Piaire : http://localhost:3117/perso/project/echo-du-huit diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md new file mode 100644 index 0000000..96bc8d2 --- /dev/null +++ b/docs/DECISIONS.md @@ -0,0 +1,46 @@ +# 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 `` (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-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`). diff --git a/docs/RECHERCHE.md b/docs/RECHERCHE.md new file mode 100644 index 0000000..e4f1213 --- /dev/null +++ b/docs/RECHERCHE.md @@ -0,0 +1,125 @@ +# Recherche technique — faits vérifiés (2026-06-29) + +Document de référence pour l'implémentation. Toutes les valeurs ci-dessous sont +**vérifiées sur le HTML/ICS réel**, pas supposées. Re-vérifier si > 6 mois. + +--- + +## 1. Source : calendrier Nextcloud (CalDAV) + +- **URL** : `https://atelier-huit.frama.space/remote.php/dav/calendars/Pierre/latelier-du-huit-sbastien_shared_by_admin/?export` +- **Auth** : session navigateur (cookies). Depuis l'extension → `fetch(url, { credentials: 'include' })` + `host_permissions` sur `https://atelier-huit.frama.space/*`. **Aucun mot de passe stocké.** +- **Réponse** : flux iCalendar brut (VCALENDAR/VEVENT). +- Si non connecté → la requête échoue : prévoir un message "connecte-toi à Nextcloud dans un onglet". + +### Cas limites du flux réel (échantillon 2026-06-29, 473 VEVENT) + +| Réalité | Chiffre | Conséquence parsing | +|---|---|---| +| VEVENT total | 473 | | +| UID **uniques** | 381 | **Dédoublonner par UID** (récurrences = même UID répété) | +| Récurrents (RRULE) | 54 | Gérer/ignorer les occurrences passées | +| Journée entière (`DTSTART;VALUE=DATE`) | 7 | **Pas d'heure** → le formulaire mairie attend une heure : cas à traiter | +| Avec LOCATION | 51 / 473 | La **majorité n'a pas de lieu** → défaut = adresse du café | +| Avec DESCRIPTION | 142 / 473 | La majorité n'a pas de description → champ souvent vide | +| Fuseaux | `Europe/Paris` **mais aussi `Africa/Lagos`** | Ne PAS supposer Europe/Paris : lire le vrai `TZID` | + +> Recommandation parsing : `ical.js` (Mozilla) plutôt que regex maison — gère RRULE, TZID, VALUE=DATE. + +--- + +## 2. Cible : formulaire mairie (Gravity Forms, form id = 6) + +URL : `https://www.ville-cugnaux.fr/mes-loisirs/associations/proposer-un-evenement-dans-lagenda/` + +### Stratégie de soumission + +Content script qui **remplit le DOM par id**, puis **l'utilisateur soumet à la main**. +- Pas de params URL : Gravity Forms n'accepte `?input_X=` que si "population dynamique" est activé par champ côté mairie (non). +- Les tokens CSRF (`gform_currency`, `state_6`, `version_hash`) sont **dans la page** et gérés par le formulaire lui-même : on n'y touche pas. ⚠️ Les tokens du curl capturé sont **par session, non réutilisables**. + +### Mapping des champs (ids confirmés présents dans le HTML) + +| id DOM | name | Contenu | Source | +|---|---|---|---| +| `#input_6_42` | `input_42` | Organisateur | config (« L'Atelier du Huit ») | +| `#input_6_41` | `input_41` | Email | config | +| `#input_6_1` | `input_1` | Titre | SUMMARY | +| `#input_6_3` | `input_3` | Description (textarea) | DESCRIPTION | +| `#input_6_20` | `input_20` | Thème (select) | mapping (voir §3) | +| `#input_6_31` | `input_31` | Date début `jj/mm/aaaa` | DTSTART | +| `#input_6_32` | `input_32` | Date fin `jj/mm/aaaa` | DTEND | +| `#input_6_38_1` / `#input_6_38_2` | `input_38[]` | Heure début [HH, MM] | DTSTART | +| `#input_6_39_1` / `#input_6_39_2` | `input_39[]` | Heure fin [HH, MM] | DTEND | +| `#input_6_35` (`_1`) | `input_35.1` | Accessibilité (case) | fixe | +| `#input_6_36` | `input_36` | Public ciblé | optionnel | +| `#input_6_40` | `input_40` | Tarifs | défaut « Gratuit » | +| `#nova_address` | `nova_address` | Adresse texte | défaut café | +| `#input_6_18` | `input_18` | `lat\|lng\|adresse` (hidden) | défaut café | +| `#input_6_34` | `input_34` | **Image (file)** | ⚠️ non remplissable par script (sécurité) → manuel | +| `#input_6_23`, `#input_6_43` | | Honeypot | laisser vide | + +**Défaut café** : `8 Rue du Pré Vicinal 31270 Cugnaux` +`input_18` = `43.53753132806989|1.342981890597508|8 Rue du Pré Vicinal 31270 Cugnaux` + +--- + +## 3. Valeurs exactes du dropdown Thème (`#input_6_20`) + +Chaînes `value` à utiliser telles quelles : + +``` +Culture +Atelier - Stage +Economie - Emploi +Exposition +Photographie +Rencontre +Spectacle +Enfance - Jeunesse +Jeunesse +Parentalité +Petite enfance +Scolarité +Loisirs +Mobilité +Nature - Environnement +Numérique +Participation citoyenne +Cérémonies officielles +Réunion publique +Santé +Seniors +Solidarité +Sport +``` + +> Le calendrier Nextcloud n'a pas de thème → prévoir un choix par l'utilisateur dans l'UI, défaut « Culture ». + +--- + +## 4. Vérification publication (agenda public mairie) + +URL filtrée par mois : +`https://www.ville-cugnaux.fr/mes-loisirs/agenda/?f=1&theme=&date=periode&date_debut=MM%2FYYYY&date_fin=MM%2FYYYY&public=` + +### Structure HTML d'un résultat (confirmée) + +Chaque événement publié = `