Commit graph

5 commits

Author SHA1 Message Date
228c678789 fix(lint): course entre ESLint et les fichiers temporaires de Vitest
Some checks failed
CI / javascript (push) Has been cancelled
CI / python (push) Has been cancelled
CI / mutation (push) Has been cancelled
`npm run check` échouait par intermittence sur :

  Error: ENOENT: no such file or directory, open
  'vitest.config.js.timestamp-1787150578770-50269fd6fd11e.mjs'

Vitest écrit une version transpilée de sa config à côté d'elle au démarrage,
puis la supprime aussitôt. Depuis que les vérifications tournent en parallèle,
ESLint apercevait ce fichier pendant son balayage et il avait disparu au
moment de le lire.

Course non déterministe : elle ne se déclenchait que si les deux tâches se
croisaient au mauvais instant, d'où un gate qui échoue sans raison apparente
un push sur trois — le pire mode de défaillance pour une porte de qualité,
puisqu'il pousse à utiliser --no-verify par réflexe.

Motif ajouté aux ignores. Trois exécutions consécutives vertes.
2026-08-19 16:45:04 +02:00
cf9b88cfbc chore(lint): supprime 27 avertissements permanents du hook pre-push
Some checks are pending
CI / javascript (push) Waiting to run
CI / python (push) Waiting to run
CI / mutation (push) Waiting to run
Chaque push affichait les 27 mêmes avertissements. Un gate qui répète
toujours la même chose n'est plus lu — et le jour où un VRAI avertissement
apparaît, il passe inaperçu au milieu. Les deux règles en cause étaient les
miennes et sont inadaptées à ce code.

require-await (7 occurrences, apps/api)
Inadapté à Fastify : un handler `async` sans `await` est la façon normale de
renvoyer une valeur — l'API distingue le style async du style callback avec
reply.send. Les rendre synchrones changerait la sémantique. Règle désactivée
pour l'API.

no-console (20 occurrences, apps/web/site)
Traces de diagnostic délibérées dans le site public. Les signaler à chaque
exécution sans jamais les retirer ne faisait que noyer le reste. Règle
désactivée pour les frontends, avec la valeur à restaurer si on décide un
jour de les nettoyer.

Le hook affiche désormais six lignes vertes et rien d'autre : tout ce qui
s'imprime redevient un signal.
2026-08-19 15:13:55 +02:00
f14060720b feat(admin): refonte du panneau par intention, vue Localisations, tests e2e
Some checks are pending
CI / javascript (push) Waiting to run
CI / python (push) Waiting to run
CI / mutation (push) Waiting to run
Le panneau était découpé par table SQL, pas par question que se pose l'admin.
Conséquence : « je constate ici, j'agis dans un autre onglet », et aucun moyen
de voir ce qui coince réellement.

Redécoupage — un onglet = une question
- Pilotage      : est-ce que ça tourne ? (fusionne l'ancien « Actions »)
- Groupes       : trouver et corriger
- Localisations : qu'est-ce qui coince dans le géocodage ?  ← NOUVEAU
- Journal       : que s'est-il passé ?
- LLM & coûts   : combien ça coûte ?

Pilotage : chaque chiffre problématique porte son action
- Bandeau de santé (groupes, géocodage, file, bloqués, coût) coloré par état
- Alertes actionnables : « 12 localisations en erreur » + [Examiner] +
  [Tout remettre en file], au lieu d'un mur de boutons dans un autre onglet
- Déclencheurs de traitements inline, opérations destructives repliées
- L'ancien bloc « Éléments de genre » (des centaines de mots-clés jamais
  consultés) est retiré

Localisations : la vue qui manquait totalement
L'admin ne disposait que d'actions EN MASSE sur band_locations et d'aucun moyen
de voir CE qui échouait. Débloquer un seul lieu supposait de relancer des
milliers d'appels Geoapify/Groq facturés.
- GET  /admin/api/locations          liste filtrable (statut, lieu, groupe, pays)
- POST /admin/api/locations/:id/requeue     relance UNE ligne
- PATCH /admin/api/locations/:id            saisie manuelle des coordonnées
- Diagnostic lisible sans clic : lieu brut, statut, essais, erreur réelle

Groupes : recherche d'abord
Une barre de recherche et des filtres rapides en chips remplacent les 8 champs
texte ; les filtres avancés sont repliés.

