Changed
- Two MCP tool pairs merged; both old names keep working (MCP server 1.44.0; 95 tools; #9432).
get_energy_storageis now theeu-gas-storage,nat-gas-storageandcrude-inventoriesdatasets ofget_energy_intelligence, with the same fields and units.get_internet_activityis now thetrafficandddosdatasets ofget_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 adeprecationobject 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_statuscountrynow takes an ISO code or English name and selects exactly that country (soDEno longer matches Bangladesh); a name fragment such asunitedthat resolves to no country now returns Invalid params before any allowance is spent, and thestaleflag on both tools covers every merged source. The merge frees 682 bytes of thetools/listdescription budget.
Fixed
get_tariff_trendswith a tariffs-only dataset returns the US MFN series again. The old name stays offtools/list. That call runs Proget_tariff_averagesfor reporter840. Any other dataset still returnsget_price_and_debt_indicators. A free account gets the same Pro denial as a direct call toget_tariff_averages. A successful response through the old name includes adeprecationobject instructuredContentthat names the tool that ran. The text carries it too, except for ajmespathprojection that returns an array or scalar, whose text stays the raw value.dataset: ["tariffs"]onget_price_and_debt_indicatorsstill returns Invalid params.
Added
-
MCP
get_conflict_eventsandget_natural_disastersfilter by area and age (#9433). Both tools accept abbox(sw_lat,sw_lon,ne_lat,ne_lon; a box withsw_longreater thanne_loncrosses the antimeridian) or alat,lonandradius_kmcircle up to 2,000 km measured as great-circle distance, plussince_daysfrom 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, beforelimitis applied. Rows without coordinates or a date are dropped and counted per list indata.geo_filter, so an empty answer is not mistaken for nothing nearby; whensince_daysreaches past the oldest row a list holds,geo_filter.oldest_heldsays so. Pro panel connections accept the same filters. Mixingbboxwith 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).
/mcprows inwm_api_usagegainrequested_tool: the name from atools/callordescribe_toolthat 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_bytesandtool_jmespathcopy the error kind, the payload size before projection and whether a JMESPath projection was requested from the same call’smcp.toolcalllog line. All of them are null on REST rows and when no tool ran, andtool_nameis unchanged. -
Capital flows for every economy and every country pair: seeder,
GET /api/economic/v1/get-capital-flowsand MCPget_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,counterpartreturns 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_datascreen,changes_since_daysandGET /api/sanctions/v1/lookup-sanction-entitynow 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. Theallowance-exhausted,upgrade-requiredandquota-exceededdenials now addrecommendedPlanwhen a self-serve plan sits one step up, pluscheckoutTool: "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 ondescribe_toolfor the rest; hosts that grade or pick tools from the listing alone saw one line per tool.tools/listnow 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, anddescribe_toolreturns 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-sectionargumentsbranches ofget_country_brief_section.generate_forecastsis 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/webhookstakes aneventType: the defaultchokepoint.disruption, now with an optionalminLiveSignalso a war-zone chokepoint standing at 70 does not fire once against a threshold of 50 and go silent, and the newcountry.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 (countriesnarrows the scope).DELETE /api/v2/shipping/webhooks/{subscriberId}unsubscribes. Both events share the signed delivery contract; the OpenAPIwebhooksentry names both in itsX-WM-Eventenum. 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’sX-WM-Signaturebefore it reaches a Zap. Thresholds come from a 90-day backtest; see Shipping v2 API. -
Two days of Al Jazeera in
/verifyandverify_claim. Al Jazeera’s own feed holds 25 entries, about ten hours of stories. Claim search now also reads its Google Newssite: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
/verifyandverify_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 Newssite: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,/verifyfound 60, up from 53. -
Science panel (opt-in, full dashboard). Reuters Science, Nature News, BBC Science, Guardian Science and ScienceDaily, after a check of
/verifyagainst 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_claimand the MCP digest (sciencecategory) 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/verifyandverify_claim. -
verify_claimMCP 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). -
/verifypage (#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-changesnow 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 theTRUST_REVIEW_SECRETkey. 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
/verifyand inverify_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 byGET /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 onestory_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 callsget-tariff-trends.get_bilateral_tariff: one HS6 product between two countries. It callsget-bilateral-tariff.get_us_import_duty: the current US duty with Section 301 and 232 measures. It callsget-us-import-duty.
get_tariff_trendsbundle is unchanged. -
Tariff answers say what they are (MCP). Every answer from the three live tariff tools, except an upstream outage, carries a
coverageobject: 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 (latestYearor the HTS release). Schedule answers for the United States point atget_us_import_duty. Thetools/listdescriptions 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_countryandproduct_sectorwere 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 asTextilesorFood), 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. Abasisfield 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-nocheckinjection in Makefile generate target for CI proto-freshness parity (#1637)
Changed
-
get_tariff_trendsrenamed toget_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 byget_tariff_averages,get_bilateral_tariffandget_us_import_duty. -
generate_forecastsretired 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 asget_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 fromtools/list,describe_tool, the server card and the docs. Useget_forecast_predictions(withdomain,regionandlimit) 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
projectionsno longer returned (2026-10-07; #8967).GET /api/forecast/v1/get-forecasts, theforecast:predictions:v2payload, and theget_forecast_predictionsandget_forecast_caseMCP tools no longer returnprojections(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: readprobabilitywithtimeHorizon; 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 errorchannel_live_detection_retiredwithout contacting YouTube;isLiveis alwaysfalseandhlsUrlis always empty. Thechannelquery field and theisLiveandhlsUrlresponse 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: passvideo_id(videoIdon/api/youtube/live) to name a video. There is no replacement for channel live detection; Live News and Live Webcams play verified streams listed insrc/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-dependenciesand/api/supply-chain/v1/list-vulnerability-rankingsshipped ungated and served full payloads to anonymous callers; all seven are now entitlement tier 1. Anonymous callers get401; a signed-in free account gets403witherror.data.reason = "upgrade-required"and anupgradeUrl. Neither is retryable — only a503carryingX-Billing-Verificationis. 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-baseadditionally lost its anonymouspublic=1shared-cache shape, which had bypassed the entitlement check entirely — so it no longer has a CDN-shared response. One path is deliberately unchanged: theget_market_dataMCP tool is a cache-backed bundle whose datasets includephysical-premiumandphysical-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-trendsrequest validation and coverage reporting (#6316). Same defect class as the trade-flows fix (#6309), on the premium MFN tariff path. (1)reporting_countryandpartner_countrymust now be 3-digit UN M49 codes,product_sectoris limited to optional alphanumeric/_/-tokens up to 16 characters, andyearsmust be 0–30; a malformed value returns HTTP 400 instead of being silently replaced with840/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)yearsis no longer restricted in practice to 10. The seed now holds a 30-year window per reporter undertrade:tariffs:v2:{reporter}and the endpoint slices it, so every lookback in 1–30 is served; requests for any other window previously returnedupstream_unavailable: true. (3) The response gainsunavailable_reason— theTariffTrendUnavailableReasonenum (NOT_COVERED,SEED_MISSING,COVERAGE_UNKNOWN,CACHE_UNAVAILABLE,INVALID_REQUEST,UNSPECIFIEDwhen datapoints are served) — pluscoverage_start_year/coverage_end_year.upstream_unavailableis nowfalseforNOT_COVERED. Coverage truth: WTO indicatorTP_A_0010is an MFN applied average for the reporting economy — it has no partner dimension and no product-sector split beyond the All-products aggregate.partner_countryis therefore accepted for forward compatibility and never changes the answer (callers that pass a partner, including the dashboard’s ownfetchTariffTrends('840','156','',10), keep working).product_sectorempty orallis the only covered sector; any other value is answeredNOT_COVERED. Fleet health is thetariffTrendsUsseries, now probingseed-meta:trade:tariffsrather than the single US years=10 data key. -
GET /api/trade/v1/get-trade-flowsrequest validation and coverage reporting (#6309). Three consumer-facing changes. (1)reporting_countryandpartner_countrymust now be 3-digit UN M49 codes andyearsmust be 0-30; a malformed value returns HTTP 400 instead of being silently replaced with840/000/10, which previously answered about a country the caller never asked for. Omitted parameters still take those documented defaults. (2)yearsis 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 returnedupstream_unavailable: true. (3) The response gainsunavailable_reason— theTradeFlowUnavailableReasonenum (NOT_COVERED,SEED_MISSING,COVERAGE_UNKNOWN,CACHE_UNAVAILABLE,INVALID_REQUEST,UNSPECIFIEDwhen rows are served) — pluscoverage_start_year/coverage_end_year.upstream_unavailableis nowfalseforNOT_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, andget_eu_industrial_productionnow cap returned country maps to 30 entries whenlimitis omitted. This aligns these tools with the documented MCPDEFAULT_LIST_LIMITbehavior used by other cache tools, but it is a consumer-facing default change: integrations that need the full country set must passlimit: 0explicitly. -
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.riskScoreshealth coverage now needs the conflict and news families (minRecordCount2). 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.combinedScorevalues fall for countries that hosted flagged infrastructure or flew their own aircraft; the risk-score cache key family moves torisk:scores:sebuf:v14and emittedmethodology_versionis nowv14. -
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.combinedScorecan rise by five points. The risk-score cache key family moves torisk:scores:sebuf:v13and emittedmethodology_versionmoves tov13. 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 inconflict: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 ingdelt: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.combinedScorevalues change; the risk-score cache key family moved torisk:scores:sebuf:v12and emittedmethodology_versionis nowv12, 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 theircountryCode.combinedScorevalues move both ways; the risk-score cache key family moved torisk:scores:sebuf:v11and emittedmethodology_versionis nowv11. -
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.combinedScorevalues rise for countries in an escalation; the risk-score cache key family moved torisk:scores:sebuf:v10and emittedmethodology_versionis nowv10. -
CII formula
v9: news now counts for every country a headline names. Top stories carrycountryCodes, 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 theget_news_intelligencecountryfilter matches any of them. The risk-score cache key family moved torisk:scores:sebuf:v9and emittedmethodology_versionis nowv9. -
CII formula
v8: fixed dead UCDP conflict-floor attribution. The server scorer read non-existentintensity_level/type_of_violencefields from the cachedconflict:ucdp-events:v1feed (whose rows actually carryviolenceType,deathsBest, anddateStart), so UCDP never applied its war (floor 70) / minor (floor 50) score floor and never counted toward theriskScoresrealtime 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 frontendderiveUcdpClassificationsheuristic. 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-eventsreader 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 toCOVERAGE_PARTIALwhile UCDP is healthy.combinedScorevalues rise for active UCDP conflict countries (e.g. UA/PK/MX gain a war floor); the risk-score cache key family moved torisk:scores:sebuf:v8and emittedmethodology_versionis nowv8; 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-emittedEurope,East Asia, andLatin Americazones, restoring KP/KR/JP/PL/DE/FR/GB/VE coverage, and future unknown zones can fall back through anomaly coordinates when available.combinedScorevalues may shift for affected records, the risk-score cache key family moved torisk:scores:sebuf:v7, and emittedmethodology_versionis nowv7; 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 koreanmaps to KP andtaiwanesemaps to TW while barekoreanremains 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.combinedScorevalues may shift for affected records, the risk-score cache key family moved torisk:scores:sebuf:v6, and emittedmethodology_versionis nowv6; clients should re-baseline. -
CII formula
v5:dynamicScoreis 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, and0means 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).combinedScorecoefficients are unchanged, the risk-score cache key family moved torisk:scores:sebuf:v5, and emittedmethodology_versionis nowv5; clients that previously treateddynamicScoreas 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:combinedScorevalues may shift for affected countries, the risk-score cache key family moved torisk:scores:sebuf:v4, and emittedmethodology_versionis nowv4. -
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/+8tiers, and browser news-alert pressure is no longer amplified by per-countryeventMultiplier. PubliccombinedScorevalues may shift andmethodology_versionis bumpedv2->v3; clients pinned on it should re-baseline. Seedocs/methodology/cii-risk-scores.mdx(#2457, methodology portions of #3726). -
CII weights source of truth (#3789, foundation for #2457):
baselineRiskandeventMultipliernow 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 v3methodology_versionbump above is from the calibration changes, not this source-of-truth move. -
CII formula
v2: the Composite Instability IndexSecuritycomponent now scores military flights, military vessels and aviation disruptions in addition to GPS jamming. The composite blend gainsnewsUrgencyBoost,earthquakeBoost,sanctionsBoostand an AIS-disruption boost;cyberBoost/fireBoostare now severity-weighted. PubliccombinedScorevalues shift accordingly andmethodology_versionis bumpedv1->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
-
POST /api/scenario/v1/run-scenarioreturns202 Acceptedagain on successful enqueue, restoring the pre-sebuf-migration contract (supersedes the200 OKentry below). The response now also carries aLocationheader pointing at theget-scenario-statuspoll URL (same value as the body’sstatusUrl), 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 any2xxas success — the interim guidance to branch onresponse.body.status === "pending"remains valid. -
POST /api/scenario/v1/run-scenarionow returns200 OKinstead of the pre-migration202 Accepted. sebuf’s HTTP annotations don’t carry per-RPC status codes. Branch on response bodystatus === "pending"instead ofresponse.status === 202.statusUrlfield is preserved.
Security
get_tariff_trendsno 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-onlyGET /api/trade/v1/get-tariff-trendsserves. 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 throughget_tariff_averages(no partner or product group, reporter840). A call withdataset: ["tariffs"]returns Invalid params that namesget_tariff_averages.- CDN cache bypass closed:
CDN-Cache-Controlheader now only emitted for trusted origins (worldmonitor.app, Vercel previews, Tauri). No-origin server-side requests always invoke the edge function sovalidateApiKeyruns, preventing a cached 200 from being served to external scrapers. - Shipping v2 webhook tenant isolation (#3242):
POST /api/v2/shipping/webhooksandGET /api/v2/shipping/webhooksnow enforcevalidateApiKey(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:candidateslists 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.
/verifyandverify_claimsearch 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), throughGET /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. Thetechvariant ofverify_claimstill 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-s3import 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)
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)
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.mjsshared configs fromscripts/shared/(#1231, #1234)
Fixed
- Rate Limiting: prioritize
cf-connecting-ipoverx-real-ipfor 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_registeredstate (#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)
Changed
- Supply Chain v2 — bump chokepoints & minerals cache keys to v2; add
aisDisruptionsfield toChokepointInfo(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; adddailycache 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:v1andsupply_chain:minerals:v1Redis keys are no longer read by any consumer — they will expire via TTL with no action required
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 inescapeHtml()(#861), usecrypto.randomUUID()instead ofMath.random()for ID generation (#844), move Finnhub API key from query string toX-Finnhub-Tokenheader (#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
SummarizeArticleResponsestatus fields (#813) - Consolidate
declare const processinto sharedenv.d.ts(#752) - Deduplicate
clampIntintoserver/_shared/constants - Add error logging for network errors in error mapper (#746)
- Redis error logging + reduced timeouts for edge functions (#749)
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)
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)
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.allwith 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_KEYis 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;
diagFetchauth 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_tokensreduced 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)
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 fromtauri.localhostfor Windows desktop builds (#262) - Linux AppImage GLib crash — fix GLib symbol mismatch on newer distros by bundling compatible libraries (#263)
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-]{10,12}$/) before passing to IFrame Player constructor - Vercel build skip on empty SHA — guard
ignoreCommandagainst unsetVERCEL_GIT_PREVIOUS_SHA(first deploy, force deploy) which causedgit diffto fail and cancel builds - Sentry noise filters — added 7 patterns: iOS readonly property, SW FetchEvent, toLowerCase/trim/indexOf injections, QuotaExceededError
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: trueflag 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 failedfilter — 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()withsafeHtml()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 ofstartsWith()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-keyheader instead of silent empty response - LAYER_TO_SOURCE config extracted from duplicate inline mappings into shared constant
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/completionsendpoint 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:{mode}:{variant}:{lang}:{hash}) - 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
localStoragebroadcast, 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
choicesarray 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
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
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
.AppImageoption 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
htmltoglobPatternssonavigateFallbackworks 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/typenull crashes
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-SAcorrectly 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.04to 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
#timeDisplayelement 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-bgand--panel-bordervariables
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:
loadNewsnow 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_RESETstorms - FRED fallback behavior: Missing
FRED_API_KEYnow returns graceful empty payloads instead of repeated hard 500s - Preview CSP tooling: Allowed
https://vercel.livescript 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
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
Fixed
- Windows console window: Hide the
node.execonsole window that appeared alongside the desktop app on Windows
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
Fixed
- Windows sidecar crash: Strip
\\?\UNC extended-length prefix from paths before passing to Node.js — Tauriresource_dir()on Windows returns UNC-prefixed paths that causeEISDIR: lstat 'C:'in Node.js module resolution - Windows sidecar CWD: Set explicit
current_diron the Node.js Command to prevent bare drive-letter working directory issues from NSIS shortcut launcher - Sidecar package scope: Add
package.jsonwith"type": "module"to sidecar directory, preventing Node.js from walking up the entire directory tree during ESM scope resolution
Fixed
- Keychain persistence: Enable
apple-native(macOS) andwindows-native(Windows) features for thekeyringcrate — 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.allSettledinloadDesktopSecretsso 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
Security
- CORS hardening: Tighten Vercel preview deployment regex to block origin spoofing (
worldmonitorEVIL.vercel.app) - Sidecar auth bypass: Move
/api/local-env-updatebehindLOCAL_API_TOKENauth check - Env key allowlist: Restrict sidecar env mutations to 18 known secret keys (matching
SUPPORTED_SECRET_KEYS) - postMessage validation: Add
originandsourcechecks 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-geoandeiafrom custom CORS to shared_cors.jsmodule - New CORS coverage: Add CORS headers + origin check to
firms-fires,stock-index,youtube/live - YouTube embed origins: Tighten
ALLOWED_ORIGINSregex inyoutube/embed.js - CSP hardening: Remove
'unsafe-inline'fromscript-srcin bothindex.htmlandtauri.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, andcleanupOutdatedCachesto workbox config — fixesNS_ERROR_CORRUPTED_CONTENT/ MIME type errors when users have a cached SW serving old HTML after redeployment
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
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
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
Initial v2.2 release with multi-variant support (World + Tech), desktop app (Tauri), and comprehensive geopolitical intelligence features.
