14 KiB
Manuel de référence — Archicratie Web Edition (Site + Gitea + Runner)
Ce manuel explique comment utiliser et maintenir l’outil d’édition :
- Site Astro “Archicratie – Web Edition”
- Proposer (tickets Gitea pré-remplis)
- Apply-ticket (application semi-automatique dans le contenu)
- Contrat d’ancres (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 d’une branche vers
master. - PAT (Personal Access Token) : jeton d’accès Gitea utilisé comme “mot de passe” pour API/Git.
- Anchor / Ancre : identifiant stable d’un paragraphe (ex:
p-8-0e65838d) utilisable dans une URL#.... - Churn : variation des ancres d’une 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 d’ensemble : la boucle éditoriale (rituel)
Boucle standard (la “cadence courte”)
- Sur le site, cliquer Proposer sur un paragraphe.
- Choisir Type (Correction / Fact-check), puis Category.
- Gitea ouvre un ticket pré-rempli (Chemin/URL/Ancre + texte actuel).
- Rédiger la Proposition (remplacer par) + Justification.
- En local :
node scripts/apply-ticket.mjs <num>(d’abord--dry-run)npm run test:anchorsnpm run build
- Commit + push + PR si nécessaire.
3) Le site “Proposer / Citer / ¶” (para-tools)
Où ça vit ?
-
src/layouts/EditionLayout.astroinjecte un script qui :- ajoute “¶” (lien d’ancre), “Citer”, “Proposer”
- construit l’URL de création d’issue Gitea
- embarque si possible le paragraphe exact (sinon un extrait)
-
src/components/ProposeModal.astrogère la modal 2 étapes :- Type (correction/fact-check)
- Category (cat/style, cat/lexique, etc.)
- ouvre ensuite l’issue 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 d’embarquer “copie exacte”
- URL_HARD_LIMIT : si l’URL 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
- crée un backup
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
- compare les ancres de
-
npm run test:anchors:update- met à jour la baseline (à faire seulement quand c’est volontaire)
Important :
dist/est un artefact ; la baseline sert à mesurer, pas à “éditer dist”.
7) Garde-fou JS inline : check-inline-js
Pourquoi : éviter qu’un 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” : c’est 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 l’onglet 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:updatesi changement volontaire d’ancres.
- 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__scrollsont dans EditionLayout.astro (balise<style>), pas dansglobal.css. global.csscontient 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-asideest 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 d’ancrage (header + bandeau)
Objectif : quand on va vers #p-…, #h2…, #h3…, l’élément visé n’est 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
headeret 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 JSscrollToElWithOffset())
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-follows’aligne vialeft: var(--reading-left)width: var(--reading-width)
- JS :
- calcule
--reading-left/--reading-widthà partir dugetBoundingClientRect()dearticle.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)
- calcule
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 l’ancreCiter: copie une citation structurée :Titre (vX) — URL#ancreProposer: 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 l’ancre 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 qu’attributdata-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
- le lien a
Contrat modal :
- Interception par délégation :
document.addEventListener("click", ...)sura[data-propose] - Step 1 : choix du type (
correction/fact)- met à jour
Type:etState:dans le body - préfixe le title (
[Correction]/[Fact-check])
- met à jour
- Step 2 : choix de
Category:(ou vide)- ajoute/retire la ligne
Category: ... - ouvre l’issue en nouvel onglet (sans remplacer la page)
- ajoute/retire la ligne
Garde-fou URL :
URL_HARD_LIMIT(modal) empêche d’exploser la taille d’URL quand on tente d’injecter le texte complet dansbody=.
14) Dépannage rapide (Firefox DevTools) — “tout comprendre” sans magie
14.1 — Ouvrir la console correctement (Firefox)
- Ouvre les DevTools :
F12(ouCtrl+Shift+I) - Va dans l’onglet Console
- Tu peux exécuter du JS directement (comme tu l’as 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
proposeeturl - optionnel :
full
Si a[data-propose] = 0 :
- le script d’injection côté paragraphes n’a pas ajouté les attributs,
- ou le selector diffère (
data-proposeabsent / renommé), - ou tu n’es 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 s’ouvre (step 1),
- puis après choix step1 + step2 : nouvel onglet Gitea.
Si ça ouvre directement Gitea sans modal :
- soit
data-propose/data-urlmanquent, - soit un autre handler stoppe l’interception,
- soit le script modal n’est pas exécuté (erreur JS en console).
14.5 — Check “offsets d’ancrage”
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 l’inspecteur, sélectionne .page-aside__scroll et vérifie les styles calculés :
overflow: automax-height: ...
Si le scroll est ailleurs :
.page-asidea 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 qu’on n’a pas de règles “dangereuses” injectées dans
dist/:npm run buildrg -n 'scroll-margin-top:\s*var\(--scroll-margin-top|overflow:\s*visible\s*!important|max-height:\s*none\s*!important' dist || echo "OK"