Skip to content

feat: add GATRA SOC integration layer#293

Open
ghifiardi wants to merge 1161 commits intokoala73:mainfrom
ghifiardi:feat/gatra-integration-layer
Open

feat: add GATRA SOC integration layer#293
ghifiardi wants to merge 1161 commits intokoala73:mainfrom
ghifiardi:feat/gatra-integration-layer

Conversation

@ghifiardi
Copy link

Summary

  • src/gatra/connector.ts — Unified connector that fetches all GATRA 5-agent pipeline data (ADA alerts, TAA analyses, CRA actions, agent health, correlations) in parallel with pub/sub snapshot notifications
  • src/panels/gatra-soc-panel.ts — Enhanced dashboard panel with agent status dots, incident stats, severity-colored alert feed, TAA threat analysis (actor/campaign/kill-chain), CRA response actions, and dynamic World Monitor correlation insights
  • src/layers/gatra-alerts-layer.ts — Standalone deck.gl layer factory with severity-colored ScatterplotLayer markers and pulsing rings for critical/high alerts
  • Extends src/services/gatra.ts with TAA analysis, correlation, and typed CRA action mock data using realistic Indonesian locations (Jakarta, Surabaya, Bandung, Medan, Makassar) and IOH infrastructure references
  • Wires the missing createGatraAlertsLayers() method in DeckGLMap.ts
  • Adds gatraAlerts field to all variant MapLayers definitions (finance, full, tech, e2e harnesses)

Test plan

  • Verify npx tsc --noEmit passes with zero errors
  • Switch to cyber variant and confirm GATRA SOC panel renders with all 6 sections
  • Confirm GATRA alert markers appear on the map with red pulsing for critical alerts
  • Verify agent health dots show correct color coding (green/yellow/red)
  • Check that correlation insights dynamically reference alert locations

🤖 Generated with Claude Code

These CSS custom properties were used in 14 places but never defined,
causing transparent backgrounds on playback panel, toggle button,
header flash animation, select options, and offline retry button.
"here" and other basic English words (pronouns, prepositions, adverbs)
were not in SUPPRESSED_TRENDING_TERMS, causing false keyword spike
findings for common words.
"here" and other basic English words (pronouns, prepositions, adverbs)
were not in SUPPRESSED_TRENDING_TERMS, causing false keyword spike
findings for common words.
…ages

Audit found missing keys in all non-EN locales (62-90 per file) and 25
stale keys in PT/NL/SV. Fixes gaps, removes stale keys, corrects FR
infra key misplacement, and adds complete Russian (ru) translation with
1013/1013 keys matching the English reference.
When the NER model isn't loaded, isSignificantTerm was returning true
for all terms, letting common words like "here" trigger keyword spike
findings. Now falls back to a capitalization heuristic: single-word
terms must appear capitalized (mid-sentence) in >50% of headlines to
be treated as significant. Also added ~45 missing English stopwords.
When the NER model isn't loaded, isSignificantTerm was returning true
for all terms, letting common words like "here" trigger keyword spike
findings. Now falls back to a capitalization heuristic: single-word
terms must appear capitalized (mid-sentence) in >50% of headlines to
be treated as significant. Also added ~45 missing English stopwords.
Shows a compact pill badge (bottom-right) inviting users to the GitHub
Discussions page. Includes pulsing dot indicator, CTA button, close
button, and "Don't show again" option that persists to localStorage.
Floating pill badge (bottom-right) with pulsing dot, "Join the
Discussion" text, and CTA linking to GitHub Discussions koala73#94.
Shows 15s after initial data load. "Don't show again" persists
dismissal to localStorage (wm-community-dismissed).
…case terms

P1: Start-of-headline matches (idx=0) no longer count against the
capitalization ratio. When all matches are headline-start only,
falls back to acronym check instead of returning false.

P2: Removed dead [A-Z]{2,} shortcut — terms are lowercased by
asDisplayTerm() before reaching isLikelyProperNoun.
…rrides

These imports were accidentally committed from the i18n PR branch.
The files don't exist on main, causing Vercel build errors.
- Add ar.json and zh.json translations (1013 keys each, full parity)
- RTL support: dir="rtl" on <html>, targeted CSS overrides in rtl-overrides.css
- Font fallbacks: system Arabic/CJK fonts via --font-body CSS variable
- i18n.ts: RTL_LANGUAGES set, applyDocumentDirection(), isRTL(), getLocale()
- story-renderer.ts: locale-aware date formatting via getLocale()
- Type declarations for both new locales
…TC clock

