Le viewport est élargi de 20 % pour précharger les bords, sans aucun bornage.
Au-delà d'un certain dézoom, la bbox sortait des limites géographiques :
zoom 2 (monde) -> -164,-88.4,144,110.4
/api/clusters rejette en 400 toute coordonnée hors [-90,90] / [-180,180].
Vérifié contre la vraie validation : les zooms 3 et 4 passent, les zooms 1 et 2
renvoient « Coordinates out of range ». L'utilisateur qui dézoomait ne voyait
donc plus aucun point, sans le moindre message.
Leaflet renvoie par ailleurs des longitudes au-delà de ±180 quand la vue
chevauche plusieurs copies du monde ; le bornage seul produirait alors un
intervalle vide, que l'API rejette aussi. On retombe dans ce cas sur la bande
complète, que la vue couvre de toute façon.
Le calcul part dans pure.js, conformément à la convention du fichier, avec des
cas limites et une propriété fast-check : aucune vue Leaflet imaginable ne doit
produire une bbox que l'API refuse. Vérifié que cette propriété échoue bien sur
l'ancienne implémentation (contre-exemple : east = 180.00000000000003).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>