Skip to main content
All notable changes to World Monitor are documented here. Subscribe via RSS to stay updated.
TradeIntelligenceSearchSupply ChainMaritimeSecurityMCP
Unreleased

Changed

  • Two MCP tool pairs merged; both old names keep working (MCP server 1.44.0; 95 tools; #9432). get_energy_storage is now the eu-gas-storage, nat-gas-storage and crude-inventories datasets of get_energy_intelligence, with the same fields and units. get_internet_activity is now the traffic and ddos datasets of get_infrastructure_status, read from the seeded cache at weight 1 and open to signed-in free accounts instead of Pro only. Calls to either old name run the replacement and add a deprecation object naming it; a dataset the old tool never served is rejected. Through the old internet name the response is the cache envelope instead of {dataset, data, truncated}. get_infrastructure_status country now takes an ISO code or English name and selects exactly that country (so DE no longer matches Bangladesh); a name fragment such as united that resolves to no country now returns Invalid params before any allowance is spent, and the stale flag on both tools covers every merged source. The merge frees 682 bytes of the tools/list description budget.

Fixed

  • get_tariff_trends with a tariffs-only dataset returns the US MFN series again. The old name stays off tools/list. That call runs Pro get_tariff_averages for reporter 840. Any other dataset still returns get_price_and_debt_indicators. A free account gets the same Pro denial as a direct call to get_tariff_averages. A successful response through the old name includes a deprecation object in structuredContent that names the tool that ran. The text carries it too, except for a jmespath projection that returns an array or scalar, whose text stays the raw value. dataset: ["tariffs"] on get_price_and_debt_indicators still returns Invalid params.

Added

  • MCP get_conflict_events and get_natural_disasters filter by area and age (#9433). Both tools accept a bbox (sw_lat, sw_lon, ne_lat, ne_lon; a box with sw_lon greater than ne_lon crosses the antimeridian) or a lat, lon and radius_km circle up to 2,000 km measured as great-circle distance, plus since_days from 1 to 365. They answer questions such as “active wildfires within 100 km of Athens” or “conflict events inside 47-49N, 36-39E in the last 7 days” from the cached rows, before limit is applied. Rows without coordinates or a date are dropped and counted per list in data.geo_filter, so an empty answer is not mistaken for nothing nearby; when since_days reaches past the oldest row a list holds, geo_filter.oldest_held says so. Pro panel connections accept the same filters. Mixing bbox with the radius arguments, or passing only part of the radius, returns Invalid params before any allowance is spent. Country risk scores are not filtered. Calls without the new arguments return the same response as before.
  • MCP usage rows record requested tools we do not serve, and how each tool call ended (#9431). /mcp rows in wm_api_usage gain requested_tool: the name from a tools/call or describe_tool that asked for a tool the server does not serve, lowercased and limited to [a-z0-9_.-]{1,64}, with any other value recorded as <invalid>. It is recorded only for authenticated callers and never includes arguments. tool_error_kind, tool_result_bytes and tool_jmespath copy the error kind, the payload size before projection and whether a JMESPath projection was requested from the same call’s mcp.toolcall log line. All of them are null on REST rows and when no tool ran, and tool_name is unchanged.
  • Capital flows for every economy and every country pair: seeder, GET /api/economic/v1/get-capital-flows and MCP get_capital_flows (MCP server 1.42.0; 96 tools). About 230 economies from four free official sources, refreshed weekly; see Capital Flows methodology. Per country: IMF balance-of-payments gross inflows and outflows split into direct, portfolio and other investment (quarterly where reported, annual otherwise) with derived Forbes and Warnock (2012) surge, stop, flight and retrenchment states; BIS cross-border deposits and borrowing of non-bank residents; IMF portfolio holdings of the country’s securities; and IMF direct-investment positions. For any pair, counterpart returns both directions: bank deposits and lending with eight quarters of adjusted flows, portfolio holdings each way, and direct investment each way with both sides’ figures. Every leg says whether it is reported, not itemized by the reporting side, or not published at all. A back-test of the episode states against 16 documented crises caught 9 of the 15 with data. People changing country of residence are not a balance-of-payments flow, so wealth migration is not covered.
  • MCP get_oil_trade_series: monthly oil production and exports by country, with N-vs-N month comparisons (MCP server 1.41.0; 95 tools). Answers questions like “UAE oil exports, last 6 months vs the 6 before”, which previously failed. Covers 24 major exporters over 24 complete months from four sources reported side by side, never blended: EIA Short-Term Energy Outlook supply, each country’s own JODI-Oil export filings for every month, UN Comtrade imports declared by buyers (compared only on buyers that filed in both windows), and IMF PortWatch tanker loadings as an index, not an export total. A month a source does not cover is null, never zero, and every source reports its status and newest month. The UAE has filed nothing to JODI since 2019, so its declared figures come from EIA and buyers’ customs data.
  • US export-control lists in sanctions screening and the change feed (#9352). get_sanctions_data screen, changes_since_days and GET /api/sanctions/v1/lookup-sanction-entity now also cover six lists from the US Consolidated Screening List: the BIS Entity, Denied Persons, Unverified and Military End User lists, and the State Department’s ITAR Debarred and Nonproliferation Sanctions lists (about 5,060 active rows). Change events from these lists carry the Federal Register citation and the list page. The CSL’s Treasury rows are skipped because OFAC supplies them, and expired Denied Persons orders are dropped. Pressure scores and country counts are unchanged: they still count OFAC and SEMA only. The first run after deploy starts a baseline, so CSL changes appear from the second run.
  • MCP start_upgrade: a checkout link an agent can hand the user (MCP server 1.40.0; 94 tools; #9144). A signed-in caller passes a self-serve plan and gets back a personal checkout link bound to the account; the user pays in a browser and the tool never charges. It spends no daily allowance, so it still works after the free allowance is gone, and it runs through the same checkout path as the pricing page: an existing subscription, a pending payment or the checkout rate limit returns a structured error instead of a second session. The allowance-exhausted, upgrade-required and quota-exceeded denials now add recommendedPlan when a self-serve plan sits one step up, plus checkoutTool: "start_upgrade" for free accounts, the one caller a new checkout can serve; their messages are unchanged. The pricing page’s Free section now states the free-account MCP allowance.
  • Complete tool descriptions in MCP tools/list (MCP server 1.34.0). Since 1.5.0 the listing truncated every description to its first sentence (at most 120 bytes) and relied on describe_tool for the rest; hosts that grade or pick tools from the listing alone saw one line per tool. tools/list now ships each description whole under a 1,024-byte per-tool budget (the limit OpenAI applies to function descriptions) and a 64 KiB total, both enforced in CI, and describe_tool returns the identical entry. The 46 weakest descriptions were rewritten to state what each tool returns, how its scope differs from neighbouring tools, its access tier and API-plan weight, whether it is read-only, and the argument semantics the schema cannot express; every input-schema property now carries a description, including the 37 per-section arguments branches of get_country_brief_section. generate_forecasts is now described as what it is: a filtered read of the published forecast run, not a fresh model run.
  • Event webhooks: country.risk_surge, live-signal floor, unsubscribe, and Zapier instant triggers (#9217 follow-up). POST /api/v2/shipping/webhooks takes an eventType: the default chokepoint.disruption, now with an optional minLiveSignal so a war-zone chokepoint standing at 70 does not fire once against a threshold of 50 and go silent, and the new country.risk_surge, which fires once per surge when a monitored country’s Country Instability Index rises 15 or more points within 24 hours into the high band of 70 or more (countries narrows the scope). DELETE /api/v2/shipping/webhooks/{subscriberId} unsubscribes. Both events share the signed delivery contract; the OpenAPI webhooks entry names both in its X-WM-Event enum. The events live in one registry, shared/event-triggers.json, that the worker, the spec and the Zapier app read. Zapier 1.1.0 adds the instant triggers New Chokepoint Disruption and New Country Risk Surge on top of them, verifying each delivery’s X-WM-Signature before it reaches a Zap. Thresholds come from a 90-day backtest; see Shipping v2 API.
  • Two days of Al Jazeera in /verify and verify_claim. Al Jazeera’s own feed holds 25 entries, about ten hours of stories. Claim search now also reads its Google News site: search, which holds 100 entries over two days. The news panels and digest still use the native feed, and a story both carry counts once.
  • Deeper headline search for /verify and verify_claim, and the BBC’s UK desks. Each feed now keeps up to 100 recent entries for claim search and country headlines instead of 30: a Google News site: feed returns 100, and AP’s first 30 covered under 7 hours. Adds BBC UK (on by default in English, last in Europe) and BBC Northern Ireland (opt-in). In a simulation over 70 Google News World stories, /verify found 60, up from 53.
  • Science panel (opt-in, full dashboard). Reuters Science, Nature News, BBC Science, Guardian Science and ScienceDaily, after a check of /verify against Google News World found science stories, such as Nature’s coral study and Reuters’ Neanderthal report, in no WorldMonitor feed. Turn it on from the panel picker or Cmd+K. Its sources are on by default in English, so /verify, verify_claim and the MCP digest (science category) search them whether or not the panel is shown. The free-tier source cap picks round-robin across categories, so a default free profile now keeps Reuters Science and Nature News in place of Layoffs News and Foreign Affairs.
  • Eight major publishers in the news feeds. The New York Times, The Washington Post, The Economist and The Independent (World), Sky News (Europe), Times of Israel and Middle East Eye (Middle East), and Japan Times (Asia). Google News World leads with these outlets and no WorldMonitor feed carried them. They come through Google News site: feeds, like AP and Reuters, and are on by default in English, listed last in their categories so the free-tier source cap keeps every source it kept before; free accounts can enable them in place of others. Their headlines also reach /verify and verify_claim.
  • verify_claim MCP tool (MCP server 1.33.0, Pro, #9092). Checks a claim or headline against the live news digest: finds the clusters reporting the same event (the 8 best matches) and returns the coverage verdict over them (corroborated, single-publisher, tier4-only, unknown), the publishers with their tiers, the matched headlines, and the figures in the claim that no matched headline states. It describes coverage, not accuracy, and a claim it cannot find is reported as not in WorldMonitor’s sources, never as false. First phase of the verification and trust work (#9091).
  • /verify page (#9092). The same evidence card for people: paste a claim at worldmonitor.app/verify and see who is reporting it, whether they are independent, and which of its numbers they state. It runs in the browser over the public news digest, needs no account, and keeps the claim in the link so a result can be shared.
  • Trust page (#9093). worldmonitor.app/trust explains the coverage labels, source tiers and CRED score, shows how many stories in the latest English digest carry each label, lists every label change and accepted correction from the last seven days with the publishers behind it, and has the “Report an error” form. list-label-changes now also returns today’s counts and is public, with a per-IP limit.
  • Report an error (#9093). Readers can report a wrong label or story without an account (POST /api/news/v1/submit-error-report, rate-limited). Reports are stored for review and never applied automatically. A reviewer accepts or rejects each one at /trust/review/ with the TRUST_REVIEW_SECRET key. An accepted report adds only the reviewer’s public note to the label history; the reader’s message and email are never published. See Revision and Corrections Log.
  • Published fact-checks on /verify and in verify_claim (#9094). Below the coverage result, WorldMonitor now quotes ratings that IFCN-verified fact-checkers have published about a similar claim: “Rated “False” by PolitiFact on 3 Oct 2026”, with a link. The rating is the fact-checker’s, verbatim; WorldMonitor does not rate claims, and a rating never changes the coverage result. Strong matches only, English, newest first, disagreeing ratings side by side. Served free by GET /api/news/v1/search-fact-checks.
  • Story label history (#9093). When a story’s coverage label moves between single publisher and corroborated, the change is recorded with the publisher counts on both sides, the publishers and tiers behind the new label, the story’s CRED score, and the time. Entries are kept for seven days and served by GET /api/news/v1/list-label-changes, for every story or one story_id. The label describes coverage, not accuracy. See Revision and Corrections Log.
  • Tariff lookups for MCP agents (MCP server 1.32.0, Pro). Three new live tools wrap the Pro trade routes:
    • get_tariff_averages: the average tariff any reporter applies, as the WTO MFN series or, with a partner and/or WITS product group, the applied simple and import-weighted averages. It calls get-tariff-trends.
    • get_bilateral_tariff: one HS6 product between two countries. It calls get-bilateral-tariff.
    • get_us_import_duty: the current US duty with Section 301 and 232 measures. It calls get-us-import-duty.
    Countries can be given as 3-digit codes, ISO codes or names. Each call costs 2 units on an API plan. The cached get_tariff_trends bundle is unchanged.
  • Tariff answers say what they are (MCP). Every answer from the three live tariff tools, except an upstream outage, carries a coverage object: the dataset consulted, whether the rate is a schedule rate or the current duty, whether Section 301 and 232 duties are included, the duty types left out, and the data vintage (latestYear or the HTS release). Schedule answers for the United States point at get_us_import_duty. The tools/list descriptions of all four tariff tools now lead with the schedule-versus-current-duty distinction so it survives the 120-byte compression.
  • Partner and product-group tariff averages (GET /api/trade/v1/get-tariff-trends, Pro and API plans). partner_country and product_sector were accepted but ignored. They now select the reporter’s effectively applied average on goods from that partner and/or in one of 24 WITS product groups (HS sections plus aggregates such as Textiles or Food), simple and import-weighted, read live from UNCTAD TRAINS via World Bank WITS and cached for seven days. Requests without them still get the WTO MFN series. A call that passed a partner and expected the MFN series should drop the partner. These are schedule rates without Section 301 or 232 duties, and TRAINS data ends in 2023. See Trade Policy.
  • US import duty lookup (GET /api/trade/v1/get-us-import-duty, Pro and API plans). Returns the current US duty on one HTS product from one country. The base rate comes from the live USITC Harmonized Tariff Schedule (column 1 General, a trade-agreement rate the exporter qualifies for, or column 2). On top of it are the chapter 99 duties in force: Section 301 China (Lists 1–4A and the four-year review), the 2026 Section 301 forced-labor action, Section 301 Brazil, and Section 232 actions. Each duty is marked as applying, conditional, exempt or scheduled. An estimated total counts only the duties that apply. IEEPA duties are not reported, because they have not been collected since 2026-02-24. Product coverage is parsed from the chapter 99 U.S. notes every six hours. See Trade Policy.
  • Bilateral HS6 tariff lookup (GET /api/trade/v1/get-bilateral-tariff, Pro and API plans). Returns the MFN, partner-specific preferential, and best available applied tariff on one 6-digit HS product between two countries, read live from UNCTAD TRAINS via World Bank WITS and cached. A basis field says whether the rate is preferential, MFN, or MFN only because the importer filed no preferences that year. Specific duties are converted to ad valorem equivalents, and EU member importers are answered from the EU schedule. TRAINS excludes unilateral duties such as US Section 301 and IEEPA tariffs. See Trade Policy.
  • Pro intelligence suite — nine derived datasets (#6436, #6437, #6438, #6439, #6440, #6441, #6448, #6449, #6507). Each ships a daily or annual seeded snapshot, a REST route, and (except the vulnerability ranking list) an MCP tool: physical-vs-paper precious metals — the SGE/COMEX premium series and the divergence index computed over its bounded history; mineral production and processing concentration by country and stage, with HHI, from USGS MCS with BGS fill; the defense industrial base — World Bank MS.MIL.* capacity indicators plus SIPRI-derived five-year arms-supplier shares and concentration; demographics capability across UN WPP age structure, the UNESCO UIS / World Bank WDI education pipeline, and ILOSTAT industrial workforce; food stocks and stocks-to-use per commodity-country from USDA PSD, with a same-year FAOSTAT production and domestic-supply balance when PSD has no complete country balance; indicator-level resilience explainability across all 72 registered indicators with runtime weights and reconciled contributions; commodity supply vulnerability per country plus the chokepoint dependency fan-out; and the five-factor country and bloc scorecard over food, energy, demographics, technology and defense. Every response distinguishes “measured zero” from “no data” through an explicit flag (available, hasScore, hasValue, state), so a numeric zero is never sufficient on its own. Full usage, per-dataset traps, and the methodology pages are collected in Pro Intelligence Suite.
  • Widescreen split layout on the web (#6417): the map-beside-panels layout now activates at 900px viewport width (was 1600px), matching the desktop app, with the threshold defined once in src/app/split-layout.ts. The map column can narrow to 220px (was 25% minimum) and the panels column keeps a 300px floor; the width divider gains a tooltip, touch drag, and RTL-aware keyboard resize; a new map header toggle moves the map to the right edge (persisted); map heights are stored per layout mode so the stacked and split layouts no longer overwrite each other’s saved height
  • Route Explorer: standalone full-screen modal (CMD+K) for planning shipments between any two countries. Current/Alternatives/Land/Impact tabs with keyboard-first navigation, URL state sharing, strategic-product trade data, dependency flags, and free-tier blur with public route highlight (#2980, #2982, #2994, #2996, #2997, #2998)
  • US Treasury customs revenue in Trade Policy panel with monthly data, FYTD year-over-year comparison, and revenue spike highlighting (#1663)
  • Security advisories gold standard migration: Railway cron seed fetches 24 government RSS feeds hourly, Vercel reads Redis only (#1637)
  • CMD+K full panel coverage: all 55 panels now searchable (was 31), including AI forecasts, correlation panels, webcams, displacement, security advisories (#1656)
  • Chokepoint transit intelligence with 3 free data sources: IMF PortWatch, CorridorRisk, AISStream (#1560)
  • 13 monitored chokepoints (was 6): added Cape of Good Hope, Gibraltar, Bosporus Strait (absorbs Dardanelles), Korea, Dover, Kerch, Lombok (#1560, #1572)
  • Expandable chokepoint cards with TradingView lightweight-charts 180-day time-series (#1560)
  • Real-time transit counting with crossing detection on Railway relay (#1560)
  • R2 trace storage for forecast debugging with Cloudflare API upload (#1655)
  • @ts-nocheck injection in Makefile generate target for CI proto-freshness parity (#1637)

Changed

  • get_tariff_trends renamed to get_price_and_debt_indicators (MCP server 1.39.0; still 93 tools). The tool returns the Big Mac index, the FAO Food Price Index and national debt, and has carried no tariff data since #9301; the old name told agents otherwise. It moves from the trade and tariffs category to markets and economy. The old name still works when called directly, with unchanged behaviour. Tariff data is served by get_tariff_averages, get_bilateral_tariff and get_us_import_duty.
  • generate_forecasts retired from the MCP tool list (MCP server 1.37.0; 94 → 93 tools). Despite its name it never generated forecasts: it returned the same published forecast list as get_forecast_predictions, which reads the same data at weight 1 instead of 2, is open to signed-in free accounts and has the interactive panel. Forecasting is unchanged. The old name still works when called directly, with unchanged behaviour, so existing integrations keep running; it is gone from tools/list, describe_tool, the server card and the docs. Use get_forecast_predictions (with domain, region and limit) instead.
  • New MCP registry description (MCP server 1.36.0). The listing in the official MCP Registry, and the directories that copy it, now reads “Real-time global intelligence across conflict, markets, energy, trade, shipping and cyber.” The server card’s long description adds the per-outlet source ratings and the publicly scored forecast record. 1.35.0 shipped an interim wording that led with those and dropped the breadth. The tool list is unchanged.
  • Forecast projections no longer returned (2026-10-07; #8967). GET /api/forecast/v1/get-forecasts, the forecast:predictions:v2 payload, and the get_forecast_predictions and get_forecast_case MCP tools no longer return projections (h24, d7, d30) on a forecast. The values were unscored editorial curves, not probabilities, and four of five domains broke the probability law (a 30-day value below the 24-hour value for the same event). Forecast.projections (field 17) stays in the v1 proto and OpenAPI as deprecated and is no longer populated, effective immediately by the 2026-10-07 decision, with no deprecation window. Migration: read probability with timeHorizon; no per-horizon value replaces it. Horizon scoring (#7075) is unchanged: the seeder still computes the values and keeps them in its internal history, where the outcome ledger grades each point-in-time horizon contract.
  • YouTube channel live detection is retired (#8167; #5503). It scraped youtube.com channel pages through a residential proxy, which #5503 flagged as a YouTube Terms of Service violation. On GET /api/aviation/v1/get-youtube-live-stream-info, a channel-only query now returns error channel_live_detection_retired without contacting YouTube; isLive is always false and hlsUrl is always empty. The channel query field and the isLive and hlsUrl response fields are deprecated and kept for v1 wire compatibility; the operation itself is not deprecated. GET /api/youtube/live?channel= now returns HTTP 410 with {"error":"channel_live_detection_retired"}, cacheable for one day; ?videoId= still returns the video’s oEmbed title and channel name. Migration: pass video_id (videoId on /api/youtube/live) to name a video. There is no replacement for channel live detection; Live News and Live Webcams play verified streams listed in src/config/live-video-sources.ts.
  • Seven intelligence-suite routes now require Pro (#7433; #6436, #6438, #6439, #6448, #6449). This removes free access that previously worked. GET /api/market/v1/get-physical-premiums, /api/market/v1/get-physical-divergence-index, /api/supply-chain/v1/get-mineral-production, /api/military/v1/get-defense-industrial-base, /api/supply-chain/v1/get-country-vulnerabilities, /api/supply-chain/v1/get-chokepoint-dependencies and /api/supply-chain/v1/list-vulnerability-rankings shipped ungated and served full payloads to anonymous callers; all seven are now entitlement tier 1. Anonymous callers get 401; a signed-in free account gets 403 with error.data.reason = "upgrade-required" and an upgradeUrl. Neither is retryable — only a 503 carrying X-Billing-Verification is. In the dashboard, free viewers now see an upgrade card in place of the defense-industrial and commodity-vulnerability cards in the country deep dive and the dependency block in the Hormuz tracker; the Supply Chain minerals tab falls back to the free deposits table with a note; and the commodities Physical tab no longer appears at all, because the tab is built only when premium data is present. /api/military/v1/get-defense-industrial-base additionally lost its anonymous public=1 shared-cache shape, which had bypassed the entitlement check entirely — so it no longer has a CDN-shared response. One path is deliberately unchanged: the get_market_data MCP tool is a cache-backed bundle whose datasets include physical-premium and physical-divergence, and it remains callable by signed-in free accounts within their daily allowance. See Pro Intelligence Suite for the access contract.
  • GET /api/trade/v1/get-tariff-trends request validation and coverage reporting (#6316). Same defect class as the trade-flows fix (#6309), on the premium MFN tariff path. (1) reporting_country and partner_country must now be 3-digit UN M49 codes, product_sector is limited to optional alphanumeric/_/- tokens up to 16 characters, and years must be 0–30; a malformed value returns HTTP 400 instead of being silently replaced with 840 / 10, which previously answered about a country the caller never asked for. Omitted parameters still take the documented defaults (840, empty partner, empty sector → All products, years=10). (2) years is no longer restricted in practice to 10. The seed now holds a 30-year window per reporter under trade:tariffs:v2:{reporter} and the endpoint slices it, so every lookback in 1–30 is served; requests for any other window previously returned upstream_unavailable: true. (3) The response gains unavailable_reason — the TariffTrendUnavailableReason enum (NOT_COVERED, SEED_MISSING, COVERAGE_UNKNOWN, CACHE_UNAVAILABLE, INVALID_REQUEST, UNSPECIFIED when datapoints are served) — plus coverage_start_year / coverage_end_year. upstream_unavailable is now false for NOT_COVERED. Coverage truth: WTO indicator TP_A_0010 is an MFN applied average for the reporting economy — it has no partner dimension and no product-sector split beyond the All-products aggregate. partner_country is therefore accepted for forward compatibility and never changes the answer (callers that pass a partner, including the dashboard’s own fetchTariffTrends('840','156','',10), keep working). product_sector empty or all is the only covered sector; any other value is answered NOT_COVERED. Fleet health is the tariffTrendsUs series, now probing seed-meta:trade:tariffs rather than the single US years=10 data key.
  • GET /api/trade/v1/get-trade-flows request validation and coverage reporting (#6309). Three consumer-facing changes. (1) reporting_country and partner_country must now be 3-digit UN M49 codes and years must be 0-30; a malformed value returns HTTP 400 instead of being silently replaced with 840 / 000 / 10, which previously answered about a country the caller never asked for. Omitted parameters still take those documented defaults. (2) years is no longer restricted in practice to 10. The seed now holds a 30-year window per reporter and the endpoint slices it, so every lookback in 1-30 is served; requests for any other window previously returned upstream_unavailable: true. (3) The response gains unavailable_reason — the TradeFlowUnavailableReason enum (NOT_COVERED, SEED_MISSING, COVERAGE_UNKNOWN, CACHE_UNAVAILABLE, INVALID_REQUEST, UNSPECIFIED when rows are served) — plus coverage_start_year / coverage_end_year. upstream_unavailable is now false for NOT_COVERED — a reporter/partner combination outside seeded coverage is a contract answer, not an outage, and retrying will not help. Coverage is currently reporter-versus-World (partner_country=000) only: the WTO indicators behind these rows publish a World total and answer HTTP 204 for every named partner.
  • MCP cache-tool default cap: get_country_macro, get_eu_housing_cycle, get_eu_quarterly_gov_debt, and get_eu_industrial_production now cap returned country maps to 30 entries when limit is omitted. This aligns these tools with the documented MCP DEFAULT_LIST_LIMIT behavior used by other cache tools, but it is a consumer-facing default change: integrations that need the full country set must pass limit: 0 explicitly.
  • CII formula v14: hosting-location cyber data and a country’s own military activity no longer score, worldwide (2026-10-21). The cyber boost (up to 12) is removed: every cyber feed it read (AbuseIPDB, Feodo Tracker, URLhaus, C2Intel, OTX) places the abusive IP, C2 server or malware URL where it is hosted, not the country under attack, so it scored datacenter countries (Oct 20: US 129 rows, China 172, UK 40, against Ukraine 2 and none for Israel, Saudi Arabia or Yemen), and AbuseIPDB’s 500 CRITICAL rows vanished whenever its blacklist call failed (cause not logged; the daily request cap is one possibility), moving the UK, France, Germany and the US by up to 12 points overnight. Security now counts only foreign military flights and vessels located in the country, at the v13 weight; own flights followed the operator anywhere in the world, so routine RAF and French training sorties kept the UK, France, Germany and the US at Security 95-100. The cyber seeder also keeps its last good 24h AbuseIPDB list when the blacklist call fails instead of publishing none, so the cyber panel and map stop flapping. riskScores health coverage now needs the conflict and news families (minRecordCount 2). On Oct 21 live inputs: UK 33 -> 20, France 43 -> 28, Germany 34 -> 20, US 33 -> 21; Saudi Arabia, Yemen, Israel and Syria unchanged; Ukraine, Russia and Iran lose only their hosting-location cyber points and at most two own-military points. combinedScore values fall for countries that hosted flagged infrastructure or flew their own aircraft; the risk-score cache key family moves to risk:scores:sebuf:v14 and emitted methodology_version is now v14.
  • Cross-border movements and CII formula v13: add separately dated UK daily arrivals and CBP monthly encounters beside UNHCR ODP reports, with source health, country/route API filters, an opt-in MCP movement view, and distinct death markers. Fresh source-confirmed arrival surges add five CII points once per explicitly named country. Stock, encounters, deaths, and stale reports do not trigger this boost. combinedScore can rise by five points. The risk-score cache key family moves to risk:scores:sebuf:v13 and emitted methodology_version moves to v13. Additional route sources remain blocked by permission or access, so global coverage acceptance is incomplete.
  • CII formula v12: a country under attack keeps rising, and recovers once the strikes stop, worldwide. v11 counted strikes only through a 72h term inside Conflict (18% of the composite), so Saudi Arabia scored 54 on Oct 19 under repeated missile and drone strikes. Each country now carries a daily strike intensity, the larger of confirmed strikes from the Middle East strike feed (severity weighted; a 30-day per-country tally in conflict:strike-pressure:v1) and, worldwide while ACLED is dark, GDELT coverage of air, drone, missile and artillery strikes aimed at the country as a share of its coverage (a fourth counter in gdelt:bulk:country-events:v1). Strike pressure decays it with a three-day half-life into a floor (40 to 70) and a boost of up to 10 on the composite; the 72h strikeBoost leaves Conflict. Replayed on Oct 19: Saudi Arabia 53 -> 63, Yemen 85 -> 89, Iran 71 -> 73, Russia 76 -> 79; calm countries are unchanged, and a country returns to its v11 level 10-14 days after its last strike. combinedScore values change; the risk-score cache key family moved to risk:scores:sebuf:v12 and emitted methodology_version is now v12, so clients should re-baseline.
  • CII formula v11: a sustained crisis scores, and geo events follow real borders, worldwide. v10 scored GDELT conflict and protest coverage only against each country’s own 28-day median, so a month-long campaign raised its own norm and read as calm (Saudi Arabia’s Conflict term was 0 on Oct 19). Each GDELT term is now the larger of that escalation and a sustained level, the conflict- or protest-coded share of the country’s articles over the last seven complete days, which is 0 at the noise floor every country carries (14% conflict, 2% protest) and needs at least 200 articles; the shared GDELT cap rises from 50 to 70. Replayed to Oct 19: Yemen Conflict 68, Saudi Arabia 41, Israel 25, while the US, GB and China stay at 0. Fires, GPS jamming, earthquakes, climate anomalies, strikes and military flights and vessels are now placed by point-in-polygon against Natural Earth 1:50m borders instead of overlapping bounding boxes, which had put Jizan in Yemen, Bahrain in Saudi Arabia, Kuwait City in Iraq and Baltic GPS jamming in Russia, and had dropped Hawaii and Alaska; strike events carry their countryCode. combinedScore values move both ways; the risk-score cache key family moved to risk:scores:sebuf:v11 and emitted methodology_version is now v11.
  • CII formula v10: GDELT escalation stands in for ACLED in Conflict and Unrest. Without ACLED access the ACLED event terms were 0 for every country, so the October 2026 Riyadh strikes and France’s nationwide riots never reached the index. While the ACLED API returns no events, Conflict and Unrest each add a term of up to 50 points scored from GDELT material-conflict and protest coverage against each country’s own 28-day median, so heavily covered countries such as the US and GB do not score on ordinary news days. combinedScore values rise for countries in an escalation; the risk-score cache key family moved to risk:scores:sebuf:v10 and emitted methodology_version is now v10.
  • CII formula v9: news now counts for every country a headline names. Top stories carry countryCodes, and the information component credits each tracked country in it instead of only the first named, so a headline led by Iran or Yemen about an attack on Riyadh now counts for Saudi Arabia. The relay threat summary credits every named country when no attack target is found, and the get_news_intelligence country filter matches any of them. The risk-score cache key family moved to risk:scores:sebuf:v9 and emitted methodology_version is now v9.
  • CII formula v8: fixed dead UCDP conflict-floor attribution. The server scorer read non-existent intensity_level / type_of_violence fields from the cached conflict:ucdp-events:v1 feed (whose rows actually carry violenceType, deathsBest, and dateStart), so UCDP never applied its war (floor 70) / minor (floor 50) score floor and never counted toward the riskScores realtime health/cache signal coverage. UCDP is now classified per country over a 2-year trailing window (war when total deaths > 1000 or event count > 100; minor when event count > 10), matching the frontend deriveUcdpClassifications heuristic. The classifier is configured for that 2-year window, but Redis writers currently fetch the newest six UCDP pages and retain at most 2,000 mapped events from a 365-day trailing event slice; the relay /ucdp-events reader can fetch up to 12 pages but applies the same 365-day filter. Live scoring is seed-input-bounded until UCDP retention is widened with production API-volume and Redis-payload safeguards. The health coverage metric’s conflict family is now satisfied by EITHER the ACLED API path OR UCDP, so a thin/empty ACLED window no longer flips health to COVERAGE_PARTIAL while UCDP is healthy. combinedScore values rise for active UCDP conflict countries (e.g. UA/PK/MX gain a war floor); the risk-score cache key family moved to risk:scores:sebuf:v8 and emitted methodology_version is now v8; clients should re-baseline.
  • CII formula v7: score attribution now changes for the second gap-closure batch. Coordinate attribution resolves three-way bbox overlaps before pairwise border rules, so Punggye-ri maps to KP instead of CN and Lublin maps to PL instead of UA; the seed mirror uses the same resolver. Targeted probes also cover Lhasa, Gaziantep, Kermanshah, Quetta/Islamabad, Dammam, Tabuk, Ruili, Belogorsk, north Hokkaido, and Amman. Climate anomaly boosts now cover the producer-emitted Europe, East Asia, and Latin America zones, restoring KP/KR/JP/PL/DE/FR/GB/VE coverage, and future unknown zones can fall back through anomaly coordinates when available. combinedScore values may shift for affected records, the risk-score cache key family moved to risk:scores:sebuf:v7, and emitted methodology_version is now v7; clients should re-baseline. Remaining coordinate attribution is still a rectangular approximation, not full point-in-polygon border geometry.
  • CII formula v6: score attribution now changes as one batch for country text, coordinate, and climate anomaly inputs. The seed and server country resolvers now agree on token/phrase matching, Rio Grande US/MX border segments, the western Korean DMZ, RU/UA, IN/PK, CN/RU, and RU/JP bbox overlaps; north korean maps to KP and taiwanese maps to TW while bare korean remains ambiguous. Climate anomalies now feed the emitted producer zones (Ukraine, California, Amazon, Taiwan Strait, Caribbean, etc.) into CII and translate enum severities into the numeric climate boost scale. combinedScore values may shift for affected records, the risk-score cache key family moved to risk:scores:sebuf:v6, and emitted methodology_version is now v6; clients should re-baseline.
  • CII formula v5: dynamicScore is now a signed movement delta in the range -100..100, derived from a valid CII snapshot from approximately 24 hours earlier. Positive values mean rising risk, negative values mean falling risk, and 0 means stable or no valid prior snapshot. The browser fallback trend deadband now matches the server rule (> 1 / < -1, an effective two-point threshold for whole-point scores). combinedScore coefficients are unchanged, the risk-score cache key family moved to risk:scores:sebuf:v5, and emitted methodology_version is now v5; clients that previously treated dynamicScore as a non-negative live score should re-baseline.
  • CII methodology v4: Country attribution and source inputs were tightened for the Composite Instability Index. Text attribution now uses token exact-match / phrase matching, known bounding-box overlaps route through explicit heuristics, the earthquake seed uses the USGS 4.5-week (4.5_week) feed, and sanctions scoring reads the full country-count map. Client impact: combinedScore values may shift for affected countries, the risk-score cache key family moved to risk:scores:sebuf:v4, and emitted methodology_version is now v4.
  • CII formula v3: conflict event activity now uses log-scaled calibration before the final component cap, preserving distance between moderate and extreme event volumes. The browser CII path now matches the server displacement log-ramp instead of the old +4/+8 tiers, and browser news-alert pressure is no longer amplified by per-country eventMultiplier. Public combinedScore values may shift and methodology_version is bumped v2 -> v3; clients pinned on it should re-baseline. See docs/methodology/cii-risk-scores.mdx (#2457, methodology portions of #3726).
  • CII weights source of truth (#3789, foundation for #2457): baselineRisk and eventMultiplier now come from one shared coefficient table used by both server-side risk scoring and frontend client-side CII rendering. The previous 7-country frontend/server drift was resolved to the published server/API values. Server/API values were unchanged by this refactor; the v3 methodology_version bump above is from the calibration changes, not this source-of-truth move.
  • CII formula v2: the Composite Instability Index Security component now scores military flights, military vessels and aviation disruptions in addition to GPS jamming. The composite blend gains newsUrgencyBoost, earthquakeBoost, sanctionsBoost and an AIS-disruption boost; cyberBoost/fireBoost are now severity-weighted. Public combinedScore values shift accordingly and methodology_version is bumped v1 -> v2; clients pinned on it should re-baseline.
  • Sebuf API migration (#3207) — scenario + supply-chain endpoints moved to the typed sebuf contract. RPC URLs now derive from method names; five renamed v1 URLs remain live as thin aliases so existing integrations keep working:
    • /api/scenario/v1/run → /api/scenario/v1/run-scenario
    • /api/scenario/v1/status → /api/scenario/v1/get-scenario-status
    • /api/scenario/v1/templates → /api/scenario/v1/list-scenario-templates
    • /api/supply-chain/v1/country-products → /api/supply-chain/v1/get-country-products
    • /api/supply-chain/v1/multi-sector-cost-shock → /api/supply-chain/v1/get-multi-sector-cost-shock
    Aliases retire at the next v1→v2 break (tracked in #3282).
  • POST /api/scenario/v1/run-scenario returns 202 Accepted again on successful enqueue, restoring the pre-sebuf-migration contract (supersedes the 200 OK entry below). The response now also carries a Location header pointing at the get-scenario-status poll URL (same value as the body’s statusUrl), completing the standard REST async-job pattern. The gateway upgrades the sebuf-generated 200 via a status-override side-channel; error statuses (400/403/429/502) are unchanged. Treat any 2xx as success — the interim guidance to branch on response.body.status === "pending" remains valid.
  • POST /api/scenario/v1/run-scenario now returns 200 OK instead of the pre-migration 202 Accepted. sebuf’s HTTP annotations don’t carry per-RPC status codes. Branch on response body status === "pending" instead of response.status === 202. statusUrl field is preserved.

Security

  • get_tariff_trends no longer serves the US tariff series to free accounts (#9301). The free-account MCP bundle read the same seeded US MFN payload that the Pro-only GET /api/trade/v1/get-tariff-trends serves. The bundle now carries only the Big Mac index, FAO Food Price Index and national debt. The tariff series stays available to Pro and API plans through get_tariff_averages (no partner or product group, reporter 840). A call with dataset: ["tariffs"] returns Invalid params that names get_tariff_averages.
  • CDN cache bypass closed: CDN-Cache-Control header now only emitted for trusted origins (worldmonitor.app, Vercel previews, Tauri). No-origin server-side requests always invoke the edge function so validateApiKey runs, preventing a cached 200 from being served to external scrapers.
  • Shipping v2 webhook tenant isolation (#3242): POST /api/v2/shipping/webhooks and GET /api/v2/shipping/webhooks now enforce validateApiKey(req, { forceKey: true }), matching the sibling [subscriberId]{,/[action]} routes. Without this gate, a Clerk-authenticated pro user with no API key would collapse into a shared 'anon' fingerprint bucket and see/overwrite webhooks owned by other 'anon'-bucket tenants.

Fixed

  • JODI oil values marked “not assessed” were discarded. JODI assessment code 3 means a value has not been assessed, not that it is missing, but the JODI oil seed treated it as missing. That dropped reported crude exports for Saudi Arabia, Kazakhstan, Nigeria, Venezuela, Mexico, Algeria and Azerbaijan, among other measures. They now flow into country energy profiles, the energy shock model and the China demand change. Placeholder cells (’-’, ’..’) still count as missing.
  • Same copy under several mastheads no longer counts as corroboration (#9095). Publisher families come from a curated map, so outlets it did not list counted separately even when they ran identical copy. In the live digest of 2026-10-11, EuroNews and Africanews, and Military Times and Defense News (twice), were each counted as two independent publishers. Items in one story whose title and lead are near-identical now count once, in the server corroboration count, story clustering and the publisher list, which marks them “Same copy, counted once”. It only lowers counts and never merges two curated publishers. npm run syndication:candidates lists the groups found for curation. See news methodology.
  • The World Brief counts same copy once too (#9095 follow-up). The brief’s lead gate and the Top Stories corroboration count read publisher families by label, so two outlets running identical copy could still lead the brief while the publisher list said “Single publisher”. The insights seeder now keeps each item’s lead and applies the same detection. Across 42 archived digests from 3 to 16 October, six stories lost eligibility, all genuine shared copy (EuroNews and Africanews three times, gCaptain reprinting Defense News, NRK and Aftenposten, Channels TV and Daily Trust); no brief lead changed and no run was left without one.
  • /verify and verify_claim search every fresh headline, not only the dashboard digest. They searched the digest the dashboard serves, which keeps five items per feed and twenty per category: about 280 headlines. Stories WorldMonitor’s feeds carried were reported as not in its sources. Both now search every headline from the last 96 hours in the full-variant feed caches, up to 30 per feed (about 4,000), through GET /api/news/v1/search-claim-headlines, and cluster the 100 that share the most of the claim’s words. On the Google News World page of 16 October 2026, 49 of 70 stories were found, up from 23. The tech variant of verify_claim still searches its digest.
  • Gateway POST→GET compatibility no longer drops nested body fields (#7276). The legacy unmatched-POST fallback used to copy only scalars and scalar array members into query parameters and ignore the rest, so a stale client could get a successful unfiltered GET while believing its object filters were applied. Mixed, nested, or non-object JSON bodies now return HTTP 400 without a partial translation. Malformed JSON also returns 400 (Invalid JSON body for POST compatibility). Empty or whitespace-only bodies still convert to GET with no extra query parameters. The existing 1 MB body cap and 200-values-per-key cap are unchanged.
  • Trade Policy panel WTO gate changed from panel-wide to per-tab, so Revenue tab works on desktop without WTO API key (#1663)
  • Conflict-intel seed succeeds without ACLED credentials by accepting empty events when humanitarian/PizzINT data is available (#1651)
  • Seed-forecasts crash from top-level @aws-sdk/client-s3 import resolved with lazy dynamic import (#1654)
  • Bootstrap desktop timeouts restored (5s/8s) while keeping aggressive web timeouts (1.2s/1.8s) (#1653)
  • Service worker navigation reverted to NetworkOnly to prevent stale HTML caching on deploy (#1653)
  • Railway seed watch paths fixed for 5 services (seed-insights, seed-unrest-events, seed-prediction-markets, seed-infra, seed-gpsjam)
  • PortWatch ArcGIS URL, field names, and chokepoint name mappings (#1572)
BlogIntelligenceSatellite
v2.6.1

Highlights

  • Blog Platform — Astro-powered blog at /blog with 16 SEO-optimized posts, OG images, and site footer (#1401, #1405, #1409)
  • Country Intelligence — country facts section with right-click context menu (#1400)
  • Satellite Imagery Overhaul — globe-native rendering, outline-only polygons, CSP fixes (#1381, #1385, #1376)

Added

  • Astro blog at /blog with 16 SEO posts and build integration (#1401, #1403)
  • Blog redesign to match /pro page design system (#1405)
  • Blog SEO, OG images, favicon fix, and site footer (#1409)
  • Country facts section and right-click context menu for intel panel (#1400)
  • Satellite imagery panel enabled in orbital surveillance layer (#1375)
  • Globe-native satellite imagery, removed sidebar panel (#1381)
  • Layer search filter with synonym support (#1369)
  • Close buttons on panels and Add Panel block (#1354)
  • Enterprise contact form endpoint (#1365)
  • Commodity and happy variants shown on all header versions (#1407)

Fixed

  • NOTAM closures merged into Aviation layer (#1408)
  • Intel deep dive layout reordered, duplicate headlines removed (#1404)
  • Satellite imagery outline-only polygons to eliminate alpha stacking blue tint (#1385)
  • Enterprise form hardened with mandatory fields and lead qualification (#1382)
  • Country intel silently dismisses when geocode cannot identify a country (#1383)
  • Globe hit targets enlarged for small marker types (#1378)
  • Imagery panel hidden for existing users and viewport refetch deadlock (#1377)
  • CSP violations for satellite preview images (#1376)
  • Safari TypeError filtering and Sentry noise patterns (#1380)
  • Swedish locale ‘avbruten’ TypeError variant filtered (#1402)
  • Satellite imagery STAC backend fix, merged into Orbital Surveillance (#1364)
  • Aviation “Computed” source replaced with specific labels, reduced cache TTLs (#1374)
  • Close button and hover-pause on all marker tooltips (#1371)
  • Invalid ‘satelliteImagery’ removed from LAYER_SYNONYMS (#1370)
  • Risk scores seeding gap and seed-meta key mismatch (#1366)
  • Consistent LIVE header pattern across news and webcams panels (#1367)
  • Globe null guards in path accessor callbacks (#1372)
  • Node_modules guard in pre-push hook, pinned Node 22 (#1368)
  • Typecheck CI workflow: removed paths-ignore, added push trigger (#1373)
  • Theme toggle removed from header (#1407)
SatelliteFinanceMapInfrastructurePro
v2.6.0

Highlights

  • Orbital Surveillance — real-time satellite tracking layer with TLE propagation (#1278)
  • Premium Finance Suite — stock analysis tools for Pro tier (#1268)
  • Self-hosted Basemap — migrated from CARTO to PMTiles on Cloudflare R2 (#1064)
  • GPS Jamming v2 — migrated from gpsjam.org to Wingbits API with H3 hexagons (#1240)
  • Military Flights Overhaul — centralized via Redis seed + edge handler with OpenSky/Wingbits fallbacks (#1263, #1274, #1275, #1276)
  • Pro Waitlist & Landing Page — referral system, Turnstile CAPTCHA, 21-language localization (#1140, #1187)
  • Server-side AI Classification — batch headline classification moves from client to server (#1195)
  • Commodity Variant — new app variant focused on commodities with relevant panels & layers (#1040, #1100)
  • Health Check System — comprehensive health endpoint with auto seed-meta freshness tracking (#1091, #1127, #1128)

Added

  • Orbital surveillance layer with real-time satellite tracking via satellite.js (#1278, #1281)
  • Premium finance stock analysis suite for Pro tier (#1268)
  • GPS jamming migration to Wingbits API with H3 hex grid (#1240)
  • Commodity app variant with dedicated panels and map layers (#1040, #1100)
  • Pro waitlist landing page with referral system and Turnstile CAPTCHA (#1140)
  • Pro landing page localization — 21 languages (#1187)
  • Pro page repositioning toward markets, macro & geopolitics (#1261)
  • Referral invite banner when visiting via ?ref= link (#1232)
  • Server-side batch AI classification for news headlines (#1195)
  • Self-hosted PMTiles basemap on Cloudflare R2, replacing CARTO (#1064)
  • Per-provider map theme selector (#1101)
  • Globe visual preset setting (Earth / Cosmos) with texture selection (#1090, #1076)
  • Comprehensive health check endpoint for UptimeRobot (#1091)
  • Auto seed-meta freshness tracking for all RPC handlers (#1127)
  • Submarine cables expanded to 86 via TeleGeography API (#1224)
  • Pak-Afghan conflict zone and country boundary override system (#1150)
  • Sudan and Myanmar conflict zone polygon improvements (#1216)
  • Iran events: 28 new location coords, 48h TTL (#1251)
  • Tech HQs in Ireland data (#1244)
  • BIS data seed job (#1131)
  • CoinPaprika fallback for crypto/stablecoin data (#1092)
  • Rudaw TV live stream and RSS feed (#1117)
  • Dubai and Riyadh added to default airport watchlist (#1144)
  • Cmd+K: 16 missing layer toggles (#1289), “See all commands” link with category list (#1270)
  • UTM attribution tags on all outbound links (#1233)
  • Performance warning dialog replaces hard layer limit (#1088)
  • Unified error/retry UX with muted styling and countdown (#1115)
  • Settings reorganized into collapsible groups (#1110)
  • Reset Layout button with tooltip (#1267, #1250)
  • Markdown lint in pre-push hook (#1166)

Changed

  • Military flights centralized via Redis seed + edge handler pattern (#1263)
  • Military flights seed with OpenSky anonymous fallback + Wingbits fallback (#1274, #1275)
  • Theater posture computed directly in relay instead of pinging Vercel RPC (#1259)
  • Countries GeoJSON served from R2 CDN (#1164)
  • Consolidated duplicated market data lists into shared JSON configs (#1212)
  • Eliminate all frontend external API calls — enforce gold standard pattern (#1217)
  • WB indicators seeded on Railway, never called from frontend (#1159, #1157)
  • Temporal baseline for news + fires moved to server-side (#1194)
  • Panel creation guarded by variant config (#1221)
  • Panel tab styles unified to underline pattern across all panels (#1106, #1182, #1190, #1192)
  • Reduce default map layers (#1141)
  • Share dialog dismissals persist across subdomains via cookies (#1286)
  • Country-wide conflict zones use actual country geometry (#1245)
  • Aviation seed interval reduced to 1h (#1258)
  • Replace curl with native Node.js HTTP CONNECT tunnel in seeds (#1287)
  • Seed scripts use _seed-utils.mjs shared configs from scripts/shared/ (#1231, #1234)

Fixed

  • Rate Limiting: prioritize cf-connecting-ip over x-real-ip for correct per-user rate limiting behind CF proxy (#1241)
  • Security: harden cache keys against injection and hash collision (#1103), per-endpoint rate limits for summarize endpoints (#1161)
  • Map: prevent ghost layers rendering without a toggle (#1264), DeckGL layer toggles getting stuck (#1248), auto-fallback to OpenFreeMap on basemap failure (#1109), CORS fallback for Carto basemap (#1142), use CORS-enabled R2 URL for PMTiles in Tauri (#1119), CII Instability layer disabled in 3D mode (#1292)
  • Layout: reconcile ultrawide zones when map is hidden (#1246), keep settings button visible on scaled desktop widths (#1249), exit fullscreen before switching variants (#1253), apply map-hidden layout class on initial load (#1087), preserve panel column position across refresh (#1170, #1108, #1112)
  • Panels: event delegation to survive setContent debounce (#1203), guard RPC response array access with optional chaining (#1174), clear stuck error headers and sanitize error messages (#1175), lazy panel race conditions + server feed gaps (#1113), Tech Readiness panel loading on full variant (#1208), Strategic Risk panel button listeners (#1214), World Clock green home row (#1202), Airline Intelligence CSS grid layouts (#1197)
  • Pro/Turnstile: explicit rendering to fix widget race condition (#1189), invisible widget support (#1215), CSP allow Turnstile (#1155), handle already_registered state (#1183), reset on enterprise form error (#1222), registration feedback and referral code gen (#1229, #1228), no-cache header for /pro (#1179), correct API endpoint path (#1177), www redirect loop fix (#1198, #1201)
  • SEO: comprehensive improvements for /pro and main pages (#1271)
  • Railway: remove custom railpack.json install step causing ENOENT builds (#1296, #1290, #1288)
  • Aviation: correct cancellation rate calculation and add 12 airports (#1209), unify NOTAM status logic (#1225)
  • Sentry: triage 26 issues, fix 3 bugs, add 29 noise filters (#1173, #1098)
  • Health: treat missing seed-meta as stale (#1128), resolve BIS credit and theater posture warnings (#1124), add WB seed loop (#1239), UCDP auth handling (#1252)
  • Country Brief: formatting, duplication, and news cap fixes (#1219), prevent modal stuck on geocode failure (#1134)
  • Economic: guard BIS and spending data against undefined (#1162, #1169)
  • Webcams: detect blocked YouTube embeds on web (#1107), use iframe load event fallback (#1123), MTV Lebanon as live stream (#1122)
  • Desktop: recover stranded routing fixes and unified error UX (#1160), DRY debounce, error handling, retry cap (#1084), debounce cache writes, batch secret push, lazy panels (#1077)
  • PWA: bump SW nuke key to v2 for CF-cached 404s (#1081), one-time SW nuke on first visit (#1079)
  • Performance: only show layer warning when adding layers, not removing (#1265), reduce unnecessary Vercel edge invocations (#1176)
  • i18n: sync all 20 locales to en.json — zero drift (#1104), correct indentation for geocode error keys (#1147)
  • Insights: graceful exit, LKG fallback, swap to Gemini 2.5 Flash (#1153, #1154)
  • Seeds: prevent API quota burn and respect rate limits (#1167), gracefully skip write when validation fails (#1089), seed-meta tracking for all bootstrap keys (#1163, #1138)
FinanceInfrastructure
v2.5.25

Changed

  • Supply Chain v2 — bump chokepoints & minerals cache keys to v2; add aisDisruptions field to ChokepointInfo (proto, OpenAPI, generated types, handler, UI panel); rename Malacca Strait → Strait of Malacca; reduce chokepoint Redis TTL from 15 min to 5 min; expand description to always show warning + AIS disruption counts; remove Nickel & Copper from critical minerals data (focus on export-controlled minerals); slice top producers to 3; use full FRED series names for shipping indices; add daily cache tier (86400s) and move minerals route to it; align client-side circuit breaker TTLs with server TTLs; fix upstream-unavailable banner to only show when no data is present; register supply-chain routes in Vite dev server plugin
  • Cache migration: old supply_chain:chokepoints:v1 and supply_chain:minerals:v1 Redis keys are no longer read by any consumer — they will expire via TTL with no action required
IntelligenceSecurityPerformanceUX
v2.5.24

Highlights

  • UCDP conflict data — integrated Uppsala Conflict Data Program for historical & ongoing armed conflict tracking (#760)
  • Country brief sharing — maximize mode, shareable URLs, native browser share button, expanded sections (#743, #854)
  • Unified Vercel deployment — consolidated 4 separate deployments into 1 via runtime variant detection (#756)
  • CDN performance overhaul — POST→GET conversion, per-domain edge functions, tiered bootstrap for ~46% egress reduction (#753, #795, #838)
  • Security hardening — CSP script hashes replace unsafe-inline, crypto.randomUUID() for IDs, XSS-safe i18n, Finnhub token header (#781, #844, #861, #744)
  • i18n expansion — French support with Live TV channels, hardcoded English strings replaced with translation keys (#794, #851, #839)

Added

  • UCDP (Uppsala Conflict Data Program) integration for armed conflict tracking (#760)
  • Iran & Strait of Hormuz conflict zones, upgraded Ukraine polygon (#731)
  • 100 Iran war events seeded with expanded geocoder (#792)
  • Country brief maximize mode, shareable URLs, expanded sections & i18n (#743)
  • Native browser share button for country briefs (#854)
  • French i18n support with French Live TV channels (#851)
  • Geo-restricted live channel support, restored WELT (#765)
  • Manage Channels UX — toggle from grid + show all channels (#745)
  • Command palette: disambiguate Map vs Panel commands, split country into map/brief (#736)
  • Command palette: rotating contextual tips replace static empty state (#737)
  • Download App button for web users with dropdown (#734, #735)
  • Reset layout button to restore default panel sizes and order (#801)
  • System status moved into settings (#735)
  • Vercel cron to pre-warm AviationStack cache (#776)
  • Runtime variant detection — consolidate 4 Vercel deployments into 1 (#756)
  • CJS syntax check in pre-push hook (#769)

Fixed

  • Security: XSS — wrap t() calls in escapeHtml() (#861), use crypto.randomUUID() instead of Math.random() for ID generation (#844), move Finnhub API key from query string to X-Finnhub-Token header (#744)
  • i18n: replace hardcoded English strings with translation keys (#839), i18n improvements (#794)
  • Market: parse comma-separated query params and align Railway cache keys (#856), Railway market data cron + complete missing tech feed categories (#850), Yahoo relay fallback + RSS digest relay for blocked feeds (#835), tech UNAVAILABLE feeds + Yahoo batch early-exit + sector heatmap gate (#810)
  • Aviation: move AviationStack fetching to Railway relay, reduce to 40 airports (#858)
  • UI: cancel pending debounced calls on component destroy (#848), guard async operations against stale DOM references (#843)
  • Sentry: guard stale DOM refs, audio.play() compat, add 16 noise filters (#855)
  • Relay: exponential backoff for failing RSS feeds (#853), deduplicate UCDP constants crashing Railway container (#766)
  • API: remove [domain] catch-all that intercepted all RPC routes (#753 regression) (#785), pageSize bounds validation on research handlers (#819), return 405 for wrong HTTP method (#757), pagination cursor for cyber threats (#754)
  • Conflict: bump Iran events cache-bust to v7 (#724)
  • OREF: prevent LLM translation cache from poisoning Hebrew→English pipeline (#733), strip translation labels from World Brief input (#768)
  • Military: harden USNI fleet report ship name regex (#805)
  • Sidecar: add required params to ACLED API key validation probe (#804)
  • Macro: replace hardcoded BTC mining thresholds with Mayer Multiple (#750)
  • Cyber: reduce GeoIP per-IP timeout from 3s to 1.5s (#748)
  • CSP: restore unsafe-inline for Vercel bot-challenge pages (#788), add missing script hash and finance variant (#798)
  • Runtime: route all /api/* calls through CDN edge instead of direct Vercel (#780)
  • Desktop: detect Linux node target from host arch (#742), harden Windows installer update path + map resize (#739), close update toast after clicking download (#738), only open valid http(s) links externally (#723)
  • Webcams: replace dead Tel Aviv live stream (#732), replace stale Jerusalem feed (#849)
  • Story header uses full domain WORLDMONITOR.APP (#799)
  • Open variant nav links in same window instead of new tab (#721)
  • Suppress map renders during resize drag (#728)
  • Append deduction panel to DOM after async import resolves (#764)
  • Deduplicate stale-while-revalidate background fetches in CircuitBreaker (#793)
  • CORS fallback, rate-limit bump, RSS proxy allowlist (#814)
  • Unavailable stream error messages updated (#759)

Performance

  • Tier slow/fast bootstrap data for ~46% CDN egress reduction (#838)
  • Convert POST RPCs to GET for CDN caching (#795)
  • Split monolithic edge function into per-domain functions (#753)
  • Increase CDN cache TTLs + add stale-if-error across edge functions (#777)
  • Bump CDN cache TTLs for oref-alerts and youtube/live (#791)
  • Skip wasted direct fetch for Vercel-blocked domains in RSS proxy (#815)

Security

  • Replace CSP unsafe-inline with script hashes and add trust signals (#781)
  • Expand Permissions-Policy and tighten CSP connect-src (#779)

Changed

  • Extend support for larger screens (#740)
  • Green download button + retire sliding popup (#747)
  • Extract shared relay helper into _relay.js (#782)
  • Consolidate SummarizeArticleResponse status fields (#813)
  • Consolidate declare const process into shared env.d.ts (#752)
  • Deduplicate clampInt into server/_shared/constants
  • Add error logging for network errors in error mapper (#746)
  • Redis error logging + reduced timeouts for edge functions (#749)
IntelligenceAviationMilitaryInfrastructure
v2.5.21

Highlights

  • Iran Attacks map layer — conflict events with severity badges, related event popups, and CII integration (#511, #527, #547, #549)
  • Telegram Intel panel — 27 curated OSINT channels via MTProto relay (#550)
  • OREF Israel Sirens — real-time alerts with Hebrew→English translation and 24h history bootstrap (#545, #556, #582)
  • GPS/GNSS jamming layer — detection overlay with CII integration (#570)
  • Day/night terminator — solar terminator overlay on map (#529)
  • Breaking news alert banner — audio alerts for critical/high RSS items with cooldown bypass (#508, #516, #533)
  • AviationStack integration — global airport delays for 128 airports with NOTAM closure detection (#552, #581, #583)
  • Strategic risk score — theater posture + breaking news wired into scoring algorithm (#584)

Added

  • Iran Attacks map layer with conflict event popups, severity badges, and priority rendering (#511, #527, #549)
  • Telegram Intel panel with curated OSINT channel list (#550, #600)
  • OREF Israel Sirens panel with Hebrew-to-English translation (#545, #556)
  • OREF 24h history bootstrap on relay startup (#582)
  • GPS/GNSS jamming detection map layer + CII integration (#570)
  • Day/night solar terminator overlay (#529)
  • Breaking news active alert banner with audio for critical/high items (#508)
  • AviationStack integration for non-US airports + NOTAM closure detection (#552, #581, #583)
  • RT (Russia Today) HLS livestream + RSS feeds (#585, #586)
  • Iran webcams tab with 4 feeds (#569, #572, #601)
  • CBC News optional live channel (#502)
  • Strategic risk score wired to theater posture + breaking news (#584)
  • CII scoring: security advisories, Iran strikes, OREF sirens, GPS jamming (#547, #559, #570, #579)
  • Country brief + CII signal coverage expansion (#611)
  • Server-side military bases with 125K+ entries + rate limiting (#496)
  • AVIATIONSTACK_API key in desktop settings (#553)
  • Iran events seed script and latest data (#575)

Fixed

  • Aviation: stale IndexedDB cache invalidation + reduced CDN TTL (#607), broken lock replaced with direct cache + cancellation tiers (#591), query all airports instead of rotating batch (#557), NOTAM routing through Railway relay (#599), always show all monitored airports (#603)
  • Telegram: AUTH_KEY_DUPLICATED fixes — latch to stop retry spam (#543), 60s startup delay (#587), graceful shutdown + poll guard (#562), ESM import path fixes (#537, #542), missing relay auth headers (#590)
  • Relay: Polymarket OOM prevention — circuit breaker + concurrency limiter (#519), request deduplication (#513), queue backpressure + response slicing (#593), cache stampede fix (#592), kill switch (#523); smart quotes crash (#563); graceful shutdown (#562, #565); curl for OREF (#546, #567, #571); maxBuffer ENOBUFS (#609); rsshub.app blocked (#526); ERR_HTTP_HEADERS_SENT guard (#509); Telegram memory cleanup (#531)
  • Live news: 7 stale YouTube fallback IDs replaced (#535, #538), broken Europe channel handles (#541), eNCA handle + VTC NOW removal + CTI News (#604), RT HLS recovery (#610), YouTube proxy auth alignment (#554, #555), residential proxy + gzip for detection (#551)
  • Breaking news: critical alerts bypass cooldown (#516), keyword gaps filled (#517, #521), fake pubDate filter (#517), SESSION_START gate removed (#533)
  • Threat classifier: military/conflict keyword gaps + news-to-conflict bridge (#514), Groq 429 stagger (#520)
  • Geo: tokenization-based matching to prevent false positives (#503), 60+ missing locations in hub index (#528)
  • Iran: CDN cache-bust pipeline v4 (#524, #532, #544), read-only handler (#518), Gulf misattribution via bbox disambiguation (#532)
  • CII: Gulf country strike misattribution (#564), compound escalation for military action (#548)
  • Bootstrap: 401/429 rate limiting fix (#512), hydration cache + polling hardening (#504)
  • Sentry: guard YT player methods + GM/InvalidState noise (#602), Android OEM WebView bridge injection (#510), setView invalid preset (#580), beforeSend null-filename leak (#561)
  • Rate limiting raised to 300 req/min sliding window (#515)
  • Vercel preview origin regex generalized + bases cache key (#506)
  • Cross-env for Windows-compatible npm scripts (#499)
  • Download banner repositioned to bottom-right (#536)
  • Stale/expired Polymarket markets filtered (#507)
  • Cyber GeoIP centroid fallback jitter made deterministic (#498)
  • Cache-control headers hardened for polymarket and rss-proxy (#613)

Performance

  • Server-side military base fetches: debounce + static edge cache tier (#497)
  • RSS: refresh interval raised to 10min, cache TTL to 20min (#612)
  • Polymarket cache TTL raised to 10 minutes (#568)

Changed

  • Stripped 61 debug console.log calls from 20 service files (#501)
  • Bumped version to 2.5.21 (#605)
PerformanceSecurityAPI
v2.5.20

Added

  • Edge caching: Complete Cloudflare edge cache tier coverage with degraded-response policy (#484)
  • Edge caching: Cloudflare edge caching for proxy.worldmonitor.app (#478) and api.worldmonitor.app (#471)
  • Edge caching: Tiered edge Cache-Control aligned to upstream TTLs (#474)
  • API migration: Convert 52 API endpoints from POST to GET for edge caching (#468)
  • Gateway: Configurable VITE_WS_API_URL + harden POST-to-GET shim (#480)
  • Cache: Negative-result caching for cachedFetchJson (#466)
  • Security advisories: New panel with government travel alerts (#460)
  • Settings: Redesign settings window with VS Code-style sidebar layout (#461)

Fixed

  • Commodities panel: Was showing stocks instead of commodities — circuit breaker SWR returned stale data from a different call when cacheTtlMs=0 (#483)
  • Analytics: Use greedy regex in PostHog ingest rewrites (#481)
  • Sentry: Add noise filters for 4 unresolved issues (#479)
  • Gateway: Convert stale POST requests to GET for backwards compat (#477)
  • Desktop: Enable click-to-play YouTube embeds + CISA feed fixes (#476)
  • Tech variant: Use rss() for CISA feed, drop build from pre-push hook (#475)
  • Security advisories: Route feeds through RSS proxy to avoid CORS blocks (#473)
  • API routing: Move 5 path-param endpoints to query params for Vercel routing (#472)
  • Beta: Eagerly load T5-small model when beta mode is enabled
  • Scripts: Handle escaped apostrophes in feed name regex (#455)
  • Wingbits: Add 5-minute backoff on /v1/flights failures (#459)
  • Ollama: Strip thinking tokens, raise max_tokens, fix panel summary cache (#456)
  • RSS/HLS: RSS feed repairs, HLS native playback, summarization cache fix (#452)

Performance

  • AIS proxy: Increase AIS snapshot edge TTL from 2s to 10s (#482)
FinanceDesktopUX
v2.5.10

Fixed

  • Yahoo Finance rate-limit UX: Show “rate limited — retrying shortly” instead of generic “Failed to load” on Markets, ETF, Commodities, and Sector panels when Yahoo returns 429 (#407)
  • Sequential Yahoo calls: Replace Promise.all with staggered batching in commodity quotes, ETF flows, and macro signals to prevent 429 rate limiting (#406)
  • Sector heatmap Yahoo fallback: Sector data now loads via Yahoo Finance when FINNHUB_API_KEY is missing (#406)
  • Finnhub-to-Yahoo fallback: Market quotes route Finnhub symbols through Yahoo when API key is not configured (#407)
  • ETF early-exit on rate limit: Skip retry loop and show rate-limit message immediately instead of waiting 60s (#407)
  • Sidecar auth resilience: 401-retry with token refresh for stale sidecar tokens after restart; diagFetch auth helper for settings window diagnostics (#407)
  • Verbose toggle persistence: Write verbose state to writable data directory instead of read-only app bundle on macOS (#407)
  • AI summary verbosity: Tighten prompts to 2 sentences / 60 words max with max_tokens reduced from 150 to 100 (#404)
  • Settings modal title: Rename from “PANELS” to “SETTINGS” across all 17 locales (#403)
  • Sentry noise filters: CSS.escape() for news ID selectors, player.destroy guard, 11 new ignoreErrors patterns, blob: URL extension frame filter (#402)
UXPerformanceMaritime
v2.5.6

Added

  • Greek (Ελληνικά) locale — full translation of all 1,397 i18n keys (#256)
  • Nigeria RSS feeds — 5 new sources: Premium Times, Vanguard, Channels TV, Daily Trust, ThisDay Live
  • Greek locale feeds — Naftemporiki, in.gr, iefimerida.gr for Greek-language news coverage
  • Brasil Paralelo source — Brazilian news with RSS feed and source tier (#260)

Performance

  • AIS relay optimization — backpressure queue with configurable watermarks, spatial indexing for chokepoint detection (O(chokepoints) vs O(chokepoints × vessels)), pre-serialized + pre-gzipped snapshot cache eliminating per-request JSON.stringify + gzip CPU (#266)

Fixed

  • Vietnam flag country code — corrected flag emoji in language selector (#245)
  • Sentry noise filters — added patterns for SW FetchEvent, PostHog ingest; enabled SW POST method for PostHog analytics (#246)
  • Service Worker same-origin routing — restricted SW route patterns to same-origin only, preventing cross-origin fetch interception (#247, #251)
  • Social preview bot allowlisting — whitelisted Twitterbot, facebookexternalhit, and other crawlers on OG image assets (#251)
  • Windows CORS for Tauri — allow http:// origin from tauri.localhost for Windows desktop builds (#262)
  • Linux AppImage GLib crash — fix GLib symbol mismatch on newer distros by bundling compatible libraries (#263)
MapUX
v2.5.2

Fixed

  • QuotaExceededError handling — detect storage quota exhaustion and stop further writes to localStorage/IndexedDB instead of silently failing; shared markStorageQuotaExceeded() flag across persistent-cache and utility storage
  • deck.gl null.getProjection crash — wrap setProps() calls in try/catch to survive map mid-teardown races in debounced/RAF callbacks
  • MapLibre “Style is not done loading” — guard setFilter() in mousemove/mouseout handlers during theme switches
  • YouTube invalid video ID — validate video ID format (/^[\w-]&#123;10,12&#125;$/) before passing to IFrame Player constructor
  • Vercel build skip on empty SHA — guard ignoreCommand against unset VERCEL_GIT_PREVIOUS_SHA (first deploy, force deploy) which caused git diff to fail and cancel builds
  • Sentry noise filters — added 7 patterns: iOS readonly property, SW FetchEvent, toLowerCase/trim/indexOf injections, QuotaExceededError
PerformanceSecurityAPI
v2.5.1

Performance

  • Batch FRED API requests — frontend now sends a single request with comma-separated series IDs instead of 7 parallel edge function invocations, eliminating Vercel 25s timeouts
  • Parallel UCDP page fetches — replaced sequential loop with Promise.all for up to 12 pages, cutting fetch time from ~96s worst-case to ~8s
  • Bot protection middleware — blocks known social-media crawlers from hitting API routes, reducing unnecessary edge function invocations
  • Extended API cache TTLs — country-intel 12h→24h, GDELT 2h→4h, nuclear 12h→24h; Vercel ignoreCommand skips non-code deploys

Fixed

  • Partial UCDP cache poisoning — failed page fetches no longer silently produce incomplete results cached for 6h; partial results get 10-min TTL in both Redis and memory, with partial: true flag propagated to CDN cache headers
  • FRED upstream error masking — single-series failures now return 502 instead of empty 200; batch mode surfaces per-series errors and returns 502 when all fail
  • Sentry Load failed filter — widened regex from ^TypeError: Load failed$ to ^TypeError: Load failed( \(.*\))?$ to catch host-suffixed variants (e.g., gamma-api.polymarket.com)
  • Tooltip XSS hardening — replaced rawHtml() with safeHtml() allowlist sanitizer for panel info tooltips
  • UCDP country endpoint — added missing HTTP method guards (OPTIONS/GET)
  • Middleware exact path matching — social preview bot allowlist uses Set.has() instead of startsWith() prefix matching

Changed

  • FRED batch API supports up to 15 comma-separated series IDs with deduplication
  • Missing FRED API key returns 200 with X-Data-Status: skipped-no-api-key header instead of silent empty response
  • LAYER_TO_SOURCE config extracted from duplicate inline mappings into shared constant
AIDesktopSecurity
v2.5.0

Highlights

Local LLM Support (Ollama / LM Studio) — Run AI summarization entirely on your own hardware with zero cloud dependency. The desktop app auto-discovers models from any OpenAI-compatible local inference server (Ollama, LM Studio, llama.cpp, vLLM) and populates a selection dropdown. A 4-tier fallback chain ensures summaries always generate: Local LLM → Groq → OpenRouter → browser-side T5. Combined with the Tauri desktop app, this enables fully air-gapped intelligence analysis where no data leaves your machine.

Added

  • Ollama / LM Studio integration — local AI summarization via OpenAI-compatible /v1/chat/completions endpoint with automatic model discovery, embedding model filtering, and fallback to manual text input
  • 4-tier summarization fallback chain — Ollama (local) → Groq (cloud) → OpenRouter (cloud) → Transformers.js T5 (browser), each with 5-second timeout before silently advancing to the next
  • Shared summarization handler factory — all three API tiers use identical logic for headline deduplication (Jaccard >0.6), variant-aware prompting, language-aware output, and Redis caching (summary:v3:&#123;mode&#125;:&#123;variant&#125;:&#123;lang&#125;:&#123;hash&#125;)
  • Settings window with 3 tabs — dedicated LLMs tab (Ollama endpoint/model, Groq, OpenRouter), API Keys tab (12+ data source credentials), and Debug & Logs tab (traffic log, verbose mode, log file access). Each tab runs an independent verification pipeline
  • Consolidated keychain vault — all desktop secrets stored as a single JSON blob in one OS keychain entry (secrets-vault), reducing macOS Keychain authorization prompts from 20+ to exactly 1 on app startup. One-time auto-migration from individual entries with cleanup
  • Cross-window secret synchronization — saving credentials in the Settings window immediately syncs to the main dashboard via localStorage broadcast, with no app restart needed
  • API key verification pipeline — each credential is validated against its provider’s actual API endpoint. Network errors (timeouts, DNS failures) soft-pass to prevent transient failures from blocking key storage; only explicit 401/403 marks a key invalid
  • Plaintext URL inputs — endpoint URLs (Ollama API, relay URLs, model names) display as readable text instead of masked password dots in Settings
  • 5 new defense/intel RSS feeds — Military Times, Task & Purpose, USNI News, Oryx OSINT, UK Ministry of Defence
  • Koeberg nuclear power plant — added to the nuclear facilities map layer (the only commercial reactor in Africa, Cape Town, South Africa)
  • Privacy & Offline Architecture documentation — README now details the three privacy levels: full cloud, desktop with cloud APIs, and air-gapped local with Ollama
  • AI Summarization Chain documentation — README includes provider fallback flow diagram and detailed explanation of headline deduplication, variant-aware prompting, and cross-user cache deduplication

Changed

  • AI fallback chain now starts with Ollama (local) before cloud providers
  • Feature toggles increased from 14 to 15 (added AI/Ollama)
  • Desktop architecture uses consolidated vault instead of per-key keychain entries
  • README expanded with ~85 lines of new content covering local LLM support, privacy architecture, summarization chain internals, and desktop readiness framework

Fixed

  • URL and model fields in Settings display as plaintext instead of masked password dots
  • OpenAI-compatible endpoint flow hardened for Ollama/LM Studio response format differences (thinking tokens, missing choices array edge cases)
  • Sentry null guard for getProjection() crash with 6 additional noise filters
  • PathLayer cache cleared on layer toggle-off to prevent stale WebGL buffer rendering
MapInfrastructure
v2.4.1

Fixed

  • Map PathLayer cache: Clear PathLayer on toggle-off to prevent stale WebGL buffers
  • Sentry noise: Null guard for getProjection() crash and 6 additional noise filters
  • Markdown docs: Resolve lint errors in documentation files
UXMobileMap
v2.4.0

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
UXInfrastructureDesktop
v2.3.9

Added

  • Full internationalization (14 locales): English, French, German, Spanish, Italian, Polish, Portuguese, Dutch, Swedish, Russian, Arabic, Chinese Simplified, Japanese — each with 1100+ translated keys
  • RTL support: Arabic locale with dir="rtl", dedicated RTL CSS overrides, regional language code normalization (e.g. ar-SA correctly triggers RTL)
  • Language switcher: in-app locale picker with flag icons, persists to localStorage
  • i18n infrastructure: i18next with browser language detection and English fallback
  • Community discussion widget: floating pill linking to GitHub Discussions with delayed appearance and permanent dismiss
  • Linux AppImage: added ubuntu-22.04 to CI build matrix with webkit2gtk/appindicator dependencies
  • NHK World and Nikkei Asia: added RSS feeds for Japan news coverage
  • Intelligence Findings badge toggle: option to disable the findings badge in the UI

Changed

  • Zero hardcoded English: all UI text routed through t() — panels, modals, tooltips, popups, map legends, alert templates, signal descriptions
  • Trending proper-noun detection: improved mid-sentence capitalization heuristic with all-caps fallback when ML classifier is unavailable
  • Stopword suppression: added missing English stopwords to trending keyword filter

Fixed

  • Dead UTC clock: removed #timeDisplay element that permanently displayed --:--:-- UTC
  • Community widget duplicates: added DOM idempotency guard preventing duplicate widgets on repeated news refresh cycles
  • Settings help text: suppressed raw i18n key paths rendering when translation is missing
  • Intelligence Findings badge: fixed toggle state and listener lifecycle
  • Context menu styles: restored intel-findings context menu styles
  • CSS theme variables: defined missing --panel-bg and --panel-border variables
FinanceDesktop
v2.3.8

Added

  • Finance variant: Added a dedicated market-first variant (finance.worldmonitor.app) with finance/trading-focused feeds, panels, and map defaults
  • Finance desktop profile: Added finance-specific desktop config and build profile for Tauri packaging

Changed

  • Variant feed loading: loadNews now enumerates categories dynamically and stages category fetches with bounded concurrency across variants
  • Feed resilience: Replaced direct MarketWatch RSS usage in finance/full/tech paths with Google News-backed fallback queries
  • Classification pressure controls: Tightened AI classification budgets for tech/full and tuned per-feed caps to reduce startup burst pressure
  • Timeline behavior: Wired timeline filtering consistently across map and news panels
  • AI summarization defaults: Switched OpenRouter summarization to auto-routed free-tier model selection

Fixed

  • Finance panel parity: Kept data-rich panels while adding news panels for finance instead of removing core data surfaces
  • Desktop finance map parity: Finance variant now runs first-class Deck.GL map/layer behavior on desktop runtime
  • Polymarket fallback: Added one-time direct connectivity probe and memoized fallback to prevent repeated ERR_CONNECTION_RESET storms
  • FRED fallback behavior: Missing FRED_API_KEY now returns graceful empty payloads instead of repeated hard 500s
  • Preview CSP tooling: Allowed https://vercel.live script in CSP so Vercel preview feedback injection is not blocked
  • Trending quality: Suppressed noisy generic finance terms in keyword spike detection
  • Mobile UX: Hidden desktop download prompt on mobile devices
UXDesktopMap
v2.3.7

Added

  • Full light mode theme: Complete light/dark theme system with CSS custom properties, ThemeManager module, FOUC prevention, and getCSSColor() utility for theme-aware inline styles
  • Theme-aware maps and charts: Deck.GL basemap, overlay layers, and CountryTimeline charts respond to theme changes in real time
  • Dark/light mode header toggle: Sun/moon icon in the header bar for quick theme switching, replacing the duplicate UTC clock
  • Desktop update checker: Architecture-aware download links for macOS (ARM/Intel) and Windows
  • Node.js bundled in Tauri installer: Sidecar no longer requires system Node.js
  • Markdown linting: Added markdownlint config and CI workflow

Changed

  • Panels modal: Reverted from “Settings” back to “Panels” — removed redundant Appearance section now that header has theme toggle
  • Default panels: Enabled UCDP Conflict Events, UNHCR Displacement, Climate Anomalies, and Population Exposure panels by default

Fixed

  • CORS for Tauri desktop: Fixed CORS issues for desktop app requests
  • Markets panel: Keep Yahoo-backed data visible when Finnhub API key is skipped
  • Windows UNC paths: Preserve extended-length path prefix when sanitizing sidecar script path
  • Light mode readability: Darkened neon semantic colors and overlay backgrounds for light mode contrast
Desktop
v2.3.6

Fixed

  • Windows console window: Hide the node.exe console window that appeared alongside the desktop app on Windows
UXDesktop
v2.3.5

Changed

  • Panel error messages: Differentiated error messages per panel so users see context-specific guidance instead of generic failures
  • Desktop config auto-hide: Desktop configuration panel automatically hides on web deployments where it is not relevant
Desktop
v2.3.4

Fixed

  • Windows sidecar crash: Strip \\?\ UNC extended-length prefix from paths before passing to Node.js — Tauri resource_dir() on Windows returns UNC-prefixed paths that cause EISDIR: lstat 'C:' in Node.js module resolution
  • Windows sidecar CWD: Set explicit current_dir on the Node.js Command to prevent bare drive-letter working directory issues from NSIS shortcut launcher
  • Sidecar package scope: Add package.json with "type": "module" to sidecar directory, preventing Node.js from walking up the entire directory tree during ESM scope resolution
DesktopSecurity
v2.3.3

Fixed

  • Keychain persistence: Enable apple-native (macOS) and windows-native (Windows) features for the keyring crate — v3 ships with no default platform backends, so API keys were stored in-memory only and lost on restart
  • Settings key verification: Soft-pass network errors during API key verification so transient sidecar failures don’t block saving
  • Resilient keychain reads: Use Promise.allSettled in loadDesktopSecrets so a single key failure doesn’t discard all loaded secrets
  • Settings window capabilities: Add "settings" to Tauri capabilities window list for core plugin permissions
  • Input preservation: Capture unsaved input values before DOM re-render in settings panel
SecurityInfrastructure
v2.3.0

Security

  • CORS hardening: Tighten Vercel preview deployment regex to block origin spoofing (worldmonitorEVIL.vercel.app)
  • Sidecar auth bypass: Move /api/local-env-update behind LOCAL_API_TOKEN auth check
  • Env key allowlist: Restrict sidecar env mutations to 18 known secret keys (matching SUPPORTED_SECRET_KEYS)
  • postMessage validation: Add origin and source checks on incoming messages in LiveNewsPanel
  • postMessage targetOrigin: Replace wildcard '*' with specific embed origin
  • CORS enforcement: Add isDisallowedOrigin() check to 25+ API endpoints that were missing it
  • Custom CORS migration: Migrate gdelt-geo and eia from custom CORS to shared _cors.js module
  • New CORS coverage: Add CORS headers + origin check to firms-fires, stock-index, youtube/live
  • YouTube embed origins: Tighten ALLOWED_ORIGINS regex in youtube/embed.js
  • CSP hardening: Remove 'unsafe-inline' from script-src in both index.html and tauri.conf.json
  • iframe sandbox: Add sandbox="allow-scripts allow-same-origin allow-presentation" to YouTube embed iframe
  • Meta tag validation: Validate URL query params with regex allowlist in parseStoryParams()

Fixed

  • Service worker stale assets: Add skipWaiting, clientsClaim, and cleanupOutdatedCaches to workbox config — fixes NS_ERROR_CORRUPTED_CONTENT / MIME type errors when users have a cached SW serving old HTML after redeployment
UXInfrastructure
v2.2.6

Fixed

  • Filter trending noise and fix sidecar auth
  • Restore tech variant panels
  • Remove Market Radar and Economic Data panels from tech variant

Docs

  • Add developer X/Twitter link to Support section
  • Add cyber threat API keys to .env.example
SecurityAPI
v2.2.5

Security

  • Migrate all Vercel edge functions to CORS allowlist
  • Restrict Railway relay CORS to allowed origins only

Fixed

  • Hide desktop config panel on web
  • Route World Bank & Polymarket via Railway relay
IntelligenceMapUX
v2.2.3

Added

  • Cyber threat intelligence map layer (Feodo Tracker, URLhaus, C2IntelFeeds, OTX, AbuseIPDB)
  • Trending keyword spike detection with end-to-end flow
  • Download desktop app slide-in banner for web visitors
  • Country briefs in Cmd+K search

Changed

  • Redesign 4 panels with table layouts and scoped styles
  • Redesign population exposure panel and reorder UCDP columns
  • Dramatically increase cyber threat map density

Fixed

  • Resolve z-index conflict between pinned map and panels grid
  • Cap geo enrichment at 12s timeout, prevent duplicate download banners
  • Replace ipwho.is/ipapi.co with ipinfo.io/freeipapi.com for geo enrichment
  • Harden trending spike processing and optimize hot paths
  • Improve cyber threat tooltip/popup UX and dot visibility
IntelligencePerformanceUX
v2.2.2

Added

  • Full-page Country Brief Page replacing modal overlay
  • Download redirect API for platform-specific installers

Fixed

  • Normalize country name from GeoJSON to canonical TIER1 name
  • Tighten headline relevance, add Top News section, compact markets
  • Hide desktop config panel on web, fix irrelevant prediction markets
  • Tone down climate anomalies heatmap to stop obscuring other layers
  • macOS: hide window on close instead of quitting

Performance

  • Reduce idle CPU from pulse animation loop
  • Harden regression guardrails in CI, cache, and map clustering
DesktopInfrastructure
v2.2.1

Fixed

  • Consolidate variant naming and fix PWA tile caching
  • Windows settings window: async command, no menu bar, no white flash
  • Constrain layers menu height in DeckGLMap
  • Allow Cloudflare Insights script in CSP
  • macOS build failures when Apple signing secrets are missing
DesktopMapIntelligence
v2.2.0
Initial v2.2 release with multi-variant support (World + Tech), desktop app (Tauri), and comprehensive geopolitical intelligence features.