Replace hardcoded English text with t() calls across 12 source files:
signal context descriptions, alert titles/summaries, intel topic
names, map legend labels, asset/threat labels, UCDP panel headers,
stablecoin/status panel sections. Add ~114 new keys to en.json and
translate to all 11 locales (fr/de/es/it/pl/pt/nl/sv/ru/ar/zh).
Remove dead #timeDisplay span that was never updated by JS.

All 12 locales at 1127-key parity with 0 placeholder mismatches.
…AppImage, locale gaps, lang normalization

- Fix relative-time {{count}} placeholders in all 12 locales (5m ago, not m ago)
- Localize CommunityWidget: replace 3 hardcoded English strings with t() calls
- Add Linux AppImage to tech/finance Tauri configs, CI matrix (ubuntu-22.04), packaging scripts, and download-node.sh
- Fix language code normalization: add supportedLngs/nonExplicitSupportedLngs to i18next, normalize getCurrentLanguage()
- Translate ~240 untranslated English strings across 11 locale files (ru/ar/zh now 100% translated)
- Add components.community section to all 12 locales
- All 12 locales at 1130-key parity, 0 placeholder mismatches
applyDocumentDirection() now splits on '-' before checking RTL set,
so ar-SA correctly triggers dir="rtl" on first detect.
- CommunityWidget: aria-label="Close" → t('common.close')
- App.ts: ' (current)' suffix → t('common.currentVariant')
- fr.json: "Cloud & Infrastructure" → "Cloud et infrastructure"
- sv.json: "24h Vol" → "24t volym"
- Added common.close + common.currentVariant to all 12 locales
New ja.json with full translation parity to en.json.
Registered in i18n.ts: import, resources, supportedLngs, LANGUAGES, getLocale map.
t() always returns a string (key itself if missing), so || 'English'
fallbacks were unreachable dead code.
t() always returns a string, so || 'English' fallbacks were
unreachable. Removed all 15 instances.
Main variant: NHK World + Nikkei Asia in asia category.
Finance variant: Nikkei Asia in markets category.
Added asia.nikkei.com to RSS proxy allowlist.
Main variant: NHK World + Nikkei Asia in asia category.
Finance variant: Nikkei Asia in markets category.
Added asia.nikkei.com to RSS proxy allowlist.
…keys

- CommunityWidget: add DOM check to prevent duplicate widgets on repeated loadNews() calls
- RuntimeConfigPanel: compare t() result against key path to suppress missing help translations
koala73 and others added 23 commits February 23, 2026 05:29
* chore: apply cargo fmt formatting to main.rs

Pure formatting normalization with no logic changes. Separated from
the behavioral fix to keep git blame clean.

https://claude.ai/code/session_01RPQ1PEqxTSEG6rB5XadzEz

* fix: restrict settings-window re-focus to macOS to avoid Windows focus churn

On Windows, the Focused(true) handler on the main window calls
show()+set_focus() on the settings window, which steals focus back,
retriggering the event in a tight loop and presenting as a UI hang.

Gate the match arm with #[cfg(target_os = "macos")] (compile-time
attribute) instead of cfg!() (runtime macro) to match the convention
used by the adjacent macOS-only handlers and eliminate dead code on
non-macOS builds entirely.

https://claude.ai/code/session_01RPQ1PEqxTSEG6rB5XadzEz

---------

Co-authored-by: Claude <noreply@anthropic.com>
…oala73#232)

* fix(sentry): add noise filters for 5 non-actionable error patterns

Filter dynamic import alt phrasing, script parse errors, maplibre
style/WebGL crashes, and CustomEvent promise rejections. Also fix
beforeSend to catch short Firefox null messages like "E is null".

* fix: cache write race, settings stale key status, yahoo gate concurrency

P1: Replace async background thread cache write with synchronous fs::write
to prevent out-of-order writes and dirty flag cleared before persistence.

P2: Add WorldMonitorTab.refresh() called after loadDesktopSecrets() so
the API key badge reflects actual keychain state.

