# Journal

Ce qui a été fait, quand, et surtout **ce que ça a appris**. Les décisions sont dans
[decisions/](/decisions), l'état courant dans [plan-action.md](/projet/plan-action) ; cette page garde
la trace du chemin, parce qu'un correctif sans sa raison se défait tout seul six mois plus tard.

Les chiffres cités sont mesurés. Ceux qui ne le sont pas le disent.

## 24 – 25 octobre 2026 — Le socle

Marque blanche, deux instances, moisson de 11 sources, fusion, 23 444 pages construites, version
anglaise, recherche Pagefind, observabilité. Première mesure des requêtes de référence :
**14/20 à Saclay, 16/20 à Paris 1**, attribués à la racinisation française de Pagefind.

POC d'analytics auto-hébergée (P0) : paquets, pile Docker à quatre services, 15 vérifications au
vert — mais rien n'était branché sur l'application.

## 26 octobre — Le branchement, et trois défauts qui se présentaient en succès

Le site construit n'envoyait rien. Trois causes, toutes silencieuses :

1. **`Illegal invocation`** : `fetch` conservé en champ de classe puis appelé par
   `this.fetchImpl(...)` reçoit l'adaptateur comme receveur ; le navigateur refuse. Toutes les
   vues de page étaient perdues. Un premier test, qui appelait la fonction nue, concluait que
   tout allait bien.
2. **`skipFirst`** sautait la première vue de chaque session. Sur un site à chargements complets,
   « la première » est toutes les fois.
3. Le test de fumée **mesurait le filtre antirobot** : Umami répond `200` avec le corps littéral
   `{"beep":"boop"}` à un User-Agent `HeadlessChrome`, et la lecture Rybbit traitait un tableau de
   tranches horaires comme un nombre.

Leçon, écrite dans [analytics/revue.md](/analytics/bilans/revue) : **chaque échec se présentait comme
un succès.** Un code HTTP 2xx ne prouve rien ; seule l'interrogation des données après coup
compte.

## 26 octobre — Style, catalogue, audit d'interface

`src/theme/tokens.css` devient la source unique des valeurs visuelles. Trois fonds blancs étaient
écrits en dur : une instance dont la charte n'est pas blanche obtenait un site à moitié blanc.

Storybook monté sur la même enveloppe que l'application. Audit UX établi sur le code des 16 pages
**et** sur les pages réellement servies ([audit-UXUX.md](/interface/audit-ux-ui)). Constat central : la
vitrine se parcourait, elle ne se fouillait pas — 60 projets atteignables sur 3 647, aucun filtre
nulle part, et `/recherche?q=…` jamais relu au chargement.

## 26 – 27 octobre — L'interface reprise

Correctifs d'accès (lien d'évitement, `aria-current`, carte cliquable, retour en haut, grille des
chiffres sur mobile, catégories vides masquées), recherche portée dans l'en-tête et dans l'accueil,
résultats paginés, état « zéro résultat » utile, filtres de liste, projets paginés par financeur.

**La recherche passe de 14/20 à 18/20**, sur les deux instances. Ce n'était pas la racinisation :

- l'index **répond avant d'être chargé** — `biologie` renvoie 6 fiches à froid, 88 après un
  `preload` sur `biolog`, et une requête à plusieurs mots exigeant tous les termes, un seul terme
  sous-chargé la vidait ;
- **les accents ne sont pas repliés** — `écologie` et `ecologie` sont deux mots de l'index, 12
  fiches contre 31.

Détail et mesures : [decisions/009-recherche-pagefind.md](/decisions/recherche-pagefind).

Bannière de consentement : sans elle, le mode `required` laissait le site muet.

## 27 octobre — Licence, mesure, reproductibilité, données

- **Licence MIT** ([decisions/014-licence.md](/decisions/licence)). Le dépôt se disait open
  source sans l'être : sans licence, aucun établissement tiers n'avait le droit de le reprendre.
