# ROADMAP — CI (Gitea Actions Synology) + Ancrages (aliases build-time)

But : permettre à un successeur de reprendre sans rien deviner.
Ce document décrit :
- l’état stable actuel (baseline)
- les invariants à ne pas casser
- les prochaines étapes “mission principale” (ancrages primaires robustes + CI durable)
- la méthode de debug rapide

---

## 0) État actuel (baseline VALIDÉE)

### CI (Gitea Actions)
- ✅ Job dans un container Node 22 (conforme `engines`)
- ✅ Checkout **sans actions GitHub**, depuis `workflow/event.json`
- ✅ Zéro `apt-get` dans le workflow
- ✅ `npm ci` + build + tests anchors + validation schema aliases
- ✅ Injection d’aliases au postbuild confirmée en logs

### Runner (DS220+)
- ✅ `container.network: host` dans `/data/config.yaml` du runner
- ✅ `NODE_OPTIONS=--dns-result-order=ipv4first` passé aux containers de job
- ✅ `--add-host=gitea.archicratie.trans-hands.synology.me:192.168.1.20`

Raison : le DNS du bridge Docker (127.0.0.11) est instable sur cette infra → EAI_AGAIN / ESERVFAIL (npm, debian).

Référence : `docs/CI-BASELINE.md` + `docs/CI-WORKFLOW.md` + `docs/HANDOFF-SESSION.md`.

---

## 1) Invariants (NE PAS “optimiser”)

Ces points sont des garde-fous. Si on les retire, on revient aux mêmes pannes.

1) Runner :
- garder `container.network: host` (tant que l’infra DNS bridge n’est pas corrigée)
- garder `-e NODE_OPTIONS=--dns-result-order=ipv4first`

2) Workflow :
- ne pas réintroduire `apt-get`
- ne pas dépendre de `actions/checkout@...`
- garder un container Node 22 tant que `package.json engines` impose `>=22 <23`

3) Ancrages :
- le fichier canonique : `src/anchors/anchor-aliases.json`
- injection build-time : `scripts/inject-anchor-aliases.mjs`
- test anchors : `scripts/check-anchors.mjs`
- validation schema aliases : `scripts/check-anchor-aliases.mjs`

---

## 2) Mission principale (raccrochage)

Objectif “métier” :
- préserver les liens profonds (ancrages) malgré l’édition (déplacements, insertions, corrections)
- éviter les résolutions “par index” (fragiles)
- rendre la migration d’ancrages **déterministe, versionnée, testée**

Traduction technique :
- quand un `newId` remplace un `oldId`, on versionne `oldId -> newId` **par page**
- au build, on injecte un alias DOM invisible portant l’ancien `id` avant l’élément ciblé

---

## 3) Prochains jalons (ordre recommandé)

### Jalons A — Verrouillage qualité (court terme, “béton”)
A1) CI : prouver l’injection (pas seulement “build ok”)
- ajouter un test qui parcourt `src/anchors/anchor-aliases.json` et vérifie dans `dist/<route>/index.html` :
  - présence de `<span id="oldId" ...>`
  - présence de l’élément `id="newId"`
  - et idéalement : alias placé “juste avant” la cible (proximité)

A2) CI : interdire les IDs en double (risque SEO/DOM)
- dans les pages `dist`, détecter les doublons d’attribut `id="..."`

A3) CI : artefacts / logs actionnables
- quand un test échoue : afficher `route`, `oldId`, `newId`, extrait HTML et ligne

### Jalons B — Ergonomie éditeur (moyen terme)
B1) `apply-ticket.mjs` : renforcer le mode `--alias`
- si un paragraphe est remplacé : écrire l’alias automatiquement
- si conflit : message clair “oldId déjà mappé / newId introuvable”

B2) `check-anchors.mjs` : suggestion d’aliases
- lorsqu’il détecte “removed X / added Y” avec même préfixe `p-8-...`
- générer une proposition, option `--write-aliases` (ou sortie patch)

### Jalons C — Robustesse long terme (ops)
C1) Runner : réduire le risque “host network”
- isoler le runner sur LAN (réseau dédié/pare-feu)
- limiter les labels/queues aux repos nécessaires
- documenter comment restaurer `/data/config.yaml`

C2) Versionner les décisions
- tout changement CI/runner : documenté dans `docs/` + commit (pas de “magic fix” non tracé)

---

## 4) Procédure standard (dev -> PR -> merge)

### Ajouter/modifier du contenu
1) modifier les sources (docx/import etc.)
2) si des IDs de paragraphes changent :
   - appliquer `scripts/apply-ticket.mjs --alias` si possible
   - sinon éditer `src/anchors/anchor-aliases.json` (par route)