P3: Replace timestamp-based Yahoo gate with promise queue to ensure
sequential execution under concurrent callers.

* feat: add Upstash Redis shared caching to all RPC handlers + fix cache key contamination

- Add Redis L2 cache (getCachedJson/setCachedJson) to 28 RPC handlers
  across all service domains (market, conflict, cyber, economic, etc.)
- Fix 10 P1 cache key contamination bugs where under-specified keys
  caused cross-request data pollution (e.g. filtered requests returning
  unfiltered cached data)
- Restructure list-internet-outages to cache-then-filter pattern so
  country/timeRange filters always apply after cache read
- Add write_lock mutex to PersistentCache in main.rs to prevent
  desktop cache write-race conditions
- Document FMP (Financial Modeling Prep) as Yahoo Finance fallback TODO
  in market/v1/_shared.ts

* fix: cache-key contamination and PizzINT/GDELT partial-failure regression

- tech-events: fetch with limit=0 and cache full result, apply limit
  slice after cache read to prevent low-limit requests poisoning cache
- pizzint: restore try-catch around PizzINT fetch so GDELT tension
  pairs are still returned when PizzINT API is down

* fix: remove extra closing brace in pizzint try-catch

* fix: recompute conferenceCount/mappableCount after limit slice

* fix: bypass WM API key gate for registration endpoint

/api/register-interest must reach cloud without a WorldMonitor API key,
otherwise desktop users can never register (circular dependency).
…a73#243)

