- 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>
En mode animation, globalGeoStats passe les chiffres de la période entière
(depuis stats global) pour que la barre affiche le vrai taux Cesium+.
Le label indique "(période)" pour rappeler que ce n'est pas par frame.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
En mode animation, visibleTransactions ne contient que les tx géolocalisées
→ geoCount/transactionCount = 100% systématiquement, ce qui est trompeur.
La couverture Cesium+ est une propriété du pipeline global, pas d'une frame.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- activate() appelle maintenant setSpeed(1) + setPlaying(true) en plus de setActive(true)
- L'effet de reset ne stoppe playing que lors d'une désactivation (active=false),
pas lors d'une activation, pour ne pas annuler le setPlaying(true) ci-dessus
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clic sur "Personnaliser" → champ inline focusé, pré-rempli si déjà custom.
Valider avec Entrée ou blur, annuler avec Échap. Plage 1–365 jours.
Le bouton affiche la valeur courante (ex. "14 jours") quand une période
custom est active, et reprend la surbrillance dorée comme les autres boutons.
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>
Problème racine : modifier l'opacité du canvas Leaflet (qui vit dans un
pane GPU-composité) via CSS causait des désynchronisations non-déterministes.
Nouvelle approche :
- Canvas : jamais touché (opacité Leaflet par défaut)
- Deux <img> overlays se croisent : prev (sortant) et next (entrant)
- Après draw(), on attend le RAF interne de Leaflet, puis on capture
le canvas via toDataURL() dans le next img
- currentSrcRef garde l'src courante pour initialiser prev au prochain tour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le cleanup n'annulait que raf1. Si raf1 avait déjà tiré avant le cleanup React,
raf2 restait en queue et déclenchait une deuxième transition (l'aller-retour visible
à la fin de chaque frame). Fix : stocker raf2 dans la closure et l'annuler aussi.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avant : overlay se dissout mais le canvas apparaît instantanément en dessous.
Maintenant : canvas part à opacity 0, les deux transitions démarrent en même temps
→ ancienne frame fade out pendant que la nouvelle fade in simultanément.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le canvas reste toujours à opacity 1. Quand les transactions changent :
1. Capture le canvas dans l'overlay img (snap à opacity 1 sans transition)
2. Met à jour le canvas en dessous
3. Double rAF pour laisser Leaflet.heat redessiner
4. Dissout l'overlay de 1→0 en 500ms via CSS transition
Élimine le double-affichage et les conflits de transition canvas/overlay.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problème : void canvas.offsetWidth forçait un repaint avec canvas ET
overlay potentiellement visibles en même temps.
Fix : flusher uniquement l'overlay (void overlay.offsetWidth), puis
appliquer canvas=0 + overlay=1 dans le même batch de paint — Frame A
passe du canvas à l'overlay en un seul rendu sans doublon.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sans forcer un reflow, le browser ignore transition:none et applique
encore l'ancienne transition — causant un bug visuel sur la 1ère frame.
void canvas.offsetWidth flush les styles en attente.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Canvas caché (opacity 0) avant update → overlay (frame A) fade out
et canvas (frame B) fade in simultanément sur 500ms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Principe : capture du canvas heatmap actuel dans une <img> superposée
(opacity 1), mise à jour immédiate du heatmap en dessous, puis
dissolution de l'overlay (opacity 0 en 500ms). Les deux frames
coexistent pendant la transition → vrai dissolve sans clignotement.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fade out 250ms → mise à jour des données → fade in 250ms sur le canvas
Leaflet.heat. Aucun état React supplémentaire — manipulation directe
du canvas interne via _canvas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remplace les frames hebdomadaires (5 frames) par des demi-semaines
(3.5 jours, ~9-10 frames) pour une animation plus fluide sur 30 jours.
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>
Les timestamps du pool étaient figés au moment du chargement du module.
On calcule le drift entre l'heure de génération et l'heure courante,
et on le réapplique à chaque appel à getTransactionsForPeriod.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nouveau mode animation accessible via "▶ Animer" dans le sélecteur de période.
- useAnimation : hook gérant frames, lecture, vitesse, filtrage client
- AnimationPlayer : barre de contrôle (play/pause, slider, ×1/×2/×4)
- Granularité auto : 24 frames/h (24h), 7 frames/jour (7j), ~4 frames/semaine (30j)
- Stats et heatmap mis à jour sur la fenêtre courante, zéro requête réseau supplémentaire
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Déclenche le pipeline sur main, dev et ci uniquement
- main → APP_DOMAIN (domaine racine, pas de préfixe)
- dev/ci → branche.APP_DOMAIN (sous-domaine par branche)
- Dossier de déploiement isolé par branche : /opt/g1flux/<branche>/
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>
Volume total, transaction count, and top city volumes now display
↑ (green) or ↓ (red) arrows compared to the previous refresh,
making it visible that data is actually updating.
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>
- Add .catch() so failed background fetches don't silently break the interval
- Add refreshing state with a spinning ↻ on the live badge during background updates
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>