Rayan Habib b2a0fcce1d fix(url-sync): don't overwrite URL position params on initial load (#2580)
* fix(url-sync): don't overwrite URL position params on initial load

Race condition: setupUrlStateSync() called debouncedUrlSync() immediately,
but applyInitialUrlState() had already started an async flyTo via setCenter().
At ~250ms the debounce fired while the flyTo was still running, reading the
map's default center (lat=20, lon=0, zoom=1) and writing it back over the
URL-specified params.

Fix: skip the immediate debouncedUrlSync() call when URL position params are
present. onStateChanged fires on moveend (after flyTo completes) and handles
the first URL write at that point with the correct position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(url-sync): pass URL zoom to setView and always apply URL lat/lon

When the URL contains ?view=global&zoom=1.00, setView was called without
a zoom override, so flyTo animated to the preset zoom (1.5) instead.
The subsequent moveend/onStateChange cycle then wrote zoom=1.50 back to
the URL, overwriting the correct zoom=1.00.

Pass the URL zoom as the second argument to setView so flyTo targets the
correct zoom level.

Also remove the effectiveZoom > 2 guard from the lat/lon branch so URL
lat/lon is always honoured regardless of zoom level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(url-sync): implement setView(view, zoom?) across all map implementations

DeckGLMap, GlobeMap, Map, and MapContainer setView() only accepted a
single view argument — the zoom passed from panel-layout was silently
ignored, so ?view=X&zoom=Y still wrote the preset zoom back to the URL
after moveend.

- DeckGLMap: pass zoom ?? preset.zoom into flyTo
- GlobeMap: apply same deck.gl→altitude mapping as setCenter when zoom provided
- Map (SVG): use zoom ?? settings.zoom for the SVG map zoom state
- MapContainer: forward the optional zoom to all underlying implementations

* fix(url-sync): forward zoom on SVG path + tighten urlHasAsyncFlyTo guard

P1: MapContainer was forwarding zoom to DeckGL/Globe but not to the SVG
fallback renderer — ?view=X&zoom=Y was still loading at preset zoom on
mobile/WebGL-fallback.

P2: urlHasPosition fired on any single URL param including lone ?lat or
?lon. applyInitialUrlState only calls setCenter when both are present,
so malformed links like ?lat=41 skipped canonicalization and left stale
params in the URL until the user moved the map.

Fix: rename to urlHasAsyncFlyTo and only suppress the initial debounced
sync when applyInitialUrlState will actually trigger an async operation:
  - view is set (setView → flyTo)
  - both lat AND lon are set (setCenter → flyTo)
  - zoom is set (setZoom → animated zoom)

* fix(url-sync): remove view from urlHasAsyncFlyTo suppression guard

view was included in the suppression condition but this breaks two
renderer paths:
- SVG/Map: setView() is synchronous and fires emitStateChange before
  the URL-sync listener is installed (App.ts wires listeners after
  applyInitialUrlState runs). No async callback ever fires to drive
  the URL write when suppressed.
- GlobeMap: onStateChanged() is a no-op stub — suppressing the
  initial debounce leaves /?view=X deep links without any URL sync
  on the globe renderer.

view state is written synchronously at the top of every setView()
implementation (this.state.view = view), so the 250ms debounced read
is always correct regardless of renderer. Remove view from the guard.

Only suppress for the two cases where an in-flight flyTo makes
getCenter() return stale intermediate coordinates:
  (a) lat+lon pair present → setCenter() flyTo
  (b) zoom-only without a view preset → setZoom() animated zoom

* fix(url-sync): eagerly cache target center+zoom in DeckGLMap.setView

DeckGLMap.setView() started a flyTo without updating this.state.zoom
or this.pendingCenter. The 250ms URL debounce would then read:
  - state.zoom  → old cached value (wrong zoom written to URL)
  - getCenter() → maplibreMap.getCenter() mid-animation (intermediate
                  coords written to URL, e.g. lat=22.1 instead of 25.0)

Fix: at the top of setView(), before flyTo starts:
  - Set this.state.zoom = zoom ?? preset.zoom  (zoom correct immediately)
  - Set this.pendingCenter = preset lat/lon     (center correct immediately)
  - getCenter() returns pendingCenter when set, falls through to live
    maplibre coords after moveend clears it

This ensures any URL sync that fires during the flyTo animation (whether
the initial 250ms debounce or an onStateChanged triggered by the sync
setView call itself) reads the correct destination values.