- **Quatre événements câblés**, un seul point d'appel par geste, deux destinations : les compteurs
  Prometheus restent la mesure de référence, le paquet ajoute les dimensions sous consentement.
  `record_viewed` n'est pas émis pour les personnes — enregistrer quels chercheurs sont consultés
  dépasse l'ADR 006.
- **Reproductibilité** : `pnpm data:freeze` fige un manifeste et 15 fiches par entité dans
  `tests/reference/`. Jusque-là, « 248 structures » était invérifiable après coup.
- **Moissonneur ANR écarté après mesure** : l'open data ne couvre que **14 %** des 3 057 projets
  ANR. Mais l'URL de la fiche se déduit du code du projet — **99 % des projets ont désormais un
  lien**, sans source à maintenir.
- **`pnpm data:export`** produit les quatre tableurs que [a-faire-valider.md](/projet/a-faire-valider)
  appelait « la liste jointe » depuis le début sans qu'ils existent.

## 27 octobre — Tableau de bord de couverture

`packages/analytics-ui` et la route `/admin`, construite seulement si `NEXT_PUBLIC_ADMIN=true`.

Le validateur de palette a chiffré un réflexe : le vert et le rouge des statuts sont à **ΔE 4,1 en
deutéranopie**. Chaque statut porte donc une icône et un mot. Et il a fallu **deux jeux** de
couleurs de statut — aplats à 3:1, texte à 4,5:1 — la première version échouant à l'audit axe sur
huit badges.

Piège de rendu : les sous-composants Mantine (`Table.Thead`…) **ne traversent pas la frontière
serveur** et valent un « Element type is invalid » qui ne dit rien.

## 27 – 28 octobre — Matomo, et un faux positif de mesure

Matomo exercé depuis l'application : **5 vues côté Matomo pour 5 côté Umami**, donc aucun double
comptage, événements retrouvés par `Live.getLastVisitsDetails`.

Trouvé en route, et plus grave : `classifyOutboundUrl` comparait `url.hostname`, qui ne porte pas
le port, à `window.location.host`, qui le porte. **Hors du port 443, tout lien interne était
compté comme lien sortant.** Invisible en production, mais toute mesure de développement, de
préproduction et de test était fausse.

## 28 octobre — Déploiement Vercel

Les JSON publiés sont versionnés — 18 Mo, pas 79 : le markdown n'est lu par personne. Le build
n'est pas poussé : 6,7 Go, 287 905 fichiers, que Vercel refait en moins de quatre minutes.

Deux pièges, trouvés en servant pour de vrai un build de déploiement :

1. **`next build` fige `public/`**, et Pagefind s'exécute après — ses 23 787 fichiers n'auraient
   jamais été déployés ;
2. servi depuis `/_next/static/`, **Pagefind préfixe toutes ses URL de résultat** par cet
   emplacement : les titres restaient justes, **tous les liens menaient à un 404**. Mon premier
   essai l'avait manqué parce qu'il n'affichait que les titres.

## 29 octobre — Premier déploiement réel, et un troisième piège

Le build Vercel échouait sur « Pagefind was not able to build an index » : sur la plateforme, Next
tourne avec un adaptateur et range les pages pré-rendues sous `.next/server/route-cache/…`, une
empreinte par route source, au lieu de `.next/server/app/`. Le build local ne le montrait pas,
faute d'adaptateur. Correctif dans `scripts/vercel-build.mjs` ; reproduit puis vérifié en local
avec un adaptateur vide : **0 page trouvée avant, 4 835 après** (Paris 1).

Le build passait ensuite, mais `/recherche` affichait « index non construit » : l'index était
construit, puis jamais servi (404). Avec un adaptateur, Next remet la liste des fichiers à
déployer **à la fin de `next build`** ; tout ce qui est écrit après est perdu, et `public/`
n'entre même pas dans cette liste. Une seconde passe de build, essayée, ne changeait rien.
`scripts/pagefind-adapter.mjs` s'intercale désormais devant l'adaptateur de Vercel : il indexe le
HTML dans `onBuildComplete` et déclare l'index. Vérifié avec un adaptateur de test : **0 fichier
d'index reçu avant, 4 829 après**.

