Files
archicratie-edition/docs/MANUEL_REFERENCE.md
archicratia 60d88939b0
All checks were successful
CI / build-and-anchors (push) Successful in 1m25s
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (pull_request) Successful in 1m20s
Seed from NAS prod snapshot 20260130-190531
2026-01-31 10:51:38 +00:00

393 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Manuel de référence — Archicratie Web Edition (Site + Gitea + Runner)
Ce manuel explique comment utiliser et maintenir loutil dédition :
- Site Astro “Archicratie Web Edition”
- Proposer (tickets Gitea pré-remplis)
- Apply-ticket (application semi-automatique dans le contenu)
- Contrat dancres (citabilité) + tests
- CI Gitea Actions + ds220-runner (DS220+)
> Objectif : une boucle éditoriale reproductible, opposable, sûre, et testée.
---
## 0) Glossaire minimal (anti-jargon)
- **PR** (Pull Request) : demande de fusion dune branche vers `master`.
- **PAT** (Personal Access Token) : jeton daccès Gitea utilisé comme “mot de passe” pour API/Git.
- **Anchor / Ancre** : identifiant stable dun paragraphe (ex: `p-8-0e65838d`) utilisable dans une URL `#...`.
- **Churn** : variation des ancres dune page entre deux builds (mesure de stabilité).
- **dist/** : sortie buildée (artefact), jamais la “source de vérité”.
- **CI** : automatisation (build + tests) à chaque push/PR.
---
## 1) Pré-requis (poste local)
### Outils
- Node.js + npm
- Git
- (Optionnel) Python3 pour scripts de debug ponctuels
### Accès
- Compte Gitea avec droits sur le repo
- Un **PAT** Gitea
- Accès au site local (dev) et/ou build (dist)
---
## 2) Vue densemble : la boucle éditoriale (rituel)
### Boucle standard (la “cadence courte”)
1) Sur le site, cliquer **Proposer** sur un paragraphe.
2) Choisir Type (Correction / Fact-check), puis Category.
3) Gitea ouvre un ticket pré-rempli (Chemin/URL/Ancre + texte actuel).
4) Rédiger la **Proposition (remplacer par)** + Justification.
5) En local :
- `node scripts/apply-ticket.mjs <num>` (dabord `--dry-run`)
- `npm run test:anchors`
- `npm run build`
6) Commit + push + PR si nécessaire.
---
## 3) Le site “Proposer / Citer / ¶” (para-tools)
### Où ça vit ?
- `src/layouts/EditionLayout.astro` injecte un script qui :
- ajoute “¶” (lien dancre), “Citer”, “Proposer”
- construit lURL de création dissue Gitea
- embarque si possible le **paragraphe exact** (sinon un extrait)
- `src/components/ProposeModal.astro` gère la modal 2 étapes :
- Type (correction/fact-check)
- Category (cat/style, cat/lexique, etc.)
- ouvre ensuite lissue en **nouvel onglet** sans quitter le site
### Invariants (non négociables)
- **Ancre + URL + Chemin** doivent toujours être présents.
- Le mode “texte exact” est *best effort* :
- si trop long → fallback extrait + note
- priorité absolue : rester robuste et cliquable.
### Limites / règles de taille
- **FULL_TEXT_SOFT_LIMIT** : taille max pour tenter dembarquer “copie exacte”
- **URL_HARD_LIMIT** : si lURL devient trop longue → repasse en extrait
> But : éviter des issues amputées + éviter des URLs trop longues / fragiles.
---
## 4) Format des tickets (machine-readable)
Le parsing des tickets est fait par `scripts/apply-ticket.mjs` + workflow auto-label.
Donc le contenu doit rester “parsable”.
Sections attendues (au minimum) :
- `Chemin: /.../`
- `Ancre: #p-...`
- `Texte actuel (...)` (idéalement “copie exacte”, sinon “extrait”)
- `Proposition (remplacer par): ...`
- `Justification: ...` (peut être vide, mais le champ doit exister si possible)
> Voir aussi : docs/CONTRAT_TICKETS.md
---
## 5) Appliquer un ticket en local : `apply-ticket`
### Commandes
- Dry run (recommandé) :
- `node scripts/apply-ticket.mjs <N> --dry-run`
- Application réelle :
- `node scripts/apply-ticket.mjs <N>`
### Comportements de sûreté (guardrails)
- `--dry-run` :
- **aucun fichier écrit**
- **aucun backup créé**
- affiche BEFORE/AFTER (extrait)
- Mode écriture :
- crée un backup `.bak.issue-<N>` uniquement si écriture
- match “best effort” mais refuse si score insuffisant
### Après application
- `git diff -- <fichier>`
- `git add <fichier>`
- `git commit -m "edit: apply ticket #N (...)"`
---
## 6) Contrat de citabilité : ancres + churn test
### Pourquoi ?
Les ancres sont le “socle de citabilité”. On veut prévenir les liens morts.
### Scripts
- `npm run test:anchors`
- compare les ancres de `dist/` avec une baseline
- calcule le churn et signale les pages modifiées
- `npm run test:anchors:update`
- met à jour la baseline (à faire seulement quand cest volontaire)
> Important : `dist/` est un artefact ; la baseline sert à mesurer, pas à “éditer dist”.
---
## 7) Garde-fou JS inline : `check-inline-js`
Pourquoi : éviter quun JS inline cassé supprime les boutons et fasse disparaître “Proposer/Citer”.
- `node scripts/check-inline-js.mjs`
- intégré dans `npm test`
---
## 8) CI (Gitea Actions)
### Objectif
Sur chaque push/PR : vérifier automatiquement que :
- build OK
- ancres OK
- JS inline OK
### Workflow
- `.gitea/workflows/ci.yml`
### Commande “contrat”
- `npm test`
- exécute : build + anchors + inline-js
---
## 9) ds220-runner (DS220+ / Gitea act-runner)
### Comportement normal
Le runner lance des jobs dans des conteneurs éphémères qui démarrent/stop.
DSM peut notifier “conteneur arrêté de manière inattendue” : cest souvent un faux positif “bruyant”.
### À surveiller vraiment
- Jobs en échec côté Gitea Actions
- logs mentionnant :
- auth API (401)
- labels manquants
- npm ci/build en échec
---
## 10) Dépannage (symptômes → causes fréquentes)
### “Proposer” ouvre deux onglets / remplace longlet courant
Cause : double handler click / manque de `stopPropagation` / mauvais fallback.
Fix : le flux doit ouvrir **nouvel onglet uniquement**, et sinon prompt.
### “Proposer/Citer/¶” disparaissent
Cause : JS inline cassé → exception ou erreur syntaxe.
Fix : `node scripts/check-inline-js.mjs` + vérifier `dist/.../index.html` console.
### apply-ticket : “Proposition introuvable”
Cause : ticket sans section “Proposition (remplacer par):”.
Fix : respecter le contrat de ticket.
### apply-ticket : “Match trop faible”
Cause : texte actuel absent / trop tronqué / mismatch.
Fix :
- privilégier “Texte actuel (copie exacte du paragraphe)”
- sinon le script récupère via `dist` (si possible).
---
## 11) Règles de contribution (discipline de repo)
- Toute modif éditoriale passe par ticket → apply-ticket → tests.
- Toute modif structurelle (layouts, rendu, MDX) doit être suivie de :
- `npm test`
- éventuellement `npm run test:anchors:update` si changement volontaire dancres.
- Ne jamais éditer `dist/` à la main.
---
## 12) Commandes utiles (raccourcis)
- Dev : `npm run dev`
- Build : `npm run build`
- Tests : `npm test`
- Anchors : `npm run test:anchors`
- Apply ticket : `node scripts/apply-ticket.mjs <N> --dry-run`
## 13) UI de lecture outillée (EditionLayout / Reading)
Cette section documente les comportements “front” qui ont été verrouillés et qui peuvent casser si on refactorise le layout, le CSS ou les scripts inline.
### 13.1 — Structure DOM (contrat)
Le layout dédition repose sur cette structure (simplifiée) :
- `src/layouts/EditionLayout.astro`
- `<aside class="page-aside">`
- `<div class="page-aside__scroll">`
- `<slot name="aside" />` (TOC global + TOC local)
- `<article class="reading" data-pagefind-body>`
- contenu (paragraphes + headings)
- `<BuildStamp />`
- `<div id="reading-follow" class="reading-follow">` (bandeau H1/H2/H3)
- `<ProposeModal />` (dialog)
Important :
- Les styles `.page-aside` / `.page-aside__scroll` sont **dans EditionLayout.astro** (balise `<style>`), pas dans `global.css`.
- `global.css` contient les styles transverses (reading, offsets, reading-follow, tools boutons…).
### 13.2 — Scrollbar “left-side” (TOC global + TOC local)
But : **un seul scroll** qui englobe tout le panneau gauche.
Contrat :
- `.page-aside` est sticky (collé sous le header).
- Le scroll est **uniquement** sur `.page-aside__scroll` :
- `max-height: calc(100vh - (...))`
- `overflow: auto`
- Sur mobile (`max-width: 860px`) :
- aside redevient “dans le flux” (plus sticky)
- plus de scroll interne
Symptôme si ça casse :
- deux scrollbars (aside + un sous-bloc), ou
- le TOC local défile mais pas le global (ou inversement).
### 13.3 — Offsets dancrage (header + bandeau)
Objectif : quand on va vers `#p-…`, `#h2…`, `#h3…`, lélément visé **nest jamais caché** derrière le header ou le bandeau.
Contrat CSS (dans `src/styles/global.css`) :
- `--sticky-offset = calc(--sticky-header-h + --followbar-h)`
- `scroll-margin-top: var(--sticky-offset)` sur :
- `.reading p[id]`
- `.reading h1`, `.reading h2[id]`, `.reading h3[id]`
- `.details-anchor`, `.para-alias` (ancres techniques)
Contrat JS (inline dans `EditionLayout.astro`) :
- mesure `header` et met à jour `--sticky-header-h`
- mesure la hauteur réelle du bandeau et met à jour :
- `--followbar-h` (CSS)
- `--sticky-offset-px` (utilisé par les scroll JS `scrollToElWithOffset()`)
### 13.4 — Bandeau “reading-follow” (H1/H2/H3)
But : afficher un rappel de contexte (H1/H2/H3) aligné sur la largeur du reading, avec navigation.
Contrat :
- CSS : `.reading-follow` saligne via
- `left: var(--reading-left)`
- `width: var(--reading-width)`
- JS :
- calcule `--reading-left` / `--reading-width` à partir du `getBoundingClientRect()` de `article.reading`
- maintient létat courant H2/H3 (y compris dans des `<details>` ouverts/fermés)
- expose 2 actions :
- “haut du chapitre” (H1)
- “haut de la section” (H2 courant)
Note : une micro-hystérésis (`HYST`) évite le clignotement quand on scroll à la limite.
### 13.5 — Outils par paragraphe (¶ / Citer / Proposer / Marque-page)
Le script inline injecte, pour chaque `.reading p[id^="p-"]`, une UI de tools.
Boutons :
- `¶` : lien direct vers lancre
- `Citer` : copie une citation structurée : `Titre (vX) — URL#ancre`
- `Proposer` : si Gitea configuré, ouvre un ticket pré-rempli (via modal)
- `Marque-page` : épingle un “pinned bookmark”
Stockage local :
- pinned : `archicratie:bookmark:pinned`
- dernier lu (par page) : `archicratie:bookmark:last:<pathname>`
Le bouton header “Reprendre la lecture” (`#resume-btn`) :
- se base sur pinned, sinon sur last
- si on est déjà sur la même page, il scroll vers lancre au bon offset (sans recharger)
### 13.6 — Boucle “Proposer” + modal (2 étapes)
Fichiers :
- génération du lien : `src/layouts/EditionLayout.astro` (inline script)
- modal + logique : `src/components/ProposeModal.astro`
Contrat côté lien “Proposer” :
- le lien DOIT porter :
- `data-propose="1"` (ou présent en tant quattribut `data-propose`)
- `data-url="<URL Gitea issues/new?...>"`
- optionnel : `data-full="<texte complet>"` (permet upgrade du body)
- fallback “sans JS” :
- le lien a `target="_blank"` et ouvre quand même Gitea
Contrat modal :
- Interception par délégation : `document.addEventListener("click", ...)` sur `a[data-propose]`
- Step 1 : choix du type (`correction` / `fact`)
- met à jour `Type:` et `State:` dans le body
- préfixe le title (`[Correction]` / `[Fact-check]`)
- Step 2 : choix de `Category:` (ou vide)
- ajoute/retire la ligne `Category: ...`
- ouvre lissue en **nouvel onglet** (sans remplacer la page)
Garde-fou URL :
- `URL_HARD_LIMIT` (modal) empêche dexploser la taille dURL quand on tente dinjecter le texte complet dans `body=`.
---
## 14) Dépannage rapide (Firefox DevTools) — “tout comprendre” sans magie
### 14.1 — Ouvrir la console correctement (Firefox)
- Ouvre les DevTools : `F12` (ou `Ctrl+Shift+I`)
- Va dans longlet **Console**
- Tu peux exécuter du JS directement (comme tu las fait).
### 14.2 — Check “ProposeModal est là et vivant”
À exécuter dans la console :
- Le dialog existe :
- `document.getElementById("propose-modal")`
- Le navigateur supporte bien les dialogs :
- `typeof document.getElementById("propose-modal")?.showModal`
- attendu : `"function"`
- Test affichage :
- `document.getElementById("propose-modal")?.showModal()`
- puis : `document.getElementById("propose-modal")?.close()`
### 14.3 — Check “les liens Proposer sont bien marqués”
- Combien de liens interceptables :
- `document.querySelectorAll('a[data-propose]').length`
- Inspecter un exemple :
- `document.querySelector('a[data-propose]')?.dataset`
- attendu : au minimum `propose` et `url`
- optionnel : `full`
Si `a[data-propose]` = 0 :
- le script dinjection côté paragraphes na pas ajouté les attributs,
- ou le selector diffère (`data-propose` absent / renommé),
- ou tu nes pas sur une page “reading” (pas de `.reading p[id^="p-"]`).
### 14.4 — Check “le click est bien intercepté”
Tu peux provoquer un click “manuel” via :
- `document.querySelector('a[data-propose]')?.click()`
Résultat attendu :
- le modal souvre (step 1),
- puis après choix step1 + step2 : nouvel onglet Gitea.
Si ça ouvre directement Gitea sans modal :
- soit `data-propose` / `data-url` manquent,
- soit un autre handler stoppe linterception,
- soit le script modal nest pas exécuté (erreur JS en console).
### 14.5 — Check “offsets dancrage”
Pour lire les variables calculées :
- `getComputedStyle(document.documentElement).getPropertyValue("--sticky-header-h")`
- `getComputedStyle(document.documentElement).getPropertyValue("--followbar-h")`
- `getComputedStyle(document.documentElement).getPropertyValue("--sticky-offset")`
- `getComputedStyle(document.documentElement).getPropertyValue("--sticky-offset-px")`
Symptôme si mauvais :
- clic sur une ancre -> heading/para caché derrière le header.
### 14.6 — Check “scroll du panneau gauche”
Dans linspecteur, sélectionne `.page-aside__scroll` et vérifie les styles calculés :
- `overflow: auto`
- `max-height: ...`
Si le scroll est ailleurs :
- `.page-aside` a récupéré `overflow` / `max-height` (mauvais endroit)
- ou une règle CSS globale a repris la main.
### 14.7 — Commandes terminal “sanity checks”
Toujours lancer depuis la racine du site (là où est `package.json`), ex :
- Rechercher les patterns sensibles :
- `rg -n --hidden --glob '!node_modules/**' 'page-aside__scroll|reading-follow|data-propose|sticky-offset' src`
- Après build : vérifier quon na pas de règles “dangereuses” injectées dans `dist/` :
- `npm run build`
- `rg -n 'scroll-margin-top:\s*var\(--scroll-margin-top|overflow:\s*visible\s*!important|max-height:\s*none\s*!important' dist || echo "OK"`