8.2 KiB
SPEC — Annotations éditoriales (YAML v1) + merge + anti-doublon
Objectif : permettre aux tickets (Gitea) de déposer “Références / Médias / Commentaires” dans
src/annotations/**, de façon univoque, stable, et sans régression.
0) Contexte et intention
Le site est statique. L’édition collaborative se fait via :
- un mode “proposition” (UI / modal)
- un ticket Gitea (issue) standardisé
- un script d’application côté éditeur (
apply-ticket.mjsou équivalent) - génération d’un YAML d’annotations versionné dans Git
La donnée d’annotation doit être :
- audit-able (Git)
- merge-able (sans tout casser)
- stable (IDs paragraphes / liens / médias)
- scalable (éviter YAML monstrueux à long terme)
1) Arborescence canonique
1.1 Un workKey par “ouvrage / section du site”
On veut une univocité entre :
- SiteNav (Méthode, Essai-thèse, Traité, Cas IA, Glossaire, Atlas) et
- l’arborescence annotations
Proposition canonique (workKey = route racine) :
methodearchicrat-ia(Essai-thèse ArchiCraT-IA)traiteiaglossaireatlas
1.2 Règle de stockage “v1”
Par page, un YAML unique :
src/annotations//.yml
Exemples :
-
Page :
/archicrat-ia/prologue/- slug content =
archicrat-ia/prologue - fichier :
src/annotations/archicrat-ia/prologue.yml
- slug content =
-
Page :
/traite/00-demarrage/- fichier :
src/annotations/traite/00-demarrage.yml
- fichier :
Note : “slugSansWorkKey” = la partie après
<workKey>/. S’il y a des sous-dossiers (chapitres), le chemin reflète la structure :chapitre-1/section-a.ymlsi on choisit du sharding.
2) Question “gros YAML” : page unique vs sharding par paragraphe
2.1 Option A (v1 recommandée) : 1 YAML par page
Avantages :
- simple
- peu de fichiers
- diff lisible si volume modéré
- cohérent avec un modèle “annotations par page”
Inconvénients :
- YAML peut grossir si milliers d’annotations
2.2 Option B (v2 future) : sharding par paragraphe
src/annotations///.yml
Avantages :
- fichiers petits
- merges moins conflictuels Inconvénients :
- plus de fichiers
- tooling plus complexe (indexation + merge multi-fichiers)
2.3 Recommandation de mission (sans casser l’existant)
- On démarre en Option A.
- On se garde une migration future (v2) quand le volume réel le justifie.
- On impose dès v1 : clé unique + merge déterministe + anti-doublon, ce qui rend la migration future possible.
3) Format YAML v1 (schéma complet)
3.1 Top-level
en yaml :
schema: 1
Optionnel mais recommandé (doit matcher la page)
page: "/"
meta: title: "Titre de la page (optionnel)" updatedAt: "2026-02-21T12:34:56Z" # ISO8601 updatedBy: "username" # compte editor source: kind: "ticket" id: 123 url: "https://gitea.../issues/123"
paras: "": references: [] media: [] comments: []
3.2 paras : clé = paraId (ex: p-0-d7974f88)
Chaque paragraphe peut porter 3 types d’éléments :
references
media
comments
Règle : si une section est vide, elle peut être [] ou absente. Mais pour simplifier les merges, on recommande de garder la forme canonique avec [].
4) Formats des items + clés uniques
4.1 References
4.1.1 Format
references:
- id: "ref:doi:10.1234/abcd.efgh" # clé stable (voir 4.1.2) kind: "doi" # doi | url | isbn | arxiv | hal | other label: "Titre court" target: "https://doi.org/10.1234/abcd.efgh" note: "Pourquoi c’est pertinent (optionnel)" addedAt: "2026-02-21T12:34:56Z" addedBy: "username"
4.1.2 Règle de clé unique (anti-doublon)
id doit être stable et déterministe :
doi → ref:doi:
isbn → ref:isbn:
url → ref:url:
Normalisation URL (v1) : au minimum
trim
lowercase scheme/host
retirer trailing slash si non significatif
conserver query si importante
4.1.3 Merge / précédence
Quand on merge deux listes references :
union par id (clé unique)
si même id existe des deux côtés :
conserver kind/target de l’item le plus “riche” (target non vide gagne)
concat/merge note :
si notes différentes : garder les deux en les séparant (ex: noteA + "\n---\n" + noteB)
addedAt : conserver le plus ancien
addedBy : conserver le premier (ou liste si on veut, mais v1 simple : first)
4.2 Media
4.2.1 Format
media:
- id: "media:image:sha256:abcd..." # clé stable (voir 4.2.2) type: "image" # image | video | audio | file src: "/public/media////" caption: "Légende (optionnel)" credit: "Auteur/source (optionnel)" license: "CC-BY (optionnel)" addedAt: "2026-02-21T12:34:56Z" addedBy: "username"
4.2.2 Règle de clé unique
id déterministe :
idéal : hash du fichier (sha256)
sinon : hash de type + src
v1 (si on ne calcule pas de hash fichier) :
media::
4.2.3 Merge / précédence
union par id
si collision :
garder src identique (sinon c’est un bug)
fusionner caption/credit/license selon “non vide gagne”
addedAt : plus ancien
4.3 Comments
4.3.1 Format
comments:
- id: "cmt:20260221T123456Z:username:0001" kind: "comment" # comment | question | objection | todo | validation text: "Texte du commentaire" status: "open" # open | resolved addedAt: "2026-02-21T12:34:56Z" addedBy: "username" source: kind: "ticket" id: 123
4.3.2 Clé unique
Les commentaires sont “append-only” → id peut être générée (timestamp + user + compteur)
Anti-doublon : si on ré-applique un ticket, on refuse de dupliquer un id existant.
4.3.3 Merge / précédence
union par id
collisions rares, mais si elles arrivent :
si textes différents → garder les deux (on renomme l’id du second)
5) Règles globales de merge (résumé)
Quand on applique un ticket sur un YAML existant :
vérifier schema == 1
vérifier page si présent :
doit matcher /
paras :
créer paras[paraId] si absent
pour chaque liste (references/media/comments) :
merge par id (anti-doublon)
appliquer règles de précédence (non vide gagne / concat note / append-only comments)
6) Table de correspondance “UI ticket → YAML”
Cette table permet à un successeur IA d’implémenter apply-ticket.mjs sans ambiguïté.
6.1 Champs UI minimaux
workKey (sélection implicite via page)
pagePath (ex: /archicrat-ia/prologue/)
pageSlug (ex: archicrat-ia/prologue)
paraId (ex: p-0-d7974f88)
kind :
reference
media
comment
6.2 Mapping exact
| UI kind | UI champs | YAML cible |
|---|---|---|
| reference | kind(doi/url/isbn), target, label, note | paras[paraId].references[] |
| media | type(image/video/audio/file), src, caption, credit, license | paras[paraId].media[] |
| comment | kind(comment/question/objection/todo/validation), text | paras[paraId].comments[] |
6.3 Règles de génération d’ID (implémentation)
reference.id :
doi : ref:doi:${doi}
isbn : ref:isbn:${isbn}
url : ref:url:${normalize(url)}
media.id :
media:${type}:${src}
comment.id :
cmt:${timestamp}:${user}:${counter}
7) Validation YAML (sanity)
Avant commit (et en CI) :
YAML parse OK
schema OK
page si présent cohérent
paras est un mapping
paraId match pattern : ^p-\d+-[a-f0-9]{8}$ (existant)
src media pointe dans /public/media/... (ou /media/... si on choisit un alias, mais v1 canon : /public/media/...)
8) Notes de compatibilité
Les routes “Essai-thèse” ont été migrées vers /archicrat-ia/*.
Les anciennes routes /archicratie/archicrat-ia/* peuvent exister en legacy, mais la donnée canonique d’annotation doit suivre le workKey final (archicrat-ia).
9) Ce que l’étape 9 devra implémenter
pipeline : ticket → YAML (apply-ticket)
index : build-annotations-index + check-annotations
tooling : détection médias orphelins / liens cassés
éventuellement : migration vers sharding par paragraphe (v2) si volume réel le justifie