Leçon : un build local n'est pas un build de déploiement tant qu'il ne tourne pas dans les mêmes
conditions — ici, une variable d'environnement posée par la plateforme. Et « la compilation
passe » ne dit rien de ce qui est servi : seule une requête sur le site déployé le dit.

La documentation passe sur Zudoku (`docs/`, projet pnpm autonome) : sections numérotées,
navigation générée, schémas Mermaid de l'architecture et des workflows, contrôle MDX et liens
avant push.

Lefthook : formatage au commit ; avant le push, données déployables, conformité à la référence,
`make check`. Rien ne reconstruit le site.

## 29 octobre — Le branchement à CRISalid Paris-Saclay

La DiBISO ouvre un accès en lecture, temporaire, à son instance CRISalid de développement. Toute
la pile tourne, le graphe porte 18,5 millions de nœuds, et le chemin « unité → chercheurs →
publications » répond en moins d'une seconde. Mais **51 unités sur 235** seulement ont des
chercheurs rattachés, et l'enrichissement est arrêté depuis le 7 septembre.

Leçon : le chemin critique n'est plus technique, il est documentaire. CRISalid entrera champ par
champ, les sources ouvertes en repli. Suite et protocole du prochain test : [Projection](/projection).

## 3 novembre — Lucarne, et une adresse par établissement

Le projet devient **Lucarne**, « une fenêtre ouverte sur les laboratoires ». Prisme a été écarté
après vérification : plusieurs laboratoires portent ce nom, dont PRISM Sorbonne, présent dans
l'instance Paris 1. Nouveau logo, palette propre au produit, références à LPI retirées de la
documentation et du code, et documentation réorganisée en sept onglets.

Le [plan des instances](/plan) fixe une adresse par établissement, `<id>.lucarne.vicode.fr`, et
recense onze candidats parmi les membres du consortium CRISalid. Une instance servie hors du
domaine de son établissement passe en **mode démonstration** : vérifié sur un build complet de
Paris 1, `robots.txt` fermé, `noindex` et bandeau sur chaque page, dans les deux langues.

Mise en ligne le même jour : la documentation sur [lucarne.vicode.fr](https://lucarne.vicode.fr),
les instances sur [saclay.lucarne.vicode.fr](https://saclay.lucarne.vicode.fr) et
[paris1.lucarne.vicode.fr](https://paris1.lucarne.vicode.fr). Relevé à chaud : les deux instances
tournent encore sans mode démonstration ni adresse publique (sitemap sur `localhost` ou sur
l'ancienne adresse `vercel.app`) ; c'est à poser dans Vercel. Logo définitif : la loupe allumée,
nom en Merriweather Black.

Leçon : un build Next réécrit `tsconfig.json` pour y ajouter son dossier de sortie. Un build de
test dans un dossier à part laisse donc une trace à effacer.

## État mesuré au 28 octobre 2026

| Contrôle                         | Résultat                                           |
| -------------------------------- | -------------------------------------------------- |
| `make check`                     | 75 tests, au vert                                  |
| Requêtes de référence            | **18/20** à Saclay, **18/20** à Paris 1            |
| Accessibilité (axe, WCAG 2.1 AA) | 0 violation, français et anglais, `/admin` compris |
| Playwright                       | 7/7                                                |
| Fumée analytics (paquets)        | 15/15                                              |
| Fumée analytics (application)    | 12/12, Matomo compris                              |
| Secrets dans le bundle           | aucun, 11 valeurs réelles cherchées                |
| Build Vercel                     | < 4 min, 23 696 pages, liens de recherche valides  |
