# 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.
> **T1 — parseur** : `ical.js` **v2.2.1** vendoré (build ESM `dist/ical.js`) dans
> `extension/vendor/ical.js`. ⚠️ Les `VTIMEZONE` embarqués dans le flux doivent
> être enregistrés (`ICAL.TimezoneService.register`) **avant** `toJSDate()`,
> sinon l'offset est faux pour les TZID non standards (ex. `Africa/Lagos`).
---
## 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)
> ⚠️ **La colonne « balise réelle » a été payée par un bug** (2026-07-17). Le
> corps de la tâche T1 affirmait « Heures = 2 **selects** » : c'est **faux**, ce
> sont des ``. Le code l'a suivi et les 4 champs d'heures
> ont échoué (`champ.options` undefined → TypeError). **Ne pas déduire le type
> d'un champ de son intitulé : le HTML est la seule source de vérité.** Types
> ci-dessous vérifiés sur la page réelle le 2026-07-17.
| id DOM | name | Balise réelle | Contenu | Source |
|---|---|---|---|---|
| `#input_6_42` | `input_42` | `input[type=text]` | Organisateur | config (« L'Atelier du Huit ») |
| `#input_6_41` | `input_41` | `input[type=email]` | Email | config |
| `#input_6_1` | `input_1` | `input[type=text]` | Titre | SUMMARY |
| `#input_6_3` | `input_3` | `textarea` | Description | DESCRIPTION |
| `#input_6_20` | `input_20` | **`select`** | Thème | mapping (voir §3) |
| `#input_6_31` | `input_31` | `input[type=text]` (datepicker) | Date début `jj/mm/aaaa` | DTSTART |
| `#input_6_32` | `input_32` | `input[type=text]` (datepicker) | Date fin `jj/mm/aaaa` | DTEND |
| `#input_6_38_1` / `#input_6_38_2` | `input_38[]` | **`input[type=number]`** (`min/max`, placeholder `HH`/`MM`) | Heure début [HH, MM] | DTSTART |
| `#input_6_39_1` / `#input_6_39_2` | `input_39[]` | **`input[type=number]`** | Heure fin [HH, MM] | DTEND |
| `#input_6_35` (`_1`) | `input_35.1` | `input[type=checkbox]` | Accessibilité | ⚠️ laissé **intact** : la machine ne sait pas si le lieu est accessible |
| `#input_6_36` | `input_36` | | Public ciblé | optionnel |
| `#input_6_40` | `input_40` | **`textarea`** | Tarifs | défaut « Gratuit » |
| `#nova_address` | `nova_address` | `input[type=text]` | Adresse texte | défaut café |
| `#input_6_18` | `input_18` | `input[type=hidden]` | `lat\|lng\|adresse` | défaut café |
| `#input_6_34` | `input_34` | `input[type=file]` | **Image** | ⚠️ non remplissable par script (sécurité) → manuel |
| `#input_6_23`, `#input_6_43` | | | Honeypot | laisser vide |
Les 4 champs d'heures acceptent le **zéro-padding** (`"09"`, `"00"`) : une suite
de chiffres est un *valid floating-point number* au sens HTML.
**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é = ``
contenant (balise ``, PAS `
` : vérifié sur l'agenda réel le
2026-07-17 — matcher sur la classe `.card-event`, agnostique de balise) :
- **Date** : `