Accessibilité
- Lignes de tableau activables au clavier (role=button, tabindex, Entrée)
- Modales : Échap ferme, focus piégé, focus rendu à l'élément d'origine
- Contraste : --err (#c61a1a) échouait WCAG AA en texte sur fond sombre (3,4:1).
  Les messages d'erreur étaient difficiles à lire. Ajout de --err-text /
  --ok-text (~6,5:1) pour les usages en couleur de texte.
  Détecté par les nouveaux tests axe sur les vues rendues.
- Statuts affichés en français au lieu des valeurs brutes de la base

Tests
- 36 tests API sur les trois nouveaux endpoints (allowlist de statuts,
  bornes des coordonnées, audit, 404)
- 53 parcours Playwright sur la VRAIE app Fastify + faux pool, dans une
  topologie identique à la production (statique servi + /admin/* proxifié)
- Tests axe sur les vues RENDUES : le test jsdom existant ne voyait que la
  coquille vide du dashboard, tout étant construit en JavaScript
- e2e branché sur le hook pre-push, pas sur `check` : la boucle de
  développement reste à 6 s, le push coûte 28 s

Correction trouvée par les tests
Le routeur ne séparait pas la query string du nom de vue :
« #/locations?status=error » ne correspondait à aucun alias et retombait sur
Pilotage — les liens des alertes ne fonctionnaient pas.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 15:05:29 +02:00
366bb8630d perf(tests): porte de qualité 17 s → 6 s, mutation complète 5 min → 3 min 40
Some checks are pending
CI / javascript (push) Waiting to run
CI / python (push) Waiting to run
CI / mutation (push) Waiting to run
Passe de performance guidée par la mesure, pas par l'intuition.

Porte de qualité (`npm run check`) — la commande qui tourne des dizaines de
fois par jour :
- Les 5 vérifications sont indépendantes : exécutées en parallèle via
  scripts/check.mjs au lieu d'un enchaînement `&&` qui imposait la somme des
  durées ET 7 démarrages npm imbriqués.        14 s → 6 s
- Cache ESLint (--cache) et compilation incrémentale tsc.  16 s → 14 s
- Chemin critique restant : vitest 4,9 s, dont ~2 s de mise en place jsdom.

Tests de mutation :
- Config Vitest dédiée en pool `threads` sans ré-isolation. Mesuré sur le
  dry-run Stryker : overhead d'amorçage 12 844 ms → 1 592 ms, soit ~90 % du
  cycle par mutant.                             5 min 02 → 3 min 40
- Le mode incrémental ramène le profil complet à 12 s après une édition.

Pistes mesurées puis REJETÉES (documentées dans README-CI.md pour éviter
qu'on les retente) :
- Découper la mutation en 5 processus Stryker parallèles : 262 s contre 235 s.
  Chaque processus repaie son bac à sable et son dry-run.
- Monter `concurrency` de 6 à 16 : aucun effet (~8,5 mutants/s partout), le
  débit est borné par l'orchestration mono-processus de Stryker.
- Séparer vitest API/frontend en deux processus : aucun gain, jsdom domine.
- Pool `threads` sur la suite complète : 18,8 s contre 3,5 s. Le bon réglage
  dépend du jeu de fichiers (jsdom ou non) — d'où deux configs distinctes.

Le périmètre muté est inchangé (1511 mutants) : un score obtenu en retirant
les mutants gênants vaudrait moins que pas de score.

Corrections au passage :
- scripts/*.mjs n'était couvert par aucune section ESLint
- Les deux suites pytest ont chacune un paquet `src` : réunies dans un seul
  processus, le premier importé masquait l'autre. Séparées (et parallèles).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 13:49:54 +02:00
60074fb015 feat(qualité): outillage de test complet, CI locale, annulation réelle des runs
Some checks are pending
CI / javascript (push) Waiting to run
CI / python (push) Waiting to run
CI / mutation (push) Waiting to run
Le dépôt n'avait aucun test, aucun linter, aucune vérification de types.

Outillage
- ESLint 9 (flat config) sur api + les deux frontends, Ruff sur le Python
- tsc --checkJs sur l'API (pas de TypeScript, juste la vérification)
- Vitest : 401 tests JS ; pytest : 43 tests Python
- Tests de mutation (Stryker), deux profils : logique pure et API complète
- Hook pre-push `npm run check` (~17 s) — le déploiement Coolify est sur webhook,
  c'est donc la seule porte de qualité avant la mise en ligne
- Workflow Forgejo Actions prêt (inerte tant qu'aucun runner n'est enregistré)

Sécurité
- Injection SQL authentifiée dans resolve-conflict : `field` était interpolé
  dans le SET sans allowlist
- timingSafeEqual levait sur un jeton multi-octets (500 au lieu de 401)
- setErrorHandler écrasait tous les 4xx en 500
- .env.example : ADMIN_JWT_SECRET et ADMIN_SEED_* n'étaient documentés nulle part
  alors que leur absence casse toute connexion admin

Annulation réelle des crawl_run (migration 014)
- L'API posait status='error' sans que le crawler en sache rien : le process
  continuait, et son UPDATE final ne matchait plus (run réussi affiché en erreur)
- Protocole coopératif : drapeau cancel_requested lu à chaque lot, le crawler
  écrit lui-même status='cancelled'

Cohérence géographique (migration 014)
- Le trigger 013 supprimait les band_locations sans purger le point dénormalisé
- L'édition admin de lat/lon n'atteignait jamais band_locations : la carte
  ignorait la correction. Override step_order = -1, dans une transaction

Corrections
- limit/offset NaN → 500 au lieu de 400
- OPTIONS sans `return reply` (Fastify poursuivait le cycle de vie)
- listen() sans catch, cast ::text en dur sur les colonnes numériques
- /admin/api/logs ne renvoyait pas sa pagination
- a11y : sélecteur de langue annoncé comme liste vide (role=option manquant)

Nettoyage
- apps/web/quizz-site supprimé (sans rapport avec le projet)
- Code mort : openModal(), LANG_NAMES, double import, variables inutilisées
- .dockerignore ajoutés ; node_modules racine n'était pas gitignoré

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 10:05:40 +02:00