### Vérifier en local
- `npm test`
- ou au minimum :
  - `npm run build`
  - vérifier injection : `grep -n "para-alias" dist/<route>/index.html`

### PR & merge
- une PR = un ticket logique
- CI doit passer
- merge seulement quand anchors + aliases sont cohérents

---

## 5) Debug express (quand ça casse)

### CI échoue “DNS / npm”
Symptômes typiques :
- `EAI_AGAIN`, `ESERVFAIL`, `Temporary failure resolving`
Actions :
1) vérifier runner config : `/data/config.yaml` contient bien `network: host`
2) vérifier job container : logs montrent `network="host"`
3) smoke test NAS :
   - `docker run --rm --network host mcr.microsoft.com/devcontainers/javascript-node:22-bookworm bash -lc "npm ping --registry=https://registry.npmjs.org"`

### CI échoue “EBADENGINE”
- Node pas 22 → corriger l’image du job (Node 22)

### CI échoue “MODULE_NOT_FOUND scripts/...”
- fichier non commité
- `git status --porcelain` puis `git add/commit/push`

### Injection d’alias absente
- vérifier que `postbuild` appelle bien `inject-anchor-aliases.mjs`
- vérifier que `src/anchors/anchor-aliases.json` respecte le schéma (par route)

---

## 6) Définition de “DONE” (quand on peut dire “mission accomplie”)

1) CI stable sur 30+ runs consécutifs (push + PR + merge)
2) Toute modification de paragraphes qui casse des anchors produit :
   - soit un alias automatique via tooling
   - soit un échec CI explicite (avec patch proposé)
3) Aliases injectés testés (preuve dans dist) + pas de doublons d’IDs
4) Documentation à jour (baseline + décisions + procédures)

---

## P2 — UX / Front “lecture outillée” (verrouillé)
- [x] Scroll unique dans le panneau gauche (TOC global + local).
- [x] Bandeau H1/H2/H3 aligné au reading + offsets d’ancres cohérents.
- [x] Boucle “Proposer” rétablie : modal 2 étapes (type + category) puis ouverture Gitea en nouvel onglet.
- [x] Bookmarks + reprise de lecture + citation copiable.
- [x] Procédure de diagnostic (terminal + dist + DevTools Firefox).

______________________________________

Dernière mise à jour : 2026-01-29

---

## ✅ Réalisé (socle solide)

### A) Ancrages stables (anti-casse)
- Aliasing d’ancres à la build (déterministe) :
  - mapping versionné (ex: `docs/anchor-aliases.json`)
  - injection dans `dist/**/index.html` via `scripts/inject-anchor-aliases.mjs`
- Audit dist : détection des IDs dupliqués (sans faux positifs JS/CSS) via `scripts/audit-dist.mjs`
- Vérification “aliases réellement présents dans dist” via `scripts/verify-anchor-aliases-in-dist.mjs`

### B) Qualité CI
- Suite de tests `npm test` (build + audit + anchors + inline JS)

### C) ✅ Production DS220+ (DSM 7.3) — Blue/Green
- Build Astro en image Docker (Node Debian) + runtime Nginx statique
- 2 slots :
  - `web_blue` → 127.0.0.1:8081
  - `web_green` → 127.0.0.1:8082
- Reverse Proxy DSM :
  - bascule port 8081/8082 = bascule prod (rollback ~10s)
- Variables `PUBLIC_GITEA_*` injectées au build → “Proposer” fonctionne end-to-end
- Smoke test + healthcheck opérationnels

Docs :
- `DEPLOY_PROD_SYNOLOGY_DS220.md`
- `OPS_COCKPIT.md`

---

## ⏭️ Prochaines actions “futées” (à très forte valeur)

### 1) Automatiser le cycle blue/green (sans complexifier)
- Script `scripts/deploy-slot.sh green` :
  - build --no-cache
  - up --force-recreate
  - smoke + health
  - imprime “OK pour bascule DSM vers 8082”
- Script `scripts/which-live.sh` :
  - compare 8081/8082 avec le domaine public

### 2) Durcir l’hygiène “transfert macOS → NAS”
- `.dockerignore` contre `PaxHeader/`, `._*`, `.DS_Store`
- (optionnel) script `scripts/clean-macos-artifacts.sh`

### 3) Observabilité minimale
- page “/healthz” (statique) ou check `/pagefind/pagefind.js`
- journalisation Nginx : garder 2–3 jours, rotation simple (optionnel)

### 4) Sécurité reverse proxy
- confirmer headers (HSTS si voulu, CSP si nécessaire)
- s’assurer que 8081/8082 restent local-only (127.0.0.1)

Fin.
