# Topo – Logique métier de l’outil éditorial (Web Edition ↔ Gitea ↔ apply-ticket ↔ CI)

Ce document explique **ce que fait réellement l’outil**, pourquoi il existe, et comment le maintenir **sans casser** la promesse centrale : **citabilité durable + édition traçable + intégration sûre**.

> Public visé : “commun des mortels” (nouveau contributeur, mainteneur occasionnel, moi dans 6 mois).

---

## 0) La promesse (le “pourquoi”)

On veut un site (Astro) où chaque paragraphe est :
- **citable** (URL + ancre stable),
- **corrigeable** (ouvrir un ticket pré-rempli depuis le paragraphe),
- **réparable de manière sûre** (appliquer la proposition dans les sources sans magie),
- **vérifié automatiquement** (tests + build + garde-fous en CI).

En bref :  
**Lecture → proposition → ticket → application → tests → build → publication**  
…sans perdre la traçabilité, ni casser les citations historiques.

---

## 1) Glossaire (pas de jargon sans définition)

- **Ancre** : partie `#...` d’une URL (ex : `#p-8-0e65838d`). Le navigateur saute à l’élément HTML portant cet `id`.
- **Paragraphe citable** : un `<p id="p-...">...</p>` dans l’article.
- **Ticket / Issue** : demande de correction sur Gitea (ex : #14).
- **PR (Pull Request)** : “demande d’ajout / fusion” : une proposition de merge d’une branche vers `master`.
- **CI** : tests automatiques exécutés sur le serveur à chaque push/PR.
- **Runner** : machine/conteneur qui exécute la CI (chez nous : DS220+ / ds220-runner).
- **DEV** : `npm run dev` (serveur Astro à la volée, pas de `dist/`).
- **BUILD / PROD-like** : `npm run build` puis servir `dist/` (ce qui ressemble à la prod).

---

## 2) Vue d’ensemble : le pipeline (la “machine éditoriale”)

### 2.1 Le flux humain (ce que fait l’éditeur)
1) Lire une page sur “Archicratie – Web Edition”.
2) Sur un paragraphe : cliquer **Proposer**.
3) Choisir **Type** (Correction / Fact-check) + **Category** (lexique, style, etc.).
4) Gitea s’ouvre en **nouvel onglet** avec un ticket pré-rempli.
5) L’éditeur écrit la proposition, justifie, et valide le ticket.

### 2.2 Le flux technique (ce que fait la machine)
6) `apply-ticket` récupère le ticket via l’API Gitea.
7) Il retrouve le bon fichier source (MDX) + le bon paragraphe (matching sûr).
8) Il applique la modification (ou refuse si trop incertain).
9) On lance `npm test` → build + test ancres + check JS inline.
10) On commit / push.
11) La CI revalide automatiquement sur le serveur.

## Lecture outillée (UI) — ce qui est “verrouillé” côté ergonomie
Au-delà de l’infra (CI/anchors/tickets), l’édition web repose sur un contrat UX précis :

### A) Panneau gauche : sommaires (TOC global + TOC local)
- Le panneau gauche reste sticky sous le header.
- Un seul scroll interne englobe :
  - TOC global (navigation chapitre)
  - TOC local (sections du chapitre courant)
- Implémentation : scroll porté par `.page-aside__scroll` (pas par des sous-blocs).

### B) Bandeau H1/H2/H3 (“reading-follow”)
- Un bandeau suit la lecture et rappelle le contexte courant.
- Il est aligné sur la largeur du reading via des variables CSS mesurées.
- Il pilote aussi l’offset réel des ancres (scroll-margin-top) pour éviter que les titres soient masqués.

### C) Outillage “par paragraphe”
Chaque paragraphe (ID `p-…`) expose :
- un lien direct (¶),
- une citation copiable,
- un bouton “Proposer” (si Gitea configuré),
- un marque-page (pinned) + un checkpoint automatique (dernier lu).

### D) Boucle “Proposer” : modal 2 étapes
Le click “Proposer” n’envoie pas directement sur Gitea :
1) Choix du type (Correction / Fact-check)
2) Choix optionnel d’une Category
Puis ouverture du ticket en nouvel onglet avec body/title mis à jour.

---

## 3) Composant A — Le site (Astro) et les outils de paragraphe

Dans le rendu final, chaque paragraphe important reçoit un `id` de forme :

- `p-<index>-<hash8>`  
  ex : `p-8-0e65838d`

Ensuite, un script ajoute des **outils de paragraphe** (“para-tools”) :
- **¶** : lien direct sur le paragraphe (ancre)
- **Citer** : copie une citation (titre + version + URL sans query-string)
- **Proposer** : ouvre la modale (2 étapes), puis ouvre le ticket Gitea

### Point crucial : progressive enhancement
- Si JS marche : UX optimale.
- Si JS casse : le site reste lisible, et le lien de proposition reste utilisable au minimum via `href`.

---

## 4) Composant B — Contrat des tickets (machine-readable)

Pour que `apply-ticket` puisse travailler, le ticket doit rester **structuré**.

