docs: record the measured CalDAV write path and the T2 decisions
This commit is contained in:
@@ -218,3 +218,62 @@ gracieuse, jamais d'exception, aucun faux positif). Deux cartes de même clé
|
|||||||
**additive** : la liste est rendue immédiatement, l'agenda ne la bloque jamais.
|
**additive** : la liste est rendue immédiatement, l'agenda ne la bloque jamais.
|
||||||
Fetch de l'agenda **sans** `credentials` (public). Aucune modification de
|
Fetch de l'agenda **sans** `credentials` (public). Aucune modification de
|
||||||
`manifest.json` (`host_permissions` couvre déjà `ville-cugnaux.fr`).
|
`manifest.json` (`host_permissions` couvre déjà `ville-cugnaux.fr`).
|
||||||
|
|
||||||
|
## 2026-08-13 — [T2] Écriture du statut : REPORT + If-Match, garde-fou, annulable
|
||||||
|
|
||||||
|
**Résolution paresseuse de la ressource, jamais d'URL devinée.** Au moment
|
||||||
|
d'écrire, un `REPORT` (`calendar-query` filtrée sur l'`UID`) rend `href` + `ETag`
|
||||||
|
+ `calendar-data`, puis on `PUT` avec `If-Match`. Mesuré : le nom du fichier
|
||||||
|
`.ics` **ne correspond pas** à l'UID (cf. RECHERCHE.md §1), l'approche
|
||||||
|
« `<uid>.ics` » aurait produit une panne partielle. `If-Match` transforme
|
||||||
|
l'écriture concurrente en **412** (« recharge la liste ») au lieu d'un écrasement
|
||||||
|
silencieux. 0 réponse → `introuvable`, plusieurs `href` → `ambigu` : on n'écrit
|
||||||
|
jamais dans une ressource choisie au hasard.
|
||||||
|
|
||||||
|
**Garde-fou de non-régression.** L'aller-retour `ical.js` pourrait abîmer une
|
||||||
|
propriété exotique. Avant tout `PUT`, on re-parse l'ICS **avant** et **après**,
|
||||||
|
on retire `CATEGORIES` des deux arbres jCal et on compare : différence ailleurs →
|
||||||
|
**aucun PUT**, message honnête. La comparaison est **structurelle**, donc le
|
||||||
|
repliement de ligne et l'espacement peuvent changer librement — une **valeur**,
|
||||||
|
non. On écrit dans l'agenda réel d'une association : un tag non posé se repose,
|
||||||
|
un événement abîmé se répare à la main.
|
||||||
|
|
||||||
|
**Ni `SEQUENCE` ni `DTSTAMP`/`LAST-MODIFIED` ne sont bumpés** : le garde-fou
|
||||||
|
l'interdit, et poser une catégorie n'est pas une modification de planification.
|
||||||
|
La synchro des autres clients passe par l'ETag/ctag.
|
||||||
|
|
||||||
|
**Le CSRF est un confort, pas une condition** : `/csrftoken` rend 200, mais un
|
||||||
|
jeton **invalide** passe quand même sur DAV (mesuré). On envoie l'en-tête
|
||||||
|
`requesttoken` quand on l'a, et un échec de récupération n'est **pas fatal** :
|
||||||
|
le serveur tranche.
|
||||||
|
|
||||||
|
**Le tag `soumis` est posé au clic « Créer » et reste annulable.** Rien ne prouve
|
||||||
|
qu'un formulaire modéré a été envoyé ; le clic est le meilleur signal disponible,
|
||||||
|
donc le statut porte toujours « Annuler l'envoi » et le bouton « Créer »
|
||||||
|
disparaît (c'est le doublon d'annonce qu'on combat). **Un échec d'écriture
|
||||||
|
n'empêche pas d'ouvrir le formulaire** : le tag est un confort d'équipe, remplir
|
||||||
|
le formulaire est la mission.
|
||||||
|
|
||||||
|
**Jamais d'affichage optimiste** : le statut affiché ne change qu'après
|
||||||
|
confirmation du serveur, et ce sont les catégories **renvoyées par
|
||||||
|
l'aller-retour** qui sont conservées. Mentir sur un état que d'autres bénévoles
|
||||||
|
lisent est pire que ne rien afficher. Seule la **ligne concernée** est re-rendue :
|
||||||
|
recharger la liste effacerait les badges « publié » déjà posés.
|
||||||
|
|
||||||
|
**Les ignorés sont grisés et repoussés en fin de liste, jamais masqués** : un
|
||||||
|
ignoré invisible est un ignoré qu'on ne peut plus dé-ignorer.
|
||||||
|
|
||||||
|
**Orthographe du tag : `mairie:ignoré`, avec l'accent** — ces tags sont lus par
|
||||||
|
des humains dans Nextcloud et l'ICS est de l'UTF-8. À re-constater au premier
|
||||||
|
aller-retour réel : si Nextcloud renormalise, basculer en ASCII et le noter.
|
||||||
|
|
||||||
|
**Limite assumée v1** : une ressource = un tag, donc un récurrent porte le même
|
||||||
|
statut pour toutes ses occurrences (cohérent avec le report des récurrences en T3).
|
||||||
|
|
||||||
|
**Limite honnête du garde-fou** : il compare `parse(ics)` à
|
||||||
|
`parse(toString(parse(ics)))`. Il attrape donc ce que la **sérialisation** perd,
|
||||||
|
pas ce que le **parsing** perdrait des deux côtés à la fois. Vérifié à
|
||||||
|
l'écriture : `ical.js` round-trippe sans dommage les formes exotiques d'un vrai
|
||||||
|
calendrier (paramètre quoté à virgule, propriété inconnue, `ATTACH` binaire,
|
||||||
|
`EXDATE` multiple, accent sur la frontière de repliement) — ces cas sont figés en
|
||||||
|
test. Le vrai arbitre reste le test humain « rien d'autre n'a bougé ».
|
||||||
|
|||||||
@@ -31,6 +31,33 @@ Document de référence pour l'implémentation. Toutes les valeurs ci-dessous so
|
|||||||
> être enregistrés (`ICAL.TimezoneService.register`) **avant** `toJSDate()`,
|
> être enregistrés (`ICAL.TimezoneService.register`) **avant** `toJSDate()`,
|
||||||
> sinon l'offset est faux pour les TZID non standards (ex. `Africa/Lagos`).
|
> sinon l'offset est faux pour les TZID non standards (ex. `Africa/Lagos`).
|
||||||
|
|
||||||
|
### Écriture CalDAV — **mesuré le 2026-08-13** (spike navigateur, T2)
|
||||||
|
|
||||||
|
Mesures faites depuis la console de la page d'extension, session Nextcloud
|
||||||
|
ouverte. La séquence complète a été jouée, `PUT` **no-op** compris (contenu
|
||||||
|
reposé à l'identique : rien modifié).
|
||||||
|
|
||||||
|
| Étape | Résultat mesuré |
|
||||||
|
|---|---|
|
||||||
|
| `GET /csrftoken` | **200**, corps `{"token":"…=:…="}` (le repli `data-requesttoken` n'a pas servi) |
|
||||||
|
| `REPORT` sur la collection (`calendar-query` filtrée sur `UID`) | **207**, en-têtes `Depth: 1` + `Content-Type: application/xml; charset=utf-8` |
|
||||||
|
| `PUT` avec `If-Match` | **204** → **le partage est en écriture** |
|
||||||
|
| `REPORT` avec un `requesttoken` **invalide** | **207 quand même** : l'endpoint DAV **n'exige pas** le jeton CSRF |
|
||||||
|
|
||||||
|
- **Collection** = URL du calendrier **sans** `?export` (d'où `URL_COLLECTION`
|
||||||
|
dérivée dans `nextcloud.js`).
|
||||||
|
- **Préfixes réels** du `multistatus` : `d:` → `DAV:`, `cal:` →
|
||||||
|
`urn:ietf:params:xml:ns:caldav` (plus `s:`, `cs:`, `oc:`, `nc:`). Ce sont des
|
||||||
|
**préfixes**, pas un contrat : sélectionner **par namespace**
|
||||||
|
(`getElementsByTagNameNS`), jamais par préfixe.
|
||||||
|
- ⚠️ **Le nom de la ressource n'est PAS l'UID** : l'événement d'UID
|
||||||
|
`db2d146b-8e18-…` vit dans `…/00D03211-DA2E-4D06-AAAE-6E8C2355BA2B.ics`.
|
||||||
|
Déduire l'URL en `<uid>.ics` **aurait échoué** — d'où le `REPORT` préalable.
|
||||||
|
- `href` du `multistatus` = chemin **absolu depuis la racine**
|
||||||
|
(`/remote.php/dav/…`) → à résoudre contre la collection.
|
||||||
|
- `getetag` est rendu **avec ses guillemets** (`"8ee26a8f…"`) : `If-Match` le
|
||||||
|
reprend **verbatim**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Cible : formulaire mairie (Gravity Forms, form id = 6)
|
## 2. Cible : formulaire mairie (Gravity Forms, form id = 6)
|
||||||
|
|||||||
Reference in New Issue
Block a user