- Replace MIT with AGPL-3.0-only to enforce attribution on derivatives
- Move hardcoded Sentry DSN to VITE_SENTRY_DSN env var
- Add null-coalesce guards for i18n legend keys and SVG viewBox
- Extend Sentry ignoreErrors with 7 additional noise patterns
- Live Webcams Panel with region filters and grid/single view (#111)
- Mobile detection: width-only, touch notebooks get desktop layout (#113)
- Le Monde RSS URL fix, workbox precache HTML, panel ordering migration
- Sentry: ML timeout catch, YT player guards, maplibre noise filters
- Changelog for v2.4.0
- Remove (pointer: coarse) from mobile detection so touch-capable
desktops (e.g. ROG Flow X13) get desktop layout instead of mobile
- Define MOBILE_BREAKPOINT_PX (768) in utils and use in
isMobileDevice(); CSS @media (max-width: 768px) kept in sync
- MobileWarningModal uses isMobileDevice() for consistent behavior
Ref: https://github.com/koala73/worldmonitor/discussions/94
Co-authored-by: Cursor <cursoragent@cursor.com>
- Idle handler now clears isIdle and re-renders on user activity
(was permanently paused after 5min timeout)
- Add grid back button in single-view switcher row for mobile
(view toggle hidden at <=768px left users stuck)
- ALL grid now picks one feed per region (was showing 4x mideast)
- Jerusalem & Tehran adjacent (conflict hotspots)
- Replace broken Berlin with Paris Eiffel Tower webcam
- Switch toolbar SVG icons from stroke to fill for dark mode clarity
CI already builds AppImage for Linux — this adds the missing UI:
- linux-appimage pattern in api/download.js
- Linux UA detection and button in DownloadBanner
- i18n key added to all 13 locale files
Spaces inside HTML tags (e.g. `< span class= "trend-up" >`) caused
browsers to render raw markup text instead of elements. Fixed ~45
instances across CIIPanel, ServiceStatusPanel, and StrategicPosturePanel.
- Fix summarization race: check isModelLoaded before attempting browser
T5, fall through to cloud providers while model loads in background
- Increase modelLoadTimeoutMs 30s→600s and inferenceTimeoutMs 45s→120s
to accommodate first-time model downloads
- Use flan-t5-small (60MB) instead of flan-t5-base (250MB) in beta mode
for country brief fallback
- Skip ML summarization in country brief when model not loaded to avoid
blocking UX
- Broadcast model-loaded notifications from worker on implicit loads so
manager's loadedModels stays in sync
- Suppress ONNX CleanUnusedInitializersAndNodeArgs warnings via
ort.env.logLevel
- Fix non-monotonic progress reporting with separate warm/cold step counts
e.target can be a text node when dragging text content inside a panel.
Text nodes lack .closest(), causing TypeError. Add instanceof Element check.
Resolves Sentry P2: TypeError: o.closest is not a function
## Summary
This release introduces comprehensive localization support, transforming
WorldMonitor into a truly global intelligence platform.
### Key Features
- **Multilingual UI**: Full support for 12 languages (EN, FR, ES, DE,
IT, PT, NL, SV, RU, AR, CN, JP).
- **RTL Support**: Native right-to-left layout for Arabic and Hebrew,
including mirrored UI components and correct text alignment.
- **Regional Intelligence**:
- Dynamic feed selection based on language preference.
- Dedicated regional monitoring panels for Africa, LatAm, Middle East,
and Asia.
- Granular error handling for regionally blocked feeds.
- **AI Integration**: On-demand translation and summarization of news
items using localized prompts.
### Technical Changes
- **Refactoring**: Overhauled \src/services/i18n.ts\ and
\src/config/feeds.ts\ to support dynamic loading.
- **Styling**: Added \src/styles/rtl-overrides.css\ using logical CSS
properties for maintenance-free RTL support.
- **Cleanup**: Resolved CSS lint warnings and improved code quality.
### Verification
- **Build**: \
pm run build\ passed successfully.
- **Manual Testing**: Verified RTL layout, language switching, and
regional feed loading.
Console-activated (beta=true) mode that swaps browser summarization to
Flan-T5-small (60MB) as first provider with comparison logging against
Groq. Persists via localStorage, shows amber BETA badge in header.
- LiveNewsPanel: player.mute/unMute may not exist before onReady (WORLDMONITOR-16)
- main.ts: add /Program failed to link/ noise filter (WORLDMONITOR-18)
- Wrap updateBaseline() in try/catch inside loadNewsCategory and intel
path so IndexedDB write failures don't delete successfully fetched
and rendered news data (P1)
- Add .catch() to saveCurrentSnapshot() initial call and setInterval
callback to prevent unhandled promise rejections from IndexedDB
readwrite failures (P2)
- webkitRequestFullscreen returns void (not Promise) on Safari — use
try/catch instead of .catch() to avoid undefined.catch() throw
- Module-import beforeSend filter: only suppress when stack frames
originate from browser extensions, not by URL domain check
- withTransaction: throw on readwrite InvalidStateError after retry
instead of silently returning undefined (prevents write-drop)
- toggleFullscreen: use void .catch() for Promise-based requestFullscreen/
exitFullscreen + webkit prefix fallback for iOS Safari (WORLDMONITOR-11/13)
- Narrow /^TypeError: Failed to fetch/ to exact match (was suppressing real
API failures). Move module-import-failed to beforeSend with extension/
webview context check instead of blanket ignore (WORLDMONITOR-15)
- Guard classList?.contains and target.closest?. on event targets that may
not be Elements (WORLDMONITOR-Z/10)
- Add noise filters: Fullscreen request denied, requestFullscreen,
vc_text_indicators_context (WORLDMONITOR-12)
Prevent getProjection null crash when WebGL context is lost by tracking
webglLost flag and skipping all setProps/layer rebuild calls until restored.
Add ignoreErrors for IndexedDB iOS kills, Twitter WebView injection, and
CSP unsafe-eval from extensions.
Browser extensions intercept window.fetch causing "Failed to fetch
(gamma-api.polymarket.com)" to leak as unhandled rejection. Remove
the $ anchor so the pattern matches any suffix.
- withTransaction now returns undefined instead of throwing when
InvalidStateError persists after retry (transient browser event)
- Add .catch() to fire-and-forget cleanOldSnapshots() call
- Add NotAllowedError, InvalidAccessError, importScripts to Sentry ignoreErrors
- Add global unhandledrejection handler for YouTube IFrame API autoplay blocks
- Add onError handler to deck.gl MapboxOverlay for internal render-cycle races
- storage.ts: add withTransaction() retry wrapper for IndexedDB InvalidStateError on iOS/Safari tab backgrounding
- usa-spending.ts: add 20s AbortController timeout to prevent Safari "Load failed" on stalled POST
- App.ts: add catch to runGuarded() to prevent unhandled rejections from task runner
- main.ts: add Sentry ignoreErrors for WebGL context loss and ResizeObserver loop
- DeckGLMap.ts: add webglcontextlost/restored handlers for graceful GPU recovery
- feeds.ts: route rsshub.app feeds (NHK, MIIT, MOFCOM) through Railway proxy, switch Nikkei Asia and ECFR to Google News proxy
- finance.ts: switch Nikkei Asia to Google News proxy, remove unused railwayRss helper
Resolve instead of reject when the script fails to load (ad blocker,
network issue). Guard initializePlayer against missing YT.Player.
Prevents noisy unhandled rejection errors in Sentry.
Initializes @sentry/browser early in main.ts with environment
detection (production/preview/development). Disabled on localhost
and Tauri desktop. Traces sampled at 10%.
## Summary
- PR #97 hid the badge but the `SignalModal` kept auto-opening on new
signals — this is what the reporter was still seeing
- Gates all 5 automatic `this.signalModal?.show()` calls behind
`this.findingsBadge?.isEnabled()` so disabling Intelligence Findings
also suppresses the full-screen popup overlay and sounds
- Signal history is still recorded (`addToSignalHistory`) even when
popup is suppressed, so re-enabling the toggle shows them
Closes#89
## Test plan
- [x] Disable Intelligence Findings via PANELS toggle or right-click
- [x] Wait for signal refresh cycle — no full-screen popup should appear
- [x] Re-enable → popups resume on next signal detection
- [x] Build succeeds with no type errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Adds [La Silla Vacía](https://www.lasillavacia.com) RSS feed (`/rss`)
to the `latam` feed category
- Adds source tier entry (Tier 3 — specialty/investigative)
- Colombian independent outlet covering political power structures,
governance, and armed conflict
Ref #96
## Test plan
- [ ] Verify feed loads in LATAM news panel (content is in Spanish)
- [ ] Confirm no duplicate or broken entries in feed list
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add lasillavacia.com RSS feed to improve Latin American political
coverage. Independent Colombian investigative outlet covering governance,
armed conflict, and regional power dynamics.
Ref #96
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>