Skip to content

feat: add election calendar with CII scoring integration#221

Open
Jacob-Strokus wants to merge 1257 commits intokoala73:mainfrom
Jacob-Strokus:main
Open

feat: add election calendar with CII scoring integration#221
Jacob-Strokus wants to merge 1257 commits intokoala73:mainfrom
Jacob-Strokus:main

Conversation

@Jacob-Strokus
Copy link

  • Add src/config/elections.ts with 25 global elections for 2026-2027
  • Integrate election proximity boost into Country Instability Index scoring
    • Election day: +15 pts, imminent (1-7d): +10, elevated (7-30d): +5
    • Significance multipliers: high=1.0, medium=0.6, low=0.3
    • Post-election uncertainty period (3 days): +8 pts
  • Display election badges in CII panel with days-until countdown
  • Add buildElectionBadge() using h() DOM helper (compatible with i18n refactor)
  • Export election utilities from config/index.ts
  • Add .cii-election-badge styles

Summary

Type of change

  • Bug fix
  • New feature
  • New data source / feed
  • New map layer
  • Refactor / code cleanup
  • Documentation
  • CI / Build / Infrastructure

Affected areas

  • Map / Globe
  • News panels / RSS feeds
  • AI Insights / World Brief
  • Market Radar / Crypto
  • Desktop app (Tauri)
  • API endpoints (/api/*)
  • Config / Settings
  • Other:

Checklist

  • Tested on worldmonitor.app variant
  • Tested on tech.worldmonitor.app variant (if applicable)
  • New RSS feed domains added to api/rss-proxy.js allowlist (if adding feeds)
  • No API keys or secrets committed
  • TypeScript compiles without errors (npm run typecheck)

Screenshots

image

koala73 and others added 30 commits February 19, 2026 13:23
Add Turkish (tr) as the 14th supported language with full translation
of all 1,134 locale keys, locale loader, tr-TR formatting, and flag
mapping. Update documentation to reflect 14 languages.
Move pattern from beforeSend extension-only check to ignoreErrors array.
All 16 events were iOS Safari with no stack trace — stale cached assets
after deploys, not actionable bugs.
…ions

- Add i18n keys for all 9 strategic posture theater names (Iran Theater,
  Baltic Theater, Taiwan Strait, etc.) across all 14 languages
- Wire StrategicPosturePanel to use t() for theater display names with
  fallback to English if key is missing
- Include webcam region button translations (ALL, MIDEAST, EUROPE, etc.)
  that were missing from deployed locale files

Fixes koala73#121
- CIIPanel: use existing t('components.cii.noSignals') for empty state
- CountryBriefPage: localize level badge and trend indicator labels
- ETFFlowsPanel: localize summary labels, table headers, net direction
- LiveWebcamsPanel: localize region filter tab labels
- MacroSignalsPanel: localize verdict, signal names, and bullish count
- StrategicRiskPanel: localize score levels, trend label, and trend values
Add Ollama as the primary summarization provider for desktop builds,
sitting before Groq/OpenRouter in the fallback chain. This enables
fully local, unlimited LLM inference via Ollama's OpenAI-compatible
endpoint (/v1/chat/completions).

Changes across six layers:
- runtime-config: OLLAMA_API_URL + OLLAMA_MODEL secret keys, aiOllama
  feature toggle (default on), URL validation
- sidecar: allowlist + endpoint probe validation (tries /v1/models
  then /api/tags)
- api/ollama-summarize.js: new handler mirroring Groq/OpenRouter with
  shared Redis cache keys
- summarization.ts: tryOllama() + updated chain order in normal, beta,
  and translation paths (Ollama → Groq → OpenRouter → Browser T5)
- RuntimeConfigPanel: signup URLs + i18n help text for new keys
- desktop-readiness: aiOllama in key-backed features + readiness check

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
…ayers

Three test files covering Ollama integration:

api/ollama-summarize.test.mjs (9 tests):
- Fallback signal when unconfigured, on API error, on empty response
- Success path with correct provider label and response shape
- Model selection via OLLAMA_MODEL env / default fallback
- Network error handling (ECONNREFUSED)
- Translate mode prompt verification

tests/summarization-chain.test.mjs (7 tests):
- Ollama success short-circuits chain (Groq never called)
- Ollama fail → Groq success fallback
- Full fallback when both unconfigured
- Provider label correctness for Ollama and Groq
- Uniform response shape across providers
- Identical fallback signal shapes

src-tauri/sidecar/local-api-server.test.mjs (8 new tests):
- OLLAMA_API_URL and OLLAMA_MODEL accepted via env-update allowlist
- Unknown keys rejected (403)
- Validation via /v1/models probe (reachable mock)
- Validation via /api/tags native fallback
- OLLAMA_MODEL pass-through validation
- Non-http protocol rejection (422)
- Auth-required behavior preserved with token

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
Server-side: extract shared CORS, validation, caching, prompt building,
and response shaping into api/_summarize-handler.js factory. Each
endpoint (Groq, OpenRouter, Ollama) becomes a thin wrapper calling
createSummarizeHandler() with a provider config: credentials, API URL,
model, headers, and provider label.

Client-side: replace three near-identical tryOllama/tryGroq/tryOpenRouter
functions with a single tryApiProvider() driven by an API_PROVIDERS
config array. Add runApiChain() helper that loops the chain with
progress callbacks. Simplify translateText() from three copy-pasted
blocks to a single loop over the same provider array.

- groq-summarize.js: 297 → 30 lines
- openrouter-summarize.js: 295 → 33 lines
- ollama-summarize.js: 289 → 34 lines
- summarization.ts: 336 → 239 lines
- New _summarize-handler.js: 315 lines (shared)
- Net: -566 lines of duplication removed

Adding a new LLM provider now requires only a provider config object
in the endpoint file + one entry in the API_PROVIDERS array.

Tests: 13 new tests for the shared factory (cache key, dedup, handler
creation, fallback, error casing, HTTP methods). All 42 existing tests
pass unchanged.

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
- Use for...of entries() instead of index-based loops in summarization.ts
  to satisfy strict noUncheckedIndexedAccess (7 TS18048/TS2345 errors)
- Replace fragile API_PROVIDERS[1] with .find(p => p.name === groq)
- Add OLLAMA_API_URL and OLLAMA_MODEL to SUPPORTED_SECRET_KEYS in main.rs
  so keychain secrets are injected into sidecar on desktop startup
…and Ollama UX

- Split settings window into 3 tabs: LLMs (Ollama/Groq/OpenRouter),
  API Keys (data feeds), and Debug & Logs
- Add featureFilter option to RuntimeConfigPanel for rendering subsets
- Consolidate keychain to single JSON vault entry (1 macOS prompt vs 20)
- Add Ollama model discovery with /api/tags + /v1/models fallback
- Strip <think> reasoning tokens from Ollama responses
- Suppress thinking with think:false in Ollama request body
- Parallel secret verification with 15s global timeout
- Fix manual model input overlapping dropdown (CSS grid-area + hidden-input class)
- Add loading spinners to settings tab panels
- Suppress notification popups when settings window is open
- Filter embed models from Ollama dropdown
- Fix settings window black screen flash with inline dark background
Add local LLM support mentions across feature descriptions, talking
points, screenshot suggestions, and changelog. New dedicated section
for Ollama/LM Studio as feature koala73#11.
DeckGLMap: guard updateLayers/debounce/raf against null maplibreMap,
null out references in destroy() to prevent post-destroy setProps crash.

main.ts: filter contentWindow.postMessage (Facebook WebView),
vertex shader compile (GPU driver), objectStoreNames (iOS background),
Unexpected identifier https (Safari 16), _0x obfuscated vars (extensions),
WKWebView deallocated (Tauri lifecycle).
…koala73#124)

## Summary

Extracts shared summarization logic (CORS, validation, caching, prompt
building) into a reusable `_summarize-handler.js` factory, then uses it
to add Ollama as the first provider in the fallback chain. This reduces
code duplication across Groq, OpenRouter, and the new Ollama endpoint
while maintaining identical behavior.

**Fallback chain is now:** Ollama → Groq → OpenRouter → Browser T5

## Type of change

- [x] New feature
- [x] Refactor / code cleanup
- [x] API endpoints (`/api/*`)

## Affected areas

- [x] AI Insights / World Brief
- [x] Desktop app (Tauri)
- [x] API endpoints (`/api/*`)
- [x] Config / Settings

## Changes

### New Files
- **`api/_summarize-handler.js`** – Shared handler factory with:
- `createSummarizeHandler(providerConfig)` – Creates edge handlers for
any LLM provider
- `getCacheKey()` – Stable cache key generation (extracted from
per-provider code)
- `deduplicateHeadlines()` – Headline deduplication logic (extracted
from per-provider code)
- Unified prompt building for brief/analysis/translate modes with
tech/full variants
  - CORS, validation, caching, and error handling pipeline

- **`api/ollama-summarize.js`** – New Ollama endpoint (34 lines):
- Calls local/remote Ollama instance via OpenAI-compatible
`/v1/chat/completions`
  - Reads `OLLAMA_API_URL` and `OLLAMA_MODEL` from environment
  - Shares Redis cache with Groq/OpenRouter (same cache key strategy)
  - Returns fallback signal when unconfigured or API fails

- **`api/ollama-summarize.test.mjs`** – Unit tests for Ollama endpoint:
  - Fallback signal when `OLLAMA_API_URL` not configured
  - Success response with provider="ollama"
  - Error handling (API errors, empty responses)
  - Model selection via `OLLAMA_MODEL` env

- **`api/_summarize-handler.test.mjs`** – Unit tests for shared factory:
  - Cache key stability and variation by mode/variant/lang/geoContext
  - Headline deduplication logic
  - Handler creation with missing credentials
  - API provider calls and response shaping

- **`tests/summarization-chain.test.mjs`** – Integration tests for
fallback chain:
  - Ollama success short-circuits (no downstream calls)
  - Ollama failure → Groq success
  - Both fail → fallback signals propagate

### Modified Files

**`api/groq-summarize.js`** & **`api/openrouter-summarize.js`**
- Replaced ~290 lines of duplicated logic with single call to
`createSummarizeHandler()`
- Now thin wrappers: 26 lines (Groq) and 28 lines (OpenRouter)
- Identical behavior, zero functional changes

**`src/services/summarization.ts`**
- Updated fallback chain: `Ollama → Groq → OpenRouter → Browser T5`
- Refactored `tryGroq()` and `tryOpenRouter()` into unified
`tryApiProvider()` function
- Added `API_PROVIDERS` config array for provider ordering
- Updated `SummarizationProvider` type to include `'ollama'`

**`src-tauri/sidecar/local-api-server.mjs`**
- Added `OLLAMA_API_URL` and `OLLAMA_MODEL` to `ALLOWED_ENV_KEYS`
- Added validation for `OLLAMA_API_URL` in
`validateSecretAgainstProvider()`:
  - Probes `/v1/models` (OpenAI-compatible endpoint)
  - Falls back to `/api/tags` (native Ollama endpoint)
- Returns "Ollama endpoint verified" or "Ollama endpoint verified
(native API
…buffers

Toggling cables/pipelines off then on caused deck.gl assertion failure
because the cached PathLayer had its WebGL resources destroyed on removal.
…keychain vault

- Ollama/LM Studio integration with auto model discovery and 4-tier fallback chain
- Settings window split into LLMs, API Keys, and Debug tabs
- Consolidated keychain vault (1 OS prompt instead of 20+)
- README expanded with privacy architecture, summarization chain docs
- CHANGELOG updated with full v2.5.0 release notes
- 5 new defense/intel RSS feeds, Koeberg nuclear plant added
Removes circular dev→prod dependency. The new polymarketPlugin()
mirrors the edge function logic locally: validates params, fetches
from gamma-api.polymarket.com, and gracefully returns [] when
Cloudflare JA3 blocks server-side TLS (expected behavior).
The Vite dev plugin was hardcoding `{ videoId: null }` with a TODO,
causing LiveNewsPanel to never resolve actual live streams during local
development. Replace the stub with the same fetch-and-scrape approach
used by the production edge function (api/youtube/live.js): fetch the
channel's /live page and extract videoId + isLive from the HTML.

https://claude.ai/code/session_01684qa7XvS7sf9CShqU8zNg
Store the interval ID returned by setInterval in a new clockIntervalId
field and clear it in App.destroy(). Previously, the interval was never
stored or cleared, causing DOM writes to double on each Vite HMR reload.

https://claude.ai/code/session_0111CXxXM5qKR83UAdUTQDyL
npm install regenerated the lockfile to reflect the current
version (2.5.0) and license field from package.json.

https://claude.ai/code/session_01684qa7XvS7sf9CShqU8zNg
…eived load

Inline a minimal HTML skeleton (header bar, map placeholder, panels grid)
with critical CSS directly in index.html so the page shows a structured
layout immediately instead of a blank screen while JavaScript boots.

- Dark theme skeleton with hardcoded colors matching CSS variables
- Light theme support via [data-theme="light"] selectors
- Shimmer animation on placeholder lines for visual feedback
- 6 skeleton panels in a responsive grid matching the real layout
- Map section with radial gradient matching the map background
- Skeleton is automatically replaced when App.renderLayout() sets innerHTML
- Marked aria-hidden="true" for screen reader accessibility

Expected gain: perceived load time drops to <0.5s.

https://claude.ai/code/session_01Fxk8GMRn2cEUq3ThC2a8e5
Replace the static LOCALE_LOADERS map (14 explicit dynamic imports) with
import.meta.glob for lazy loading. English is now statically imported as
the always-needed fallback; all other 13 locales are loaded on demand.

- Statically import en.json so it's bundled eagerly (no extra fetch)
- Use import.meta.glob with negative pattern to lazy-load non-English
  locales only when the user actually switches language
- Add manualChunks rule to prefix lazy locale chunks with `locale-`
- Exclude locale-*.js from service worker precache via globIgnores
- Add CacheFirst runtime caching for locale files when loaded on demand

SW precache reduced from 43 entries (5587 KiB) to 29 entries (4840 KiB),
saving ~747 KiB from the initial download.

https://claude.ai/code/session_01TfRgC5GWsv51swxRSGxxeJ
koala73 and others added 21 commits February 26, 2026 09:29
… filters (koala73#393)

PR koala73#382 accidentally removed the maplibregl.FilterSpecification type
cast, causing tsc to infer (string | string[])[] which doesn't satisfy
setFilter's parameter type. This broke the Vercel build.
…#392)

- Add to tech variant security feed group
- Add to main variant global feeds as type: 'cyber'
- Add www.ransomware.live to RSS proxy allowlist
- Set source tier to 3 (specialty)
Runs npm run typecheck (tsc --noEmit) on every PR to catch type
errors before merge. Would have prevented the koala73#382 regression.
…73#395)

Linux AppImage (koala73#370, koala73#257):
- Upgrade CI from Ubuntu 22.04 to 24.04 (GLib 2.80 fixes g_task_set_static_name symbol mismatch)
- Set GDK_BACKEND=wayland,x11 when WAYLAND_DISPLAY detected (fixes GTK init on niri/river)
- Disable WebKit bubblewrap sandbox in AppImage context (FUSE mount breaks it → blank screen)
- Set GTK_IM_MODULE to built-in simple context in AppImage (prevents host module conflicts)

RSS proxy:
- Add 32 missing domains to allowlist (EuroNews lang variants, international news feeds)
- Normalize www prefix in domain validation (prevents entire class of www/non-www mismatch 403s)

Console cleanup:
- TrendingKeywords: console.log → console.debug for suppressed terms (30+ noisy log lines)
- PostHog: add ui_host for reverse proxy setup (fixes /ingest/ 404s)
…73#396)

Add a "Streaming" section to the GENERAL settings tab with a quality
dropdown (Auto / Low 360p / Medium 480p / High / HD 720p). The setting
persists to localStorage and applies to all live streams:

- LiveWebcamsPanel: appends vq= to direct embeds, passes through proxy
- LiveNewsPanel: sets quality via YT.Player API onReady + desktop proxy
- YouTube embed proxy: accepts vq param, calls setPlaybackQuality()

Closes koala73#365
…koala73#400)

* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns
…ala73#399)

* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* fix: sort supply chain chokepoints by disruption score descending
* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* chore: bump version to 2.5.9 and make pre-push hook executable

* docs: update README with supply chain intel, universal CII, Happy Monitor, security hardening, and recent features
…oise filters (koala73#402)

- CSS.escape() on data-news-id querySelector to prevent SyntaxError
  when news item IDs contain special characters (WORLDMONITOR-5J)
- typeof guard on this.player.destroy() for partially-initialized
  YouTube players (WORLDMONITOR-5C/5B)
- 11 new ignoreErrors patterns for IndexedDB races, browser vendor
  internals, and extension-injected errors
- beforeSend filter for blob: URL extension frames
…7 locales (koala73#403)

The header.settings i18n key was incorrectly set to "PANELS" (and its
translations) for the settings modal title. The modal contains General,
Panels, and Sources tabs — the overall title should be "SETTINGS".
…3#404)

* fix: sequential Yahoo calls, sector fallback, and missing try-catch guards

- list-commodity-quotes: replace Promise.all with fetchYahooQuotesBatch to prevent Yahoo 429
- get-sector-summary: add Yahoo Finance fallback when FINNHUB_API_KEY is missing
- list-etf-flows: sequential fetch loop + add missing try-catch around cachedFetchJson
- get-macro-signals: replace unnecessary Promise.allSettled([single]) with direct await

* fix: tighten AI summary prompts to 2 sentences / 60 words max

Summaries were often verbose walls of text despite "2-3 sentences" in
the prompt. Changed to "2 concise sentences MAX (under 60 words total)"
across all brief, analysis, and fallback prompts. Reduced max_tokens
from 150 to 100 to hard-cap output length.
…uards (koala73#406)

- list-commodity-quotes: replace Promise.all with fetchYahooQuotesBatch to prevent Yahoo 429
- get-sector-summary: add Yahoo Finance fallback when FINNHUB_API_KEY is missing
- list-etf-flows: sequential fetch loop + add missing try-catch around cachedFetchJson
- get-macro-signals: replace unnecessary Promise.allSettled([single]) with direct await
…sistence (koala73#407)

- Show rate-limited message instead of generic "Failed to load" on Markets,
  ETF, Commodities, and Sector panels when Yahoo returns 429
- fetchYahooQuotesBatch returns rateLimited flag; early-exit after 3 misses
- ETF panel skips retry loop when rate-limited, shows specific i18n message
- Fallback Finnhub symbols through Yahoo when API key missing
- 401-retry in runtime fetch patch for stale sidecar token after restart
- diagFetch auth helper for settings window diagnostic endpoints
- Verbose toggle writes to writable dataDir instead of read-only app bundle
* chore: bump v2.5.10 and update README for recent fixes

Version 2.5.9 → 2.5.10. Roadmap entries for:
- Yahoo Finance rate-limit UX across all market panels
- Sidecar auth resilience (401-retry, settings diagFetch)
- Verbose toggle persistence to writable data directory
- Finnhub-to-Yahoo fallback routing

* chore: add v2.5.10 changelog entry
…oala73#347) (koala73#410)

showOfflineMessage() and showEmbedError() replaced innerHTML without
cleaning up this.player, so initializePlayer() bailed early on the
next channel switch — leaving a black screen. destroyPlayer() is
idempotent so the onError path won't double-destroy.

Co-authored-by: N Cho-chin <Niboshi-Wasabi@users.noreply.github.com>
Fixes:
- Linux AppImage black screen on WebKit/GStreamer (koala73#411)
- Destroy live news player before showing offline/error message (koala73#410)
Launch the built AppImage under Xvfb after the Linux build to catch
startup crashes and render failures automatically. Uploads a screenshot
artifact for visual inspection.
…oala73#414)

The squash merge of koala73#413 put smoke test steps into build-desktop.yml
instead of the intended standalone workflow. This commit:
- Reverts build-desktop.yml to its original state
- Adds test-linux-app.yml as a separate Linux-only smoke test workflow
@Jacob-Strokus
Copy link
Author

awesome. Ill work on this today and put in a PR to address the Final review. Cheers

koala73 and others added 5 commits February 26, 2026 17:57
* fix(ci): use weston+XWayland for Linux smoke test instead of pure Wayland

Previous attempt used GDK_BACKEND=wayland which caused GTK init panic
(tao requires X11). Now: weston headless with XWayland provides X11
through a real compositor. Falls back to Xvfb if weston fails.
Also uploads weston/app logs as artifacts for debugging.

* refactor(ci): use xwfb-run for Linux smoke test display

xwfb-run (from xwayland-run package) is purpose-built for this:
Xwayland on a headless Wayland compositor, replaces xvfb-run.
Falls back to plain Xvfb if xwfb-run is unavailable.
Uploads display-server and app logs as artifacts.
Only run CI on PRs from branches within the repo, not from external
forks. Prevents unnecessary action minutes from contributor PRs.
* ci(linux): add AppImage smoke test to desktop build

Launch the built AppImage under Xvfb after the Linux build to catch
startup crashes and render failures automatically. Uploads a screenshot
artifact for visual inspection.

* Optimize Wingbits API usage and panel polling
…oala73#419)

Linux had no keyring backend feature enabled — keyring v3 fell back to
in-memory mock store. Secrets appeared to save but were lost on restart.

Added `linux-native-sync-persistent` (kernel keyutils + D-Bus Secret
Service combo) and `crypto-rust` for Secret Service encryption. This
uses GNOME Keyring or KDE Wallet on desktop Linux for persistent storage.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.