Elie Habib d1e084061d fix(sw): preserve open modals when tab-hide auto-reload would fire (#3184)
* fix(sw): preserve open modals when tab-hide auto-reload would fire

Scenario: a Pro user opens the Clerk sign-in modal, enters their email,
and switches to their mail app to fetch the code. If a deploy happens
while they wait and the SW update toast's 5 s dwell window has elapsed,
`visibilitychange: hidden` triggers `window.location.reload()` — which
wipes the Clerk flow, so the code in the inbox is for a now-dead attempt
and the user has to re-request. Same failure applies to UnifiedSettings,
the ⌘K search modal, story/signal popups, and anything else with modal
semantics: leaving the tab = lose your place.

Fix: in `sw-update.ts`, the hidden-tab auto-reload now checks for any
open modal/dialog via a compound selector (`[aria-modal="true"],
[role="dialog"], .modal, .cl-modalBackdrop, dialog[open]`) and
suppresses the reload when one matches. Covers Clerk's `.cl-modalBackdrop`,
the site-wide `.modal` convention (UnifiedSettings, WidgetChatModal),
and any well-authored dialog. The reload stays armed — next tab-hide
after the modal closes fires it. Manual "Reload" button click is
unaffected (explicit user intent).

Over-matching is safe (worst case: user clicks Reload manually).
Under-matching keeps the bug, so the selector errs generous.

Tests: three new cases cover modal-open suppression, re-arming after
modal close, and manual-click bypass. 25/25 sw-update tests pass.

Follow-up ticket worth filing: add `aria-modal="true"` + `role="dialog"`
to the modals that are missing them (SearchModal, StoryModal, SignalModal,
WidgetChatModal, McpConnectModal, MobileWarningModal, CountryIntelModal,
UnifiedSettings). That's the proper long-term a11y fix and would let us
narrow the selector once coverage is complete.

* fix(sw): filter modal guard by actual visibility, not just DOM presence

Addresses review feedback on #3184:

The previous selector (`[role="dialog"]` etc.) matched the UnifiedSettings
overlay, which is created in its constructor at app startup
(App.ts:977 → UnifiedSettings.ts:68-71 sets role="dialog") and stays in
the DOM for the whole session. That meant auto-reload was effectively
disabled for every user, not just those with an actually-open modal.

Fix: don't just check for selector matches — check whether the matched
element is actually rendered. Persistent modal overlays hide themselves
via `display: none` (main.css:6744: `.modal-overlay { display: none }`)
and reveal via an `.active` class (main.css:6750: `.active { display: flex }`),
so `offsetParent === null` cleanly distinguishes closed from open. We
prefer `checkVisibility()` where available (Chrome 105+, Safari 17.4+,
Firefox 125+, which covers virtually all current WM users) and fall back
to `offsetParent` otherwise.

This also handles future modals automatically, without needing us to
enumerate every `.xxx-modal-overlay.active` class the site might
introduce.

New tests:
- Modal mounted AND visible → reload suppressed (original Clerk case)
- Modal mounted but hidden → reload fires (reviewer's regression case)
- Modal visible, then hidden on return → reload fires on next tab-hide
- Manual Reload click unaffected in all cases

26/26 sw-update tests pass.

* fix(sw): replace offsetParent fallback with getClientRects for fixed overlays

Addresses second review finding on #3184:

The previous fallback `el.offsetParent !== null` silently failed on every
`position: fixed` overlay — which is every modal in this app:

- `.modal-overlay` (main.css:6737) — UnifiedSettings, WidgetChatModal
- `.story-modal-overlay` (main.css:3442)
- `.country-intel-modal-overlay` active state (main.css:18415)

MDN: `offsetParent` is specified to return null for any `position: fixed`
element, regardless of visibility. So on Firefox <125 or Safari <17.4
(where `Element.checkVisibility()` is unavailable), `isModalOpen` would
return false for actually-open modals → auto-reload fires → Clerk sign-in
and every other fixed-position flow gets wiped exactly as PR #3184 was
meant to prevent.

Fix: fall back to `getClientRects().length > 0`. This returns 0 for
`display: none` elements (how `.modal-overlay` hides when `.active` is
absent) and non-zero for rendered elements, including position:fixed.
It's universally supported and matches the semantics we want.

New tests exercise the fallback path explicitly with a `supportsCheckVisibility`
toggle on the fake env:

- visible position:fixed modal + no checkVisibility → reload suppressed
- hidden mounted modal + no checkVisibility → reload fires

28/28 sw-update tests pass.

* fix(a11y): add role=dialog + aria-modal=true to five missing modals

Addresses third review finding on #3184.

SW auto-reload guard uses a `[role="dialog"]` selector but five modals
were missing the attribute, so `isModalOpen()` returned false and the
page could still auto-reload mid-flow on those screens. Broadening the
selector to enumerate specific class names was rejected because the app
has many non-modal `-overlay` classes (`#deckgl-overlay`,
`.conflict-label-overlay`, `.layer-warn-overlay`, `.mobile-menu-overlay`)
that would cause false positives and permanently disable auto-reload.

Instead, standardize on the existing convention used by UnifiedSettings:
every modal overlay sets `role="dialog"` + `aria-modal="true"` at
creation. This makes the SW selector work AND improves screen-reader
behavior (focus trap, background element suppression).

Modals updated:
- SearchModal (⌘K search) — both mobile sheet and desktop variants use
  the same element, single set-attributes call at create time
- StoryModal (news story detail)
- SignalModal (instability spike detail)
- CountryIntelModal (country deep-dive overlay)
- MobileWarningModal (mobile device warning)

No change to sw-update.ts — the existing selector already covers the
newly-attributed elements. All 28 sw-update tests still pass; typecheck
clean.
2026-04-18 22:54:58 +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 65+ external data sources across geopolitics, finance, energy, climate, aviation, cyber, military, infrastructure, and news intelligence. 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%