* test(url-sync): regression coverage for initial URL-sync suppression + DeckGLMap.pendingCenter

20 tests across three suites:
- urlHasAsyncFlyTo guard: 10 cases covering cold load, view-only, partial lat/lon,
  full lat+lon, bare zoom, view+zoom, and the setCenter override path.
- DeckGLMap.pendingCenter: 8 cases verifying eager center/zoom cache on setView,
  override zoom, consecutive calls, and moveend clearing the cache.
- Integration regression: view=mena path confirms suppression is skipped and
  pendingCenter holds correct preset coords for the URL builder.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-04-01 10:15:26 +04:00

World Monitor

Real-time global intelligence dashboard — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.

GitHub stars GitHub forks Discord License: AGPL v3 TypeScript Last commit Latest release

Web App  Tech Variant  Finance Variant  Commodity Variant  Happy Variant

Download Windows  Download macOS ARM  Download macOS Intel  Download Linux

Documentation  ·  Releases  ·  Contributing

World Monitor Dashboard


What It Does

  • 435+ curated news feeds across 15 categories, AI-synthesized into briefs
  • Dual map engine — 3D globe (globe.gl) and WebGL flat map (deck.gl) with 45 data layers
  • Cross-stream correlation — military, economic, disaster, and escalation signal convergence
  • Country Intelligence Index — composite risk scoring across 12 signal categories
  • Finance radar — 92 stock exchanges, commodities, crypto, and 7-signal market composite
  • Local AI — run everything with Ollama, no API keys required
  • 5 site variants from a single codebase (world, tech, finance, commodity, happy)
  • Native desktop app (Tauri 2) for macOS, Windows, and Linux
  • 21 languages with native-language feeds and RTL support

For the full feature list, architecture, data sources, and algorithms, see the documentation.


Quick Start

git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
npm run dev

Open localhost:5173. No environment variables required for basic operation.

For variant-specific development:

npm run dev:tech       # tech.worldmonitor.app
npm run dev:finance    # finance.worldmonitor.app
npm run dev:commodity  # commodity.worldmonitor.app
npm run dev:happy      # happy.worldmonitor.app

See the self-hosting guide for deployment options (Vercel, Docker, static).


Tech Stack

Category Technologies
Frontend Vanilla TypeScript, Vite, globe.gl + Three.js, deck.gl + MapLibre GL
Desktop Tauri 2 (Rust) with Node.js sidecar
AI/ML Ollama / Groq / OpenRouter, Transformers.js (browser-side)
API Contracts Protocol Buffers (92 protos, 22 services), sebuf HTTP annotations
Deployment Vercel Edge Functions (60+), Railway relay, Tauri, PWA
Caching Redis (Upstash), 3-tier cache, CDN, service worker

Full stack details in the architecture docs.


Flight Data

Flight data provided gracefully by Wingbits, the most advanced ADS-B flight data solution.


Data Sources

WorldMonitor aggregates 30+ external data sources across geopolitics, finance, energy, climate, aviation, and cyber. See the full data sources catalog for providers, feed tiers, and collection methods.


Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

npm run typecheck        # Type checking
npm run build:full       # Production build

License

AGPL-3.0 for non-commercial use. Commercial license required for any commercial use.

Use Case Allowed?
Personal / research / educational Yes
Self-hosted (non-commercial) Yes, with attribution
Fork and modify (non-commercial) Yes, share source under AGPL-3.0
Commercial use / SaaS / rebranding Requires commercial license

See LICENSE for full terms. For commercial licensing, contact the maintainer.

Copyright (C) 2024-2026 Elie Habib. All rights reserved.


Author

Elie HabibGitHub

Contributors

Security Acknowledgments

We thank the following researchers for responsibly disclosing security issues:

  • Cody Richard — Disclosed three security findings covering IPC command exposure, renderer-to-sidecar trust boundary analysis, and fetch patch credential injection architecture (2026)

See our Security Policy for responsible disclosure guidelines.


worldmonitor.app  ·  docs.worldmonitor.app  ·  finance.worldmonitor.app  ·  commodity.worldmonitor.app

Star History

Star History Chart
Description
Mirrored from GitHub
Readme AGPL-3.0 382 MiB
Languages
TypeScript 49.1%
JavaScript 47%
CSS 2.9%
HTML 0.4%
Rust 0.3%
Other 0.1%