diff --git a/.claudedocs/sentry-triage/2026-02-19.md b/.claudedocs/sentry-triage/2026-02-19.md new file mode 100644 index 000000000..c079e8be4 --- /dev/null +++ b/.claudedocs/sentry-triage/2026-02-19.md @@ -0,0 +1,32 @@ +# Sentry Triage — 2026-02-19 + +Commit: `09174fd` on `main` + +## Issues Triaged (5) + +### ACTIONABLE — Fixed in Code + +| ID | Title | Events | Users | Fix | +|---|---|---|---|---| +| WORLDMONITOR-1G | `Error: ML request unload-model timed out after 120000ms` | 30 | 27 | Wrapped `unloadModel()` in try/catch; timeout no longer leaks as unhandled rejection. Cleans up `loadedModels` set on failure. | +| WORLDMONITOR-1F | `Error: ML request unload-model timed out after 120000ms` | 9 | 9 | Same root cause as 1G (different release build hash). | +| WORLDMONITOR-1K | `TypeError: this.player.playVideo is not a function` | 1 | 1 | Added optional chaining (`playVideo?.()`, `pauseVideo?.()`) in `LiveNewsPanel.ts`. YT IFrame API player object may not have methods ready during initialization race. | + +### NOISE — Filtered + +| ID | Title | Events | Users | Filter | +|---|---|---|---|---| +| WORLDMONITOR-1J | `InternalError: too much recursion` | 1 | 1 | i18next internal `translate -> extractFromKey` cycle on Firefox 147. Added `/too much recursion/` to `ignoreErrors`. | +| WORLDMONITOR-1H | `TypeError: Cannot read properties of null (reading 'id')` | 1 | 1 | maplibre-gl internal render crash (`_drawLayers -> renderLayers`). Extended `beforeSend` regex to suppress null `id`/`type` when stack is in map chunk. | + +## Files Modified + +| File | Change | +|---|---| +| `src/services/ml-worker.ts` | `unloadModel()`: try/catch around `this.request()`, clean `loadedModels` on failure | +| `src/components/LiveNewsPanel.ts` | Optional chaining on `playVideo?.()` and `pauseVideo?.()` | +| `src/main.ts` | Added `/too much recursion/` to `ignoreErrors`; extended maplibre `beforeSend` filter for null `id`/`type` | + +## Sentry Status + +All 5 issues marked **resolved (in next release)** via API. They will auto-reopen if errors recur after deployment. diff --git a/CHANGELOG.md b/CHANGELOG.md index 465b8756f..f2435d752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ All notable changes to World Monitor are documented here. +## [2.4.0] - 2026-02-19 + +### Added + +- **Live Webcams Panel**: 2x2 grid of live YouTube webcam feeds from global hotspots with region filters (Middle East, Europe, Asia-Pacific, Americas), grid/single view toggle, idle detection, and full i18n support (#111) +- **Linux download**: added `.AppImage` option to download banner + +### Changed + +- **Mobile detection**: use viewport width only for mobile detection; touch-capable notebooks (e.g. ROG Flow X13) now get desktop layout (#113) +- **Webcam feeds**: curated Tel Aviv, Mecca, LA, Miami; replaced dead Tokyo feed; diverse ALL grid with Jerusalem, Tehran, Kyiv, Washington + +### Fixed + +- **Le Monde RSS**: English feed URL updated (`/en/rss/full.xml` → `/en/rss/une.xml`) to fix 404 +- **Workbox precache**: added `html` to `globPatterns` so `navigateFallback` works for offline PWA +- **Panel ordering**: one-time migration ensures Live Webcams follows Live News for existing users +- **Mobile popups**: improved sheet/touch/controls layout (#109) +- **Intelligence alerts**: disabled on mobile to reduce noise (#110) +- **RSS proxy**: added 8 missing domains to allowlist +- **HTML tags**: repaired malformed tags in panel template literals +- **ML worker**: wrapped `unloadModel()` in try/catch to prevent unhandled timeout rejections +- **YouTube player**: optional chaining on `playVideo?.()` / `pauseVideo?.()` for initialization race +- **Panel drag**: guarded `.closest()` on non-Element event targets +- **Beta mode**: resolved race condition and timeout failures +- **Sentry noise**: added filters for Firefox `too much recursion`, maplibre `_layers`/`id`/`type` null crashes + ## [2.3.9] - 2026-02-18 ### Added diff --git a/package.json b/package.json index fa0c01a11..adddc5ec8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "world-monitor", "private": true, - "version": "2.3.9", + "version": "2.4.0", "type": "module", "scripts": { "lint:md": "markdownlint-cli2 '**/*.md'", diff --git a/src/App.ts b/src/App.ts index 95bf63b64..4ecac680f 100644 --- a/src/App.ts +++ b/src/App.ts @@ -2253,6 +2253,14 @@ export class App { panelOrder.unshift('live-news'); } + // live-webcams MUST follow live-news (one-time migration for existing users) + const webcamsIdx = panelOrder.indexOf('live-webcams'); + if (webcamsIdx !== -1 && webcamsIdx !== panelOrder.indexOf('live-news') + 1) { + panelOrder.splice(webcamsIdx, 1); + const afterNews = panelOrder.indexOf('live-news') + 1; + panelOrder.splice(afterNews, 0, 'live-webcams'); + } + // Desktop configuration should stay easy to reach in Tauri builds. if (this.isDesktopApp) { const runtimeIdx = panelOrder.indexOf('runtime-config'); diff --git a/src/config/feeds.ts b/src/config/feeds.ts index 1cd889dd1..a79f510ff 100644 --- a/src/config/feeds.ts +++ b/src/config/feeds.ts @@ -411,7 +411,7 @@ const FULL_FEEDS: Record = { { name: 'Le Monde', url: { - en: rss('https://www.lemonde.fr/en/rss/full.xml'), + en: rss('https://www.lemonde.fr/en/rss/une.xml'), fr: rss('https://www.lemonde.fr/rss/une.xml') } }, diff --git a/src/main.ts b/src/main.ts index 3f8c568ce..c8dd2ce7a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,7 +49,7 @@ Sentry.init({ if (frames.some(f => /^(chrome|moz)-extension:/.test(f.filename ?? ''))) return null; } // Suppress maplibre internal null-access crashes (light, placement) only when stack is in map chunk - if (/this\.light is null|can't access property "type", \w+ is undefined|Cannot read properties of null \(reading '(id|type)'\)/.test(msg)) { + if (/this\.style\._layers|this\.light is null|can't access property "type", \w+ is undefined|Cannot read properties of null \(reading '(id|type)'\)/.test(msg)) { if (frames.some(f => /\/map-[A-Za-z0-9]+\.js/.test(f.filename ?? ''))) return null; } return event; diff --git a/vite.config.ts b/vite.config.ts index 21e3a4461..e6b809a75 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -196,7 +196,7 @@ export default defineConfig({ }, workbox: { - globPatterns: ['**/*.{js,css,ico,png,svg,woff2}'], + globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], globIgnores: ['**/ml-*.js', '**/onnx*.wasm'], navigateFallback: '/index.html', navigateFallbackDenylist: [/^\/api\//, /^\/settings/],