* fix: resolve AppImage blank white screen and font crash on Linux (koala73#238)

Disable WebKitGTK DMA-BUF renderer by default on Linux to prevent blank
white screens caused by GPU buffer allocation failures (common with
NVIDIA drivers and immutable distros like Bazzite). Add Linux-native
monospace font fallbacks (DejaVu Sans Mono, Liberation Mono) to all font
stacks so WebKitGTK font resolution doesn't hit out-of-bounds vector
access when macOS-only fonts (SF Mono, Monaco) are unavailable.

https://claude.ai/code/session_01TF2NPgSSjgenmLT2XuR5b9

* fix: consolidate monospace font stacks into --font-mono variable

- Define --font-mono in :root (main.css) and .settings-shell (settings-window.css)
- Align font stack: SF Mono, Monaco, Cascadia Code, Fira Code, DejaVu Sans Mono, Liberation Mono
- Replace 3 hardcoded JetBrains Mono stacks with var(--font-mono)
- Replace 4 hardcoded settings-window stacks with var(--font-mono)
- Fix pre-existing bug: var(--font-mono) used in 4 places but never defined
- Match index.html skeleton font stack to --font-mono

---------

Co-authored-by: Claude <noreply@anthropic.com>
* feat: make intelligence alert popup opt-in via dropdown toggle

Auto-popup was interrupting users every 10s refresh cycle. Badge still
counts and pulses silently. New toggle in dropdown (default OFF) lets
users explicitly opt in to auto-popup behavior.

* chore: bump version to 2.5.5

## Changelog

### Features
- Intelligence alert popup is now opt-in (default OFF) — badge counts silently, toggle in dropdown to enable auto-popup

### Bug Fixes
- Linux: disable DMA-BUF renderer on WebKitGTK to prevent blank white screen (NVIDIA/immutable distros)
- Linux: add DejaVu Sans Mono + Liberation Mono font fallbacks for monospace rendering
- Consolidate monospace font stacks into --font-mono CSS variable (fixes undefined var bug)
- Reduce dedup coordinate rounding from 0.5° to 0.1° (~10km precision)
- Vercel build: handle missing previous deploy SHA
- Panel base class: add missing showRetrying method
- Vercel ignoreCommand shortened to fit 256-char limit

### Infrastructure
- Upstash Redis shared caching for all RPC handlers + cache key contamination fix
- Format Rust code and fix Windows focus handling

### Docs
- Community guidelines: contributing, code of conduct, security policy
- Updated .env.example

* chore: track Cargo.lock for reproducible Rust builds

* fix: update layer help popup with all current map layers

Added missing layers to the ? help popup across all 3 variants:
- Full: UCDP Events, Displacement, Spaceports, Cyber Threats, Fires,
  Climate Anomalies, Critical Minerals; renamed Shipping→Ship Traffic
- Tech: Tech Events, Cyber Threats, Fires
- Finance: GCC Investments

* docs: update README with crypto prices, analytics, typography, and dedup grid fix

* fix: add /ingest to service worker NetworkOnly routes

The SW was intercepting PostHog /ingest/* requests and returning
no-response (404) because no cache match existed. Adding NetworkOnly
ensures analytics requests pass through to Vercel's rewrite proxy.

* chore: update Cargo.lock for v2.5.5

* fix: use explicit colors for findings toggle switch visibility
)

vi → vn (ISO 3166-1 alpha-2 for Vietnam). Also add explicit th mapping for Thailand.
- Fix PostHog /ingest 404: Workbox registerRoute defaults to GET only,
  PostHog sends POST. Add POST routes for /api/ and /ingest/.
- Fix fullscreen crash: optional chaining on exitFullscreen()?.catch()
  for browsers returning undefined instead of Promise.
- Add 6 noise filters: __firefox__, ifameElement.contentDocument,
  Invalid video id, Fetch is aborted, Stylesheet append timeout,
  Cannot assign to read only property.
- Widen Program failed to link filter (remove ": null" suffix).
The broad regex /^https?:\/\/.*\/api\/.*/i matched ANY URL with /api/
in the path, including external APIs like NASA EONET
(eonet.gsfc.nasa.gov/api/v3/events). Workbox intercepted these
cross-origin requests with NetworkOnly, causing no-response errors
when CORS failed.

Changed all /api/, /ingest/, and /rss/ SW route patterns to use
sameOrigin callback check so only our Vercel routes get NetworkOnly
handling. External APIs now pass through without SW interference.
koala73#251)

* fix: restrict SW route patterns to same-origin only

The broad regex /^https?:\/\/.*\/api\/.*/i matched ANY URL with /api/
in the path, including external APIs like NASA EONET
(eonet.gsfc.nasa.gov/api/v3/events). Workbox intercepted these
cross-origin requests with NetworkOnly, causing no-response errors
when CORS failed.

Changed all /api/, /ingest/, and /rss/ SW route patterns to use
sameOrigin callback check so only our Vercel routes get NetworkOnly
handling. External APIs now pass through without SW interference.

* fix: whitelist social preview bots on OG image assets

Slack-ImgProxy (distinct from Slackbot) was blocked from fetching
/favico/og-image.png by both our bot filter and Vercel Attack Challenge.
Extend middleware matcher to /favico/* and allow all social preview/image
bots through on static asset paths.
Full translation of all 1,397 i18n keys to Greek.
Registers 'el' in SUPPORTED_LANGUAGES, LANGUAGES display array, and getLocale() map.
…a73#262)

On Windows, Tauri webviews send requests with origin
`http://tauri.localhost` (HTTP), but the CORS allowlist only permitted
`https://tauri.localhost` (HTTPS). This caused every sidecar API
request to be blocked by CORS, making the app non-functional on
Windows with a "sidecar not reachable" error.

Change the regex from `^https:` to `^https?:` so both HTTP and HTTPS
origins from tauri.localhost are accepted.

https://claude.ai/code/session_016XMWtTPfE81bitu3QEoUwy

Co-authored-by: Claude <noreply@anthropic.com>
…ching (koala73#266)

* perf: optimize WebSocket relay to prevent Railway crashes under load

Key fixes:
- Fix crash: guard all res.writeHead() calls against "headers already sent"
  (the ais-relay.cjs:740 crash from error/timeout race conditions)
- Pre-serialize + pre-gzip AIS snapshots — eliminates JSON.stringify and
  zlib.gzip on every HTTP request to /ais/snapshot
- Add upstream WebSocket backpressure: pause/resume at 4MB/512KB watermarks
  to prevent unbounded buffer growth and OOM
- Replace O(chokepoints × vessels) disruption detection with O(chokepoints)
  spatial bucket lookup populated at ingest time
- Cap all Maps (vessels: 50K, history: 50K, density: 5K, candidates: 1.5K)
  to prevent unbounded memory growth between cleanup cycles
- Reduce WS fanout from every 10th to every 50th message with per-client
  backpressure (skip clients with >1MB buffered)
- Hoist all require('https'/'http'/'zlib') to top-level (6 inline calls
  removed from hot request paths)
- Add memory monitoring: 60s interval logs RSS/heap, emergency cache flush
  at 400MB RSS threshold
- Health endpoint now reports memory, droppedMessages, upstreamPaused state
- Delete errored WS clients from Set immediately

https://claude.ai/code/session_01WFQkHftPgni8oaWtznv1v5

* fix: harden AIS relay backpressure and chokepoint accuracy

---------

Co-authored-by: Claude <noreply@anthropic.com>
…oala73#263)

* fix: resolve AppImage crash on Ubuntu 25.10+ (GLib symbol mismatch)

The AppImage bundles GLib from the build system, but host GIO modules
(e.g. GVFS libgvfsdbus.so) compiled against a newer GLib reference
symbols like g_task_set_static_name that don't exist in the older
bundled copy, causing "undefined symbol" errors and WebKit crashes.

Set GIO_MODULE_DIR="" when running as AppImage to prevent host GIO
modules from loading against the incompatible bundled GLib. GVFS
features (network mounts, trash, MTP) are unused by this app.

Note: the CI should also be upgraded from ubuntu-22.04 to ubuntu-24.04
in .github/workflows/build-desktop.yml to ship GLib 2.80+ and extend
forward-compatibility. This requires workflows permission to push.

https://claude.ai/code/session_01J8HBrfb26GJm22MFCeGoAA

* fix(appimage): keep bundled GIO modules for Ubuntu 25.10

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Add Brasil Paralelo source

One of the biggest independent media company in Brazil

* Add Brasil Paralelo domain to rss-proxy.js

* fix: add missing comma and https:// protocol for Brasil Paralelo feed

---------

Co-authored-by: Elie Habib <elie.habib@gmail.com>
…la73#270)

rsshub.app was returning non-2xx responses (likely 429 rate-limit) which
were never cached, causing a thundering herd: 874 requests in 5 minutes
to the same URL instead of 1. The in-flight dedup also cascaded — when
waiters woke up and found no cache, they started their own fetches.

- Cache all RSS responses (non-2xx with 60s TTL vs 5min for success)
- Dedup waiters serve 502 on failure instead of cascading to new fetches
- Log upstream error status codes for future diagnosis
- Raise memory cleanup threshold to 450MB, only clear OpenSky cache
- Add 5 Nigeria news sources to Africa section (Premium Times, Vanguard,
  Channels TV, Daily Trust, ThisDay)
- Add 5 Greek feeds with lang: 'el' for locale-aware filtering
  (Kathimerini, Naftemporiki, in.gr, iefimerida, Proto Thema)
- Add source tiers for all new outlets
- Allowlist 8 new domains in RSS proxy
…73#277)

The existing scheduler skips fetches while the tab is hidden and
reschedules with a 4x delay. When the user returns, they could wait
up to interval x 4 (e.g. 20 min for a 5-min service) before seeing
fresh data.

Add flushStaleRefreshes() -- on visibilitychange, cancel pending
timeouts for any service whose hidden duration exceeded its refresh
interval and re-trigger them with 150ms stagger to avoid thundering
herd. Services that are not stale yet keep their existing schedule.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: persist circuit breaker cache to IndexedDB across page reloads

On page reload, all 28+ circuit breaker in-memory caches are lost,
triggering 20-30 simultaneous POST requests to Vercel edge functions.

Wire the existing persistent-cache.ts (IndexedDB + localStorage +
Tauri fallback) into CircuitBreaker so every breaker automatically:

- Hydrates from IndexedDB on first execute() call (~1-5ms read)
- Writes to IndexedDB fire-and-forget on every recordSuccess()
- Falls back to stale persistent data on network failure
- Auto-disables for breakers with cacheTtlMs=0 (live pricing)

Zero consumer code changes -- all 28+ breaker call sites untouched.
Reloads within the cache TTL (default 10min) serve instantly from
IndexedDB with zero network calls.

Also adds deletePersistentCache() to persistent-cache.ts for clean
cache invalidation via clearCache().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add Playwright e2e tests for circuit breaker persistent cache

7 tests covering: IndexedDB persistence on success, hydration on new
instance, TTL expiry forcing fresh fetch, 24h stale ceiling rejection,
clearCache cleanup, cacheTtlMs=0 auto-disable, and network failure
fallback to stale persistent data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: desktop cache deletion + clearCache race condition

P1: deletePersistentCache sent empty string to write_cache_entry,
which fails Rust's serde_json::from_str (not valid JSON). Add
dedicated delete_cache_entry Tauri command that removes the key
from the in-memory HashMap and flushes to disk.

P2: clearCache() set persistentLoaded=false, allowing a concurrent
execute() to re-hydrate stale data from IndexedDB before the async
delete completed. Remove the reset — after explicit clear there is
no reason to re-hydrate from persistent storage.

* fix: default persistCache to false, fix falsy data guard

P1b: 6 breakers store Date objects (weather, aviation, ACLED,
military-flights, military-vessels, GDACS) which become strings
after JSON round-trip. Callers like MapPopup.getTimeUntil() call
date.getTime() on hydrated strings → TypeError. Change default
to false (opt-in) so persistence requires explicit confirmation
that the payload is JSON-safe.

P2: `if (!entry?.data) return` drops valid falsy payloads (0,
false, empty string). Use explicit null/undefined check instead.

* fix: address blocking review issues on circuit breaker persistence

- clearCache() nulls persistentLoadPromise to orphan in-flight hydration
- delete_cache_entry defers disk flush to exit handler (avoids 14MB sync write)
- hydratePersistentCache checks TTL before setting lastDataState to 'cached'
- deletePersistentCache resets cacheDbPromise on IDB error + logs warning
- hydration catch logs warning instead of silently swallowing
- deletePersistentCache respects isStorageQuotaExceeded() for localStorage

---------

Co-authored-by: Elias El Khoury <efk@anghami.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(live): custom channel management — add/remove/reorder, standalone window, i18n

- Standalone channel management window (?live-channels=1) with list, add form, restore defaults
- LIVE panel: gear icon opens channel management; channel tabs reorderable via DnD
- Row click to edit; custom modal for delete confirmation (no window.confirm)
- i18n for all locales (manage, addChannel, youtubeHandle, displayName, etc.)
- UI: margin between channel list and add form in management window
- settings-window: panel display settings comment in English

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(tauri): channel management in desktop app, dev base_url fix

- Add live-channels.html and live-channels-main.ts for standalone window
- Tauri: open_live_channels_window_command, close_live_channels_window, open live-channels window (WebviewUrl::App or External from base_url)
- LiveNewsPanel: in desktop runtime invoke Tauri command with base_url (window.location.origin) so dev works when Vite runs on a different port than devUrl
- Vite: add liveChannels entry to build input
- capabilities: add live-channels window
- tauri.conf: devUrl 3000 to match vite server.port
- docs: PR_LIVE_CHANNEL_MANAGEMENT.md for PR koala73#276

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address review issues in live channel management PR

- Revert settings button to open modal (not window.open popup)
- Revert devUrl from localhost:3000 to localhost:5173
- Guard activeChannel against empty channels (fall back to defaults)
- Escape i18n strings in innerHTML with escapeHtml() to prevent XSS
- Only store displayNameOverrides for actually renamed channels
- Use URL constructor for live-channels window URL
- Add CSP meta tag to live-channels.html
- Remove unused i18n keys (edit, editMode, done) from all locales
- Remove unused CSS classes (live-news-manage-btn/panel/wrap)
- Delete PR instruction doc (PR_LIVE_CHANNEL_MANAGEMENT.md)

---------

Co-authored-by: Masaki <yukkurihakutaku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…atching (koala73#283)

* fix: add request coalescing to Redis cache layer

Concurrent cache misses for the same key now share a single upstream
fetch instead of each triggering redundant API calls. This eliminates
duplicate work within Edge Function invocations under burst traffic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: reduce AIS polling frequency from 10s to 30s

Vessel positions do not change meaningfully in 10 seconds at sea.
Reduces Railway relay requests by 66% with negligible UX impact.
Stale threshold bumped to 45s to match the new interval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: quantize military flights bbox cache keys to 1-degree grid

Precise bounding box coordinates caused near-zero cache hit rate since
every map pan/zoom produced a unique key. Snapping to a 1-degree grid
lets nearby viewports share cache entries, dramatically reducing
redundant OpenSky API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: parallelize ETF chart fetches instead of sequential await loop

The loop awaited each ETF chart fetch individually, blocking on every
Yahoo gate delay. Using Promise.allSettled lets all 10 fetches queue
concurrently through the Yahoo gate, cutting wall time from ~12s to ~6s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add Redis pipeline batch GET to reduce round-trips

Add getCachedJsonBatch() using the Upstash pipeline API to fetch
multiple keys in a single HTTP call. Refactor aircraft details batch
handler from 20 sequential GETs to 1 pipelined request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add structural tests for Redis caching optimizations

18 tests covering: cachedFetchJson request coalescing (in-flight dedup,
cache-before-fetch ordering, cleanup), getCachedJsonBatch pipeline API,
aircraft batch handler pipeline usage, bbox grid quantization (1-degree
step, expanded fetch bbox), and ETF parallel fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enforce military bbox contract and add behavioral cache tests

---------

Co-authored-by: Elias El Khoury <efk@anghami.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…oala73#284)

* fix: add request coalescing to Redis cache layer

Concurrent cache misses for the same key now share a single upstream
fetch instead of each triggering redundant API calls. This eliminates
duplicate work within Edge Function invocations under burst traffic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: reduce AIS polling frequency from 10s to 30s

Vessel positions do not change meaningfully in 10 seconds at sea.
Reduces Railway relay requests by 66% with negligible UX impact.
Stale threshold bumped to 45s to match the new interval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: quantize military flights bbox cache keys to 1-degree grid

Precise bounding box coordinates caused near-zero cache hit rate since
every map pan/zoom produced a unique key. Snapping to a 1-degree grid
lets nearby viewports share cache entries, dramatically reducing
redundant OpenSky API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: parallelize ETF chart fetches instead of sequential await loop

The loop awaited each ETF chart fetch individually, blocking on every
Yahoo gate delay. Using Promise.allSettled lets all 10 fetches queue
concurrently through the Yahoo gate, cutting wall time from ~12s to ~6s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add Redis pipeline batch GET to reduce round-trips

Add getCachedJsonBatch() using the Upstash pipeline API to fetch
multiple keys in a single HTTP call. Refactor aircraft details batch
handler from 20 sequential GETs to 1 pipelined request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add structural tests for Redis caching optimizations

18 tests covering: cachedFetchJson request coalescing (in-flight dedup,
cache-before-fetch ordering, cleanup), getCachedJsonBatch pipeline API,
aircraft batch handler pipeline usage, bbox grid quantization (1-degree
step, expanded fetch bbox), and ETF parallel fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enforce military bbox contract and add behavioral cache tests

---------

Co-authored-by: Elias El Khoury <efk@anghami.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a unified GATRA SOC integration layer for the World Monitor
cyber variant:

- src/gatra/connector.ts: Centralized connector that fetches all GATRA
  data sources (ADA alerts, TAA analyses, CRA actions, agent health,
  correlations) in parallel with pub/sub notifications
- src/panels/gatra-soc-panel.ts: Enhanced dashboard panel with 6
  sections — agent status, incident stats, alert feed, TAA threat
  analysis (actor/campaign/kill-chain), CRA response actions, and
  dynamic World Monitor correlation insights
- src/layers/gatra-alerts-layer.ts: Standalone deck.gl layer factory
  with severity-colored markers and pulsing rings for critical/high
  alerts
- Extends src/services/gatra.ts with TAA analysis, correlation, and
  typed CRA action mock data using Indonesian locations and IOH
  infrastructure references
- Wires createGatraAlertsLayers() in DeckGLMap.ts and adds gatraAlerts
  to all variant MapLayers definitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 24, 2026

@ghifiardi is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e782af66e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

research: { name: 'Security Research', enabled: true, priority: 1 },
policy: { name: 'Cyber Policy', enabled: true, priority: 2 },
aiSecurity: { name: 'AI & Security', enabled: true, priority: 2 },
'gatra-soc': { name: 'GATRA SOC', enabled: true, priority: 1 },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Instantiate the configured gatra-soc dashboard panel

This config enables a gatra-soc panel by default, but App.createPanels() never registers a panel instance for that key (and GatraSOCDashboardPanel has no usage sites), so the key is skipped when panel order is rendered and the SOC panel never appears in the cyber variant.

Useful? React with 👍 / 👎.

Comment on lines +3309 to +3311
public setGatraAlerts(alerts: GatraAlert[]): void {
this.gatraAlerts = alerts;
this.render();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire GATRA alert data into the new map layer path

The new setGatraAlerts entry point is never called anywhere in src, so this.gatraAlerts remains empty and the added render guard (mapLayers.gatraAlerts && this.gatraAlerts.length > 0) keeps the GATRA layer from ever drawing markers even when the toggle is enabled.

Useful? React with 👍 / 👎.

export const DEFAULT_PANELS = SITE_VARIANT === 'tech' ? TECH_PANELS : SITE_VARIANT === 'finance' ? FINANCE_PANELS : FULL_PANELS;
export const DEFAULT_MAP_LAYERS = SITE_VARIANT === 'tech' ? TECH_MAP_LAYERS : SITE_VARIANT === 'finance' ? FINANCE_MAP_LAYERS : FULL_MAP_LAYERS;
export const MOBILE_DEFAULT_MAP_LAYERS = SITE_VARIANT === 'tech' ? TECH_MOBILE_MAP_LAYERS : SITE_VARIANT === 'finance' ? FINANCE_MOBILE_MAP_LAYERS : FULL_MOBILE_MAP_LAYERS;
export const DEFAULT_PANELS = SITE_VARIANT === 'tech' ? TECH_PANELS : SITE_VARIANT === 'finance' ? FINANCE_PANELS : SITE_VARIANT === 'cyber' ? CYBER_PANELS : FULL_PANELS;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Align cyber panel defaults with actual feed loading

Switching SITE_VARIANT === 'cyber' to CYBER_PANELS exposes cyber-only news panels (for example indonesia, threats, malware), but news loading still iterates FEEDS from config/feeds.ts which has no cyber branch, so these default-enabled panels do not receive updates and stay empty/loading in cyber builds.

Useful? React with 👍 / 👎.

@koala73
Copy link
Owner

koala73 commented Feb 24, 2026

thank you @ghifiardi
I think this might be part of a bigger feature ?

Currently this can't be merged

P1 - High

Build is currently broken.
src/config/panels.ts:502 adds gatraAlerts: ['gatra'], but gatra is not a valid DataSourceId in src/services/data-freshness.ts:9.
I ran npm run typecheck on pr-293; it fails with:
Type '"gatra"' is not assignable to type 'DataSourceId'.

The gatra-soc panel is configured but never instantiated in the app.

src/config/panels.ts:393 enables the panel, but src/App.ts:2092 creates no GATRA panel instance.
Then src/App.ts:2430 silently skips panel keys that have no object in this.panels.

GATRA map data path is incomplete, so the map layer cannot be populated.

src/components/DeckGLMap.ts:3309 adds setGatraAlerts, but MapContainer has no corresponding setter around its data API (compare src/components/MapContainer.ts:320).
Also, app loaders never fetch/set GATRA data in layer flows (see src/App.ts:3150 and src/App.ts:3191).

Cyber variant is only partially wired into product flows.

SITE_VARIANT persistence/recognition still only handles full|tech|finance in src/config/variant.ts:4.
Feed routing still has no cyber branch in src/config/feeds.ts:966.
Header variant switcher exposes only full/tech/finance in src/App.ts:1837.
So cyber-specific panel/feed intent is not integrated end-to-end.

P2 - Medium

Duplicate, unused panel implementations increase maintenance risk.
Both src/components/GatraSOCPanel.ts:22 and src/panels/gatra-soc-panel.ts:62 define GATRA panels, but neither is wired into App.ts.

Integration + Value Assessment
Current value added to the shipped product is low-to-negative:

  1. It does not pass typecheck/build.
  2. The flagship panel/map integration paths are not connected in App.ts/MapContainer.
  3. Cyber variant rollout is incomplete across variant selection + feed plumbing.
  4. If fully wired, the feature could add clear value (SOC context + geo correlation), but in the current PR state it is not production-ready and should not merge.

Instantiate GatraSOCDashboardPanel for cyber variant, add loadGatraData()
method with 60s refresh cycle, wire gatraAlerts layer toggle, and add
setGatraAlerts() proxy in MapContainer. Also adds cyber variant to the
header switcher and enables local variant switching on localhost.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@koala73
Copy link
Owner

koala73 commented Feb 26, 2026

Feedback pls @ghifiardi

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.