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

14 KiB
Raw Blame History

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"