Le ticket contient des lignes “clé: valeur” (une par ligne), par ex :
- `Chemin: /archicratie/prologue/`
- `URL locale: http://localhost:4321/archicratie/prologue/#p-...`
- `Ancre: #p-8-...`
- `Version: ...`
- `Type: type/correction` (ou type/fact-check)
- `State: state/recevable` (ou state/a-sourcer)
- `Category: cat/lexique` (optionnel)

**Règle d’or :** ces clés doivent rester simples, stables, sur une ligne chacune.  
Sinon, scripts + CI + auto-labeling deviennent fragiles.

---

## 5) Composant C — apply-ticket (appliquer sans magie)

`apply-ticket` fait 4 choses :

1) **Fetch** : récupère le ticket via API (FORGE_TOKEN requis).
2) **Parse** : extrait Chemin/Ancre/Proposition/(Texte actuel si présent).
3) **Match** : retrouve le paragraphe dans le fichier source (MDX) :
   - idéal : “Texte actuel (copie exacte…)” → match fort
   - sinon : match par score → si score trop faible, **refus** (sécurité)
4) **Apply** : modifie le fichier source, sans casser le reste.

### Mode sûr
- `--dry-run` : montre BEFORE/AFTER, **n’écrit rien**
- mode normal : écrit + propose ensuite `git diff`, `git add`, `git commit`

---

## 6) Composant D — Citabilité P0 : ancres, churn test, aliases

### 6.1 Le problème
Si l’ID dépend du contenu, modifier un paragraphe peut changer l’ID.  
Donc une citation historique `...#ancien` casse.

### 6.2 La solution robuste (“web native”)
On ne “résout” pas l’ancre : **on la fait exister**.

On maintient un fichier :
- `src/anchors/anchor-aliases.json`

Exemple (par page/chemin) :
en json
{
  "/archicratie/prologue/": {
    "p-8-e7075fe3": "p-8-0e65838d"
  }
}

Au build, un script injecte dans dist/.../index.html :
<span class="para-alias" id="p-8-e7075fe3"></span>
<p id="p-8-0e65838d">...</p>

Résultat :

le navigateur résout #p-8-e7075fe3 sans JS.

### 6.3 Pourquoi un fallback JS existe encore ?

En mode npm run dev, on ne passe pas par dist/, donc pas d’injection build-time.
Le fallback JS est un filet :

utile en DEV,

utile si un alias manque,

mais pas la solution principale.

## 7) Tests & garde-fous (qualité automatique)
### 7.1 npm test doit rester “simple et vrai”

Il exécute :

npm run build (génère dist)

npm run test:anchors (stabilité des ancres)

node scripts/check-inline-js.mjs (évite les scripts inline invalides)

### 7.2 test:anchors (baseline + churn)

tests/anchors-baseline.json = snapshot de référence.

check-anchors compare dist à la baseline.

Si trop de churn → échec (donc on voit la casse).

On met à jour la baseline uniquement quand on accepte consciemment la nouvelle réalité :
npm run test:anchors:update

## 8) DEV vs BUILD : comprendre sans se tromper

npm run dev → serveur Astro “live”, pas de dist/
⇒ les alias build-time n’existent pas ⇒ fallback JS peut s’activer.

npm run build && npx serve dist → rendu final “prod-like”
⇒ alias injectés ⇒ citations historiques fonctionnent sans JS.

Repère immédiat :

port 4321 = DEV

port 3000 (serve dist) = PROD-like

## 9) Rituels opérationnels (les 3 recettes)
### Recette 1 — Créer un ticket propre

Cliquer “Proposer”

Choisir Type + Category

Dans Gitea : compléter “Proposition (remplacer par)” + “Justification”

Ne pas détruire les lignes Chemin / Ancre / Type / State / Category

### Recette 2 — Appliquer un ticket
en bash :
node scripts/apply-ticket.mjs <ID> --dry-run
node scripts/apply-ticket.mjs <ID>
git diff
npm test
git add ...
git commit -m "edit: apply ticket #<ID> (<chemin>#<ancre>)"
git push

### Recette 3 — Quand un ID change (citabilité)

trouver ancien id + nouveau id

ajouter alias dans src/anchors/anchor-aliases.json

npm run build

vérifier ...#ancien → scroll sur le bon paragraphe

npm test

## 10) Règles d’or (invariants non négociables)

Ne jamais éditer dist/ à la main (artefact).

Ne jamais sacrifier l’ancre : Chemin + Ancre doivent exister.

Tickets toujours “parsables” (clés stables ligne-par-ligne).

apply-ticket doit pouvoir refuser quand c’est ambigu.

npm test doit rester “le bouton rouge” fiable.

## 11) Dépannage rapide

401 invalid token : FORGE_TOKEN absent ou mal exporté.

Header invalid value : token collé avec des caractères parasites (espaces, retours, texte autour).

Proposer ouvre 2 onglets / remplace la page : bug d’interception click → vérifier preventDefault + stopPropagation et l’“openInNewTab”.

Ancien #id ne marche plus :

en PROD-like : vérifier alias injecté + JSON

en DEV : normal que fallback JS soit requis

## 12) Où lire quoi (docs)

docs/QUICKSTART.md : démarrer vite

docs/MANUEL_REFERENCE.md : usage complet + procédures

docs/CONTRAT_TICKETS.md : format strict des issues

(ce doc) docs/TOPO_LOGIQUE_METIER.md : la logique qui relie tout