158 lines
4.5 KiB
Markdown
158 lines
4.5 KiB
Markdown
# Contrat des ancres (paragraphes opposables)
|
||
|
||
## Source de vérité du sélecteur
|
||
Le site garantit la citabilité des paragraphes via des IDs injectés sur les balises `<p>`.
|
||
|
||
**Sélecteur contractuel :**
|
||
- `.reading p[id^="p-"]`
|
||
|
||
Tout outillage (scripts, tests, docs) doit utiliser ce sélecteur comme référence.
|
||
|
||
## Ce que le test vérifie
|
||
Le test compare, page par page, la liste des IDs de paragraphes présents dans `dist/` contre une baseline versionnée.
|
||
|
||
- Ajouts d’IDs : généralement OK (nouveaux paragraphes).
|
||
- Suppressions / churn élevé : alerte (risque de casser des citations existantes).
|
||
|
||
## Fichier baseline
|
||
- `tests/anchors-baseline.json`
|
||
|
||
## Commandes
|
||
1) Générer / mettre à jour la baseline (cas intentionnel) :
|
||
- `npm run build`
|
||
- `npm run test:anchors:update`
|
||
|
||
2) Vérifier sans changer la baseline (cas normal) :
|
||
- `npm run build`
|
||
- `npm run test:anchors`
|
||
|
||
## Politique d’échec (pragmatique)
|
||
Le test échoue si le churn d’une page dépasse un seuil (défaut : 20%) sur une page “suffisamment grande”.
|
||
|
||
## Aliases build-time
|
||
- `src/anchors/anchor-aliases.json`
|
||
- `scripts/inject-anchor-aliases.mjs`
|
||
- `scripts/check-anchor-aliases.mjs`
|
||
- et rappelle : *alias = compat rétro de liens historiques sans JS*
|
||
|
||
## Ancres de section (H2) quand on utilise `<details>`
|
||
Quand un chapitre est structuré en sections repliables (`<details>`), on utilise une ancre “technique” dédiée pour garantir :
|
||
- une cible stable même si le H2 est dans un bloc replié,
|
||
- un scroll-offset correct (header + bandeau),
|
||
- une détection fiable pour le bandeau “reading-follow”.
|
||
|
||
Contrat côté HTML rendu :
|
||
- présence d’un élément de type :
|
||
- `<span class="details-anchor" id="..."></span>`
|
||
- puis, dans le même `<details>`, un H2 visible dans le body.
|
||
|
||
Contrat côté CSS :
|
||
- `.details-anchor` a `scroll-margin-top: var(--sticky-offset)`.
|
||
|
||
Contrat côté JS (EditionLayout) :
|
||
- `openDetailsIfNeeded(el)` ouvre le `<details>` parent si nécessaire avant de scroller.
|
||
|
||
## Compat “legacy hash” : `#p-<idx>-<hash>`
|
||
Un hotfix de compat existe pour les anciennes ancres de paragraphes au format :
|
||
- `#p-<index>-<8 hex>`
|
||
|
||
Si l’ID exact n’existe plus :
|
||
- on cherche le premier élément dont l’id commence par `p-<index>-`
|
||
- puis scroll avec offset.
|
||
|
||
But : éviter les “liens morts” historiques quand une régénération d’IDs a eu lieu.
|
||
|
||
Limite : c’est un fallback de dernier recours (moins déterministe qu’un alias explicite).
|
||
Le mécanisme recommandé reste : `src/anchors/anchor-aliases.json` + injection au build.
|
||
|
||
_______________________________________
|
||
|
||
Dernière mise à jour : 2026-01-29
|
||
|
||
Ce document formalise comment on évite de casser les liens profonds (URLs avec `#ancre`).
|
||
|
||
---
|
||
|
||
## 1) Principe
|
||
|
||
Les IDs d’ancres générés (ou dérivés) peuvent changer :
|
||
- réécriture
|
||
- insertion/suppression de paragraphes
|
||
- re-slug d’un titre
|
||
|
||
➡️ Donc : on ne “devine” pas un fallback par index en runtime.
|
||
➡️ On fait un aliasing **déterministe à la build**, versionné.
|
||
|
||
---
|
||
|
||
## 2) Le mapping d’alias
|
||
|
||
- Fichier versionné (ex) : `src/anchors/anchor-aliases.json`
|
||
- Format : `oldId -> newId` par page
|
||
|
||
Ex en json :
|
||
{
|
||
"/archicratie/archicrat-ia/chapitre-4/": {
|
||
"p-8-ancien": "p-8-nouveau"
|
||
}
|
||
}
|
||
|
||
## 3) Injection à la build (dans dist)
|
||
|
||
Script : scripts/inject-anchor-aliases.mjs
|
||
|
||
Moment : postbuild (après astro build, avant pagefind)
|
||
|
||
Ce script injecte dans dist/**/index.html un <span id="oldId"></span> juste avant l’élément portant id="newId".
|
||
|
||
## 4) Contrôles (incassables)
|
||
### A) Vérifier qu’on n’a pas d’IDs dupliqués en HTML
|
||
|
||
Script : scripts/audit-dist.mjs
|
||
|
||
Robustesse :
|
||
|
||
ignore <script> et <style> (évite faux positifs)
|
||
|
||
exige un espace avant id= (évite data-id=)
|
||
|
||
Exécution :
|
||
npm run audit:dist
|
||
|
||
### B) Vérifier que les aliases sont vraiment présents dans dist
|
||
|
||
Script : scripts/verify-anchor-aliases-in-dist.mjs
|
||
|
||
Exécuté dans npm test
|
||
|
||
## 5) Mise à jour assistée (si besoin)
|
||
|
||
Pour générer/mettre à jour des aliases à partir des deltas d’ancres :
|
||
npm run test:anchors:update
|
||
|
||
## 6) ⚠️ Artefacts macOS (PaxHeader / ._ / DS_Store)
|
||
|
||
Quand on transfère une archive depuis macOS, on peut importer des dossiers/fichiers parasites :
|
||
|
||
PaxHeader/
|
||
|
||
._*
|
||
|
||
.DS_Store
|
||
|
||
Ces artefacts peuvent polluer le build (Astro croit voir un “vrai” contenu MDX).
|
||
|
||
➡️ Recommandation : .dockerignore strict :
|
||
# macOS / archives
|
||
.DS_Store
|
||
._*
|
||
**/._*
|
||
PaxHeader
|
||
**/PaxHeader
|
||
|
||
# Node
|
||
node_modules
|
||
dist
|
||
|
||
# (Optionnel) Avant build sur NAS : supprimer ces dossiers si présents.
|