- PeerDiscovery.ts : appel duniter_peerings sur rpc.duniter.org, extraction
des endpoints squid, normalisation URLs, cache localStorage 24h
- EndpointPopover : section "Réseau Ğ1" avec nœuds découverts auto-testés
à l'ouverture, bouton actualiser pour forcer un refresh du cache
- FlowMap : zone de hit des arcs réduite (max 12→4 px) pour ne plus
interférer avec le zoom/déplacement de la carte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nouveau commentParser.ts : ~80 règles regex multilingues, 11 catégories
- SubsquidAdapter : fetch du champ comment.remark depuis SubSquid
- Transaction et TransactionArc : champs comment et category
- StatsPanel : section Nature des échanges avec barres cliquables (détail inline)
- FlowMap : tooltip au survol des arcs avec répartition catégories + commentaires
- InfoPanel mis à jour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dots de statut en temps réel dans le StatsPanel (ok/slow/error + latence)
- Bannière d'alerte si un service est inaccessible
- EndpointPopover : sélection parmi nœuds connus, test de latence live, URL custom
- Rechargement automatique des données après changement d'endpoint
- SubsquidAdapter et CesiumAdapter lisent l'URL active depuis EndpointConfig
- InfoPanel mis à jour (overlay DU + statut des services)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bouton DU (gauche carte) : affiche en overlay des cercles verts
proportionnels au nombre de membres WoT actifs géolocalisés par ville.
Chargement à la demande, mis en cache 1h.
Pipeline :
SubsquidAdapter.fetchActiveMemberKeys() → isMember:true (~7000)
CesiumAdapter.resolveGeoByKeysBatched() → lots de 500 clés
DataService.fetchMemberCities() → agrégation + cache 1h
HeatMap → CircleMarkers Leaflet en overlay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pour les fromId/toId absents du keyMap WoT, applique ss58ToDuniterKey
directement pour tenter un lookup Cesium+. Les non-membres ayant un
profil géolocalisé (ex: comptes portefeuille avec ville renseignée)
apparaissent désormais dans le flux animé.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Affiche l'équivalent en DU pour le volume total et la moyenne par tx
- Taux de géolocalisation réel par frame d'animation (via allTimestamps)
- Sélecteur de période personnalisée inline à côté des boutons 24h/7j/30j
- Clic sur Animer lance la lecture automatique à vitesse ×1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SubsquidAdapter : fetchCurrentUD() interroge universalDividends (fallback 11.78 Ğ1)
- DataService : getCurrentUD() avec cache 1h, inclus dans DataResult
- StatsPanel : formatDU() + affichage "≈ X DU" sous le volume total
et "≈ X Ğ1 / tx · ≈ Y DU / tx" sous le compteur de transactions
- DU actuel Ğ1v2 : 11.78 Ğ1 (bloc 225874)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Un profil Cesium+ (clé 2QsNk...) a city:null. La contrainte
.string().optional() accepte undefined mais pas null → ZodError
silencieux dans resolveGeoByKeys → geoMap vide → 0 transactions
affichées en mode 30 jours.
Correction : .string().nullable().optional() pour title et city.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Les transferts Ğ1v1 migrés (avant le 7 mars 2026) ont des blockNumber
négatifs dans l'indexeur Subsquid. La contrainte .positive() provoquait
un ZodError silencieux qui abandonnait le fetch 30 jours et conservait
les données 7 jours en mémoire — d'où les frames vides en animation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extrait le pays depuis le champ city Cesium+ en priorité (ex: "Heusy, 4800, Belgique" → BE)
- Bounding boxes réordonnées : petits pays (LU, BE, CH, NL) avant FR pour éviter les faux positifs
- Affiche l'heure du dernier refresh sur le badge live
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restore full Cesium+ city field (including postal code), restructure
the city card so name wraps on two lines with country badge + tx count
+ volume all readable without truncation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Emoji flags render as boxes on Linux. Replace with a small FR/BE/CH
badge. Also strip postal codes from Cesium+ city names (e.g.
"Saint-Jean-de-Laur, 46260" → "Saint-Jean-de-Laur").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Determine country from geoPoint coordinates using bounding boxes
for the main Ğ1 community countries (FR, BE, CH, ES, DE, IT, ...).
Display the emoji flag before each city name in the top villes panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add totalCount to the GraphQL query so transactionCount reflects the
true number of transfers in the period, not the 2000-item fetch cap.
This also fixes the average Ğ1/tx calculation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cesium+ stores geoPoint in multiple Elasticsearch formats (object,
string "lat,lon", array [lon,lat]). Using z.object() caused a ZodError
that silently swallowed the entire Cesium+ response, leaving geoMap
empty and displaying 0 geolocalized transactions.
Replace the strict Zod schema with z.unknown() and a parseGeoPoint()
helper that normalizes all three formats. Also add [GéoFlux] debug
logs to DataService to trace keyMap/duniterKeys/geoMap pipeline steps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Au lieu de chercher par nom (title), on résout maintenant par clé :
1. buildIdentityKeyMap() : charge toutes les identités Ğ1v2 depuis Subsquid
avec leur ownerKeyChange → currentSS58 → genesisKey → duniterKey
2. ss58ToDuniterKey() : conversion SS58 v2 (préfixe 2 octets) → base58 Ed25519
= _id Cesium+ (même matériau cryptographique, encodage différent)
3. resolveGeoByKeys() : query Cesium+ par ids{} → résultat exact, pas d'ambiguïté
4. Cache keyMap 10 min : 1 requête Subsquid pour ~8000 identités, pas par refresh
Résultat : les membres migrés v1→v2 avec un profil Cesium+ sont correctement
géolocalisés même si leur nom v2 diffère de leur nom v1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CesiumAdapter : terms query en minuscules (champ title analysé par ES)
- CesiumAdapter : z.coerce.number() pour geoPoint.lat/lon (37% des profils
stockent les coordonnées en string → ZodError silencieux → 0 géolocalisées)
- CesiumAdapter : clé de la Map en toLowerCase() pour cohérence
- DataService : lookup geoMap par fromName.toLowerCase()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CesiumAdapter : utilise le champ `title` (analysé ES) au lieu de `title.keyword`
qui retournait 0 résultats ; coerce lat/lon en number (certains profils stockent des strings)
- DataService : sépare totalVolume (all tx blockchain) de geoCount (tx heatmap)
- StatsPanel : barre de couverture géo uniquement en mode live
- App : badge source "● live Ğ1v2" ou "○ mock"
- DataService.test.ts : mock SubsquidAdapter + CesiumAdapter directement (vi.mock hoistés)
pour que les tests soient déterministes quel que soit VITE_USE_LIVE_API dans .env.local
- tsconfig.app.json : exclude src/test pour éviter les erreurs de build prod
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>