All notable changes to StarMapper, ordered by release.
/profile/[login] now shows the cached top repos grid (up to 8, from topRepos in DB). Count badge reflects the real publicRepos value from GitHub, not the cached repo list length.@ (e.g. @ruvnet) now works the same as without. The prefix is stripped before debouncing to the search state.topReposFetchedAt is reset so the next profile load re-fetches top repos from GitHub instead of serving the outdated cache.CHANGELOG.md au build. Server Component avec rendu inline bold+code sans dépendance markdown externe. Lien ajouté dans le footer et dans la bannière d'annonce.user_repo_count_mv sur 12k lignes via Neon dépassait le statement timeout de 10s. Résolu en remontant le filtre lat IS NOT NULL avant le JOIN et en limitant les candidats à 500 avant enrichissement.w-96 avec padding augmenté pour éviter le wrapping des URLs RSS/JSON./feed/[login], le dropdown était redondant (URLs déjà affichées). Prop minimal ajoutée : toggle direct sans dropdown.NewsTimeline intégré sur /profile/[login] avec skeleton loader, bouton "Publish" conditionnel (visible uniquement si le token stocké correspond au login de la page).GET /api/feed/[login]/rss (RSS 2.0 avec <atom:link>, If-Modified-Since, réponse 304) et GET /api/feed/[login]/json (JSON Feed 1.1). Cachés 1h CDN. Subscribe link (icône RSS + "Subscribe") affiché en tête de la section News sur le profil.src/lib/github-auth.ts : vérification d'un GitHub PAT via l'API REST (/user), résultat mis en cache dans Upstash Redis 5 min. Clé de cache = préfixe SHA-256 du PAT (jamais le token brut). Fallback gracieux si Redis indisponible./api/feed/[login]/rss est comptabilisé dans page_view (type "feed_rss", slug = login). Consultable via pnpm stats:views.crypto.randomUUID()), le passe via le header x-nonce, et construit un Content-Security-Policy avec 'nonce-{n}' — supprime unsafe-inline. layout.tsx lit le nonce et l'applique aux deux scripts inline. La directive CSP statique dans next.config.ts est supprimée (gérée dynamiquement).SM_TOKEN_SECRET est défini, le middleware vérifie un cookie de session HMAC sur toutes les routes POST. Les appels curl/server-side sans cookie sont bloqués ; les navigateurs envoient automatiquement le cookie HttpOnly.rateLimit() en mode failClosed=true sur toutes les routes POST : si Redis est indisponible, retourne 503 au lieu de laisser passer silencieusement.pat:* dans Upstash sont signées via HMAC-SHA256 (CACHE_SIGN_SECRET). Un attaquant avec accès Redis ne peut pas forger de valeur sans la clé de signature. Fallback en plain string si CACHE_SIGN_SECRET absent.SET NX avant le check cooldown + création pour prévenir le TOCTOU (deux requêtes concurrentes passant le cooldown simultanément → doublon)./profile/[login]), getStoredUsername() retournait "", ce qui faisait passer isOwner à false et masquait le bouton "Publish" même pour le propriétaire du profil. handleSave résout désormais le login via GET /api.github.com/user et le stocke. "Verifying…" pendant la vérification, handleRemove efface également le username.POST_LIMITERS utilisait une comparaison exacte sur les routes POST, manquant les routes avec segments dynamiques (ex. /api/news/item/123). Remplacé par des regex./api/organic-score/refresh n'avait pas de guard server-side sur le feature flag. Guard ajouté : retourne 404 si NEXT_PUBLIC_ORGANIC_SCORE_ENABLED !== "true".CLS, FID, FCP, LCP, TTFB, INP) + validation que les champs numériques sont bien des nombres. Évite les injections de données arbitraires dans la table web_vitals.buildRss20() (XML RSS 2.0, CDATA correct, ]]> splitté en deux sections CDATA) et buildJsonFeed() (objet JSON Feed 1.1). Logique de construction découplée des routes pour testabilité.github-auth.ts, réutilisés par toutes les routes news et feed.BANNER_ID ; bumper l'ID pour le faire réapparaître sur la prochaine annonce. Header home passé en sticky pour s'empiler naturellement sous le bandeau.PostToolUse hook qui détecte la création d'une nouvelle page.tsx ou route.ts et rappelle de mettre à jour AnnouncementBanner.opacity-0) pour les users *avec* coordonnées, et visible (gris) pour ceux *sans*. Inversé : bouton toujours visible et cliquable pour les users géolocalisés, invisible (espace préservé) pour les autres.country_stats_mv créée à vide (aucun countryNormalized au moment de la création), puis jamais rafraîchie après le backfill. Ajout des commandes create:country-stats-mv / create:country-stats-mv:prod pour créer et rafraîchir la MV.repo-metrics + repo-languages). Commandes update:prod, update:local, update:local:force.DATABASE_DRIVER=standard.OrganicScorePill récupéré indépendamment du reste de la page.openIssuesCount mixte). Modal organic score affiche les deux badges séparément et cliquables (liens vers GitHub)./api/admin/calibrate-organic-score — page de debug pour comparer les scores sur un échantillon réel, accessible localement.stats/[owner]/[repo] levait une timeout Neon sur les grosses tables. Fallback gracieux : retourne les données partielles disponibles sans planter.backfill-repo-metrics.ts utilisait DATABASE_URL_LOCAL au lieu de DATABASE_URL sur les commandes :prod. Corrigé + NEXT_PUBLIC_ORGANIC_SCORE_ENABLED=true forcé.docs/organic-score.md : comparaison StarScout vs StarMapper, formule de normalisation, limites connues./profile/[login] : layout deux colonnes (panneau scrollable 2/3 + carte sticky 1/3). Données : bio, followers, repos, langages, star events trackés, contribution par pays. Profil partiel si user absent de la DB (refresh déclenché automatiquement).explore/top, explore/power, explore/nearby. Bouton "View StarMapper profile →" dans le popup stargazer.POST /api/track déclenché au chargement ; compteur de vues journalier par profil dans page_view.GET /api/geo/[owner]/[repo] : endpoint agrégé retournant les points GeoJSON d'un scan, protégé par API key HMAC. Utilisable par des outils tiers.star_event.starredAt.startTransition autour des dispatches chunk loop, useDeferredValue sur le filtre stargazers, useCallback sur les handlers carte, gating des memos coûteux, lazy-load TokenModal + SponsorsBlock, width/height sur les avatars (CLS).stargazer-cache, TTL CDN optimisé, index login superflu supprimé.setData throttlée pour éviter les frames bloquées.geocoder.ts vers une classe réutilisable. Tests unitaires ajoutés.compressToGzBase64 centralisé dans compression.ts. buildUserWritePayload extrait dans le chunk route..gitignore renforcé, timing attacks réduits.db:sync:from-neon : variantes --repo, --limit, --tables pour sync partiel. SET statement_timeout=0 ajouté en tête de tous les scripts DDL (index + MV). Slow query logger Prisma activé.user_repo_count_mv (per-user repo count, nearby query 6s→200ms). Index GIN trigram sur login + name (ILIKE search 6s→50ms).chunk route + github qui échouaient silencieusement..gitignore durci, timing side-channels réduits.callJawg() dans geocoder.ts envoyait le token uniquement via header x-api-key. Ajout du query param access-token requis par l'endpoint dédié starmapper.jawg.io. Sans ce param, les requêtes Jawg retournaient 401 silencieusement./api/explore/geocode reconstruisait le label manuellement avec p.city (champ inexistant dans le modèle Jawg Places). Remplacé par feature.properties.label que Jawg fournit nativement.geocoder.test.ts stubbait JAWGMAP_ACCESS_TOKEN alors que le code lit JAWG_TOKEN_HEADER. 7 tests pré-existants échouaient silencieusement. Corrigé.README.md et docs/ARCHITECTURE.md. Jawg Places est basé sur Pelias mais la marque correcte est Jawg Places.stargazer-map.tsx (30 lignes, sans cache) et une version dans lib/map-style.ts (avec cache). lib/map-style.ts est désormais la source unique. La fonction accepte un paramètre projection ("mercator" | "globe", défaut "mercator") avec clé de cache composite ${url}#${projection} pour éviter les collisions globe/mercator.lang=en retiré des URLs de style dans theme.ts et stargazer-map.tsx (Jawg gère la langue nativement). Patch glyphs URL retiré (lib/map-style.ts, stargazer-map.tsx). Remplacement name:fr → name:en retiré (map-style.ts, stargazer-map.tsx).api.jawg.io vers starmapper.jawg.io (endpoint dédié StarMapper). Token migré de JAWGMAP_ACCESS_TOKEN vers JAWG_TOKEN_HEADER. Ajout du header x-api-key.scripts/ utilisent désormais node:util parseArgs avec strict: true au lieu des patterns ad-hoc process.argv.includes / getArg. parseArgs est natif Node 18+, sans dépendance./devs/atlas : carte choroplèthe mondiale affichant le langage le plus populaire par pays, calculé sur les repos étoilés et contribués. Détail par pays au clic (langage dominant, %, nombre de devs). Bandeau "Early preview" le temps du backfill./devs et /devs/[language] : carte des développeurs filtrée par langage, avec combobox de sélection.backfill-languages.ts pour alimenter le champ languages[] sur github_user. Mode --from-cache : dérive les langages depuis star_event + badge_cache sans appel GitHub (1,23M users en quelques secondes). Mode API : parallélisable via --token-index.pnpm db:sync et le cron admin quotidien.maxRepositories: 30 → 10 (moins de points GraphQL consommés), batch par défaut 10 → 50, bulk UPDATE via unnest() (1 requête SQL au lieu de N individuelles).github_user passait de DO NOTHING à DO UPDATE : les colonnes languages et languagesFetchedAt n'étaient jamais poussées vers Neon. Corrigé.country_language_stats_mv sur Neon lors du sync si elle n'existe pas encore.src/lib/language-colors.ts.ssr: false).sm-token (HttpOnly + SameSite=Strict), signé HMAC-SHA256 via Web Crypto API (Edge-compatible). Émis à chaque page load, vérifié sur tous les endpoints strict-get. Nécessite SM_TOKEN_SECRET. Bloque le scraping par Referer forgé même avec un cookie valide.CF-Connecting-IP avant x-forwarded-for : les limites par-IP utilisent la vraie IP visiteur derrière Cloudflare (avant : ~15 IPs fixes Cloudflare vues par Upstash)./api/stargazer-cache/* obtient son propre limiter 3 req/min au lieu de partager le pool strict-get 30/min. Un seul hit cache retourne jusqu'à 50k users./api/repos et /api/explore/global-map passent de moderate-get à strict-get (Referer + HMAC). Les deux étaient des points d'entrée d'énumération sans validation d'origine.explore/top et explore/power : MAX_SKIP=500. explore/top : filtre minimum 2 caractères pour bloquer l'énumération cross-product par caractère unique.NEXT_PUBLIC_APP_URL est absent (avant : check silencieusement ignoré).stargazer-map.tsx popup : remplacement du template literal innerHTML par createTextNode + createElement. Élimine le vecteur XSS sur le champ topLogin.unsafe-eval retiré de script-src en production (dev uniquement). Strict-Transport-Security ajouté (max-age=2y). X-Robots-Tag: noindex, nofollow sur toutes les routes /api/*.country/search dans explore/top.sanitizeError/logError dans api-helpers retire les URLs Postgres, Bearer tokens et GitHub PATs des logs serveur avant qu'ils atteignent le dashboard Vercel.explore/user-repos n'effectue plus d'écriture DB sur GET.max-w-7xl.POST /api/stargazer-cache. Résout la perte silencieuse du cache sur les repos >~15k étoiles (payload brut ~15MB > limite Vercel 4.5MB). Payload réduit à ~800KB.[object Object], artifacts XSS, templates Jinja).db.ts : DATABASE_DRIVER=standard → @prisma/adapter-pg (Docker, Railway, Supabase) ; défaut → @prisma/adapter-neon. L'auto-hébergement sans Neon fonctionne.take: 10_000), health guard TTL-aware.worker-src blob: (bloquait le web worker MapLibre → carte blanche sur certaines configs).localStorage pendant le render SSR causait React error #418. Fix : état initialisé SSR-safe, synchronisé via useEffect.cacheCheckDone.isGeocodeableLocation étendu aux préfixes #$<>[{"!.api-validation.ts, api-helpers.ts, compression.ts, compress-client.ts. Remplacement de 10 patterns dupliqués sur 15 route handlers.interface → type. import type pour les imports type-only.batch-scan.ts : écritures FLUSH_EVERY incrémentales, cache géocodage session-level, meilleure récupération sur erreur./api/badge/[owner]/[repo] — shield avec mapped count et country count, cache CDN 6h.robots.txt, sitemap.xml, FAQ structurée, Open Graph metadata./explore listant les repos mappés avec stats.stargazer_cache table) — rechargement instantané pour les visiteurs suivants sur un même repo. Limite : 100k stars.lat=null/lng=null — évite les appels API répétés pour le même garbage.isGeocodeableLocation() filtre TLDs, préfixes téléphoniques, URLs, valeurs placeholder avant tout appel.POST /api/chunk (100 users/appel) pour rester sous le timeout Vercel de 10s.geocache Neon partagée entre tous les repos — une location géocodée une fois bénéficie à tous les scans futurs.github_user + star_event tables pour tracker les utilisateurs et leurs repos au niveau utilisateur.Follows Semantic Versioning — format inspired by Keep a Changelog.