docs/Docs_To_Review/TODO_Performance.md
All items below target end-user perceived speed: faster initial load, smoother panel rendering, lower memory footprint, and snappier map interactions. Items are ordered roughly by expected impact.
Priority: 🔴 High impact · 🟡 Medium impact · 🟢 Low impact (polish).
Status: · 🔄 Partial · ❌ Not started
vite.config.ts manualChunks splits panel components into a dedicated panels chunk, loaded in parallel with the main bundle for better caching and reduced initial parse time.App.ts statically imports all 35+ panel components, bloating the main bundle to ~1.5 MB.import() and only load when the user enables that panel.App.ts with await import('@/components/FooPanel'). Use Vite's built-in chunk splitting.src/services/i18n.ts uses per-language dynamic import() via LOCALE_LOADERS. Only en.json is bundled eagerly; all other locales are lazy-loaded on demand.import(@/locales/${lang}.json) only the active language. Pre-load the fallback (en.json) and lazy-load the rest.src/utils/index.ts provides deferToIdle() using requestIdleCallback with setTimeout fallback. App.loadAllData() defers non-critical fetches (UCDP, displacement, climate, fires, stablecoins, cable activity) by 5 seconds, keeping news/markets/conflicts/CII as priority.App.init() fires ~30 fetch calls simultaneously on startup. Most are background data (UCDP, displacement, climate, fires, stablecoins).requestIdleCallback.index.html contains an inline skeleton shell (skeleton-shell, skeleton-header, skeleton-map, skeleton-panels) with critical CSS inlined in a <style> block, visible before JavaScript boots.index.html (dark background, header bar, map placeholder, sidebar placeholder).vite.config.ts sets build.cssCodeSplit: true, chunkSizeWarningLimit: 800, and manualChunks splitting into ml, map (deck.gl/maplibre-gl/h3-js), d3, topojson, i18n, and sentry vendor chunks.build.rollupOptions.output.manualChunks to split:
vendor-mapbox (deck.gl, maplibre-gl): ~400 KBvendor-charts (any chart libs)locale-[lang] per languagepanels (lazy group)build.cssCodeSplit: true for per-chunk CSS.vite.config.ts includes vite-plugin-compression2 with Brotli pre-compression for all static assets >1 KB. Pre-compressed .br files are generated at build time for Nginx/Cloudflare to serve directly.vite-plugin-compression. Serve .br files from Nginx/Cloudflare..br with gzip_static on and brotli_static on.vite.config.ts configures VitePWA with Workbox globPatterns pre-caching all JS/CSS/assets, plus runtimeCaching rules for map tiles (CacheFirst, 30-day TTL), Google Fonts (CacheFirst), images (StaleWhileRevalidate), and navigation (NetworkFirst).workbox-precaching to cache:
VirtualList.ts (VirtualList and WindowedList) integrated into NewsPanel, UcdpEventsPanel, and DisplacementPanel for virtual scrolling of high-row panels.VirtualList.ts component exists but is not used by most panels. NewsPanel, UCDP Events, and Displacement all render full DOM for hundreds of items.VirtualList into every panel that can display >20 rows.Panel.setContentThrottled() in src/components/Panel.ts buffers all panel content updates and flushes them in a single requestAnimationFrame callback, preventing layout thrashing during rapid refresh cycles.this.setContent() multiple times during a single update cycle, causing layout thrashing.requestAnimationFrame callback.src/utils/dom-utils.ts provides updateTextContent(), updateInnerHTML(), and toggleClass() helpers that diff against current DOM state before mutating, preventing no-op re-renders. Pairs with the RAF throttling in PERF-009.Panel.setContent() to batch rapid-fire updates.DocumentFragment for Batch DOM Insertionsrc/utils/dom-utils.ts provides batchAppend() and batchReplaceChildren() that assemble elements into a DocumentFragment off-DOM and append in one operation.innerHTML. For complex panels, pre-build a DocumentFragment off-DOM and append once.<style> Tags from Panel RendersSatelliteFiresPanel and OrefSirensPanel moved to src/styles/panels.css, loaded once via main.css. Inline <style> blocks removed from setContent() calls.SatelliteFiresPanel, OrefSirensPanel, and CIIPanel inject <style> blocks on every render.src/styles/panels.css (loaded once). Remove inline <style> from setContent() calls.src/utils/visibility-manager.ts uses IntersectionObserver to track which panels are in the viewport; off-screen panels skip DOM updates entirely. Complements the DOM-diff helpers in dom-utils.ts (PERF-010).setContent() replaces the entire panel innerHTML on every update. This destroys focus, scroll position, and animations.contain Property on Panelssrc/styles/main.css sets contain: content on .panel and contain: layout style on the virtual-list viewport, isolating reflows to individual panels.contain: content to .panel and contain: layout style to .panel-body.will-change for Animated Elementssrc/styles/main.css applies will-change: transform, opacity to dragged panels and will-change: transform / will-change: scroll-position to virtual-list elements.will-change: transform to elements with CSS transitions (panel collapse, modal fade, map markers).innerHTML with Incremental DOM Utilitiessrc/utils/dom-utils.ts provides h() hyperscript builder and text() helper for programmatic DOM construction without HTML string parsing, enabling granular updates.h() function that creates elements programmatically instead of parsing HTML strings.src/utils/fetch-cache.ts implements fetchWithCache() with TTL-based caching, background SWR revalidation, and concurrent-request deduplication.fetchWithCache(url, ttl) utility that:
fetch() calls across services with this utility.fetchWithCache() in src/utils/fetch-cache.ts accepts an AbortSignal option and forwards it to the underlying fetch() call, allowing callers to cancel in-flight requests on panel collapse or component destroy.AbortController to all fetch calls, cancel on component destroy / panel collapse.api/aggregate.js Vercel serverless function accepts ?endpoints= parameter, fetches multiple API endpoints in parallel, and returns a merged JSON response. Reduces HTTP round-trips from ~30 to ~5 on startup./api/aggregate that returns a combined JSON payload with: news, markets, CII, conflicts, fires, signals — in one request.src-tauri/sidecar/local-api-server.mjs adds zlib.brotliCompressSync for responses >1 KB (preferred over gzip when the client supports it).Content-Encoding properly and the Nginx proxy is configured for Brotli compression.local-api-server.mjs), add zlib.brotliCompress for responses >1 KB.src/services/persistent-cache.ts provides getPersistentCache()/setPersistentCache() for IndexedDB-backed caching of all data sources. Used by RSS feeds, news, and other services for offline-first display.src/utils/sse-client.ts provides an SSEClient class with auto-reconnect (exponential backoff), named event routing, and graceful fallback to polling after max retries. Ready for server-side SSE endpoint integration.deploy/nginx-http2-push.conf configures HTTP/2 server push for critical JS/CSS assets. Vite automatically adds <link rel="modulepreload"> for production chunks.<script> tags.earthquakes.js, firms-fires.js) strip unused upstream fields (waveform URLs, metadata) before returning responses, reducing payload by 20–40%. acled-conflict.js already sanitized fields.src/components/DeckGLMap.ts maintains a layerCache: Map<string, Layer> and uses deck.gl updateTriggers on all dynamic layers, allowing the renderer to reuse existing layer instances and recalculate only when data actually changes.data prop.updateTriggers to control when expensive recalculations happen.src/utils/tile-prefetch.ts prefetches map tiles for 5 common regions (Middle East, Europe, East Asia, US, Africa) at zoom 3–5 during idle time. Tiles populate the Workbox service worker cache for instant renders.src/components/DeckGLMap.ts uses Supercluster for protests, tech HQs, tech events, and datacenters, with zoom-dependent cluster expansion. Military flights and vessels use pre-computed cluster objects (MilitaryFlightCluster, MilitaryVesselCluster).src/utils/geo-bounds.ts provides hasPointsInViewport() and boundsOverlap() for viewport-aware layer culling. Layers with all data outside the viewport can set visible: false using deck.gl's built-in prop.deck.gl's visible flag bound to viewport bounds checks.DeckGLMap.ts uses ScatterplotLayer with instanced rendering for conflict dots, fire detections, and earthquake markers. IconLayer is reserved for markers requiring distinct textures.ScatterplotLayer with instanced rendering instead of IconLayer with per-marker textures.src/utils/perf-monitor.ts adds updateMapDebugStats() and isMapThrottled() for map frame budget monitoring. Shows FPS, layer count, draw calls in the ?debug=perf overlay and throttles layer updates when FPS drops below 30.src/utils/geo-simplify.ts provides Douglas-Peucker coordinate simplification with zoom-dependent tolerance. At zoom <5, uses 0.01° tolerance for ~80% vertex reduction.src/utils/data-structures.ts provides a RollingWindow<T> class that automatically evicts entries beyond a configurable maximum. src/utils/fetch-cache.ts provides evictStaleCache(), called every 60 seconds from src/main.ts to purge entries older than 5 minutes.src/utils/dom-utils.ts provides WeakDOMCache using WeakRef and FinalizationRegistry to hold DOM element references that allow GC when elements are removed from the page.WeakRef for optional DOM caches to allow GC.Panel.ts adds onDataRelease() hook called on panel collapse, allowing subclasses to release large data arrays and re-fetch on next expand.src/utils/data-structures.ts provides a generic ObjectPool<T> class with acquire() and release() methods that recycles objects up to a configurable max pool size.src/utils/visibility-manager.ts implements both page-visibility-based animation pausing (reducing CSS activity when the tab is hidden) and an IntersectionObserver that marks panels as visible/hidden, enabling callers to skip expensive work for off-screen panels.App instance in closure scope.src/workers/analysis.worker.ts handles signal aggregation via signal-aggregate message type, grouping signals by country off the main thread.src/workers/rss.worker.ts offloads RSS/XML parsing (both RSS 2.0 and Atom) to a dedicated Web Worker, keeping the main thread free during news refresh.src/workers/geo-convergence.worker.ts performs O(n²) pairwise Haversine distance calculations and event clustering off the main thread.src/workers/cii.worker.ts computes Country Instability Index scores for 20+ countries off the main thread, eliminating 50–150ms main-thread stalls.src/utils/shared-buffer.ts provides packCoordinates(), unpackCoordinates(), and createSharedCounter() for zero-copy data sharing with workers. Cross-Origin-Isolation headers documented in deploy/nginx-http2-push.conf.font-display: swap via URL parameter. Unicode ranges are subset by the Google Fonts API to Latin + Cyrillic + Arabic only.src/utils/font-loader.ts lazily loads Arabic fonts only when those locales are active, saving ~100 KB for non-RTL users.vite.config.ts sets cacheDir: '.vite' for persistent filesystem caching between builds. .vite directory added to .gitignore.build.cache: true and ensure .vite cache directory persists between deployments.vite.config.ts configures optimizeDeps.include to pre-bundle deck.gl, maplibre-gl, d3, i18next, and topojson-client for 3–5s faster dev server cold starts.optimizeDeps.include to pre-bundle heavy dependencies (deck.gl, maplibre-gl) for faster dev server cold starts.Cache-Control: public, max-age=N, s-maxage=N, stale-while-revalidate=M headers. Examples: hackernews.js (5 min), yahoo-finance.js (60 s), acled-conflict.js (5 min), coingecko.js (2 min), country-intel.js (1 hr).Cache-Control headers on all API handlers: s-maxage=60 for news, s-maxage=300 for earthquakes, etc.index.html includes <link rel="preconnect"> for api.maptiler.com, a.basemaps.cartocdn.com, fonts.googleapis.com, fonts.gstatic.com, and WorldMonitor.io, plus <link rel="dns-prefetch"> for earthquake.usgs.gov, api.gdeltproject.org, and query1.finance.yahoo.com.<link rel="preconnect"> in index.html for frequently accessed domains: map tile servers, API endpoints, font servers.define and import.meta.env.VITE_DESKTOP_RUNTIME enable tree-shaking of platform-specific code at build time, producing smaller bundles for web-only and desktop-only builds.src/utils/perf-monitor.ts implements maybeShowDebugPanel(), activated by ?debug=perf in the URL, showing live FPS, DOM node count, JS heap usage, the last 5 panel render timings, and current Web Vitals — all updated on every animation frame./debug flag) showing: FPS, memory usage, DOM node count, active fetch count, worker thread status, and panel render times.src/utils/perf-monitor.ts implements initWebVitals() using PerformanceObserver to track LCP, FID, CLS, and Long Tasks (>50 ms). Called early in src/main.ts and values are shown in the debug panel and logged to console.web-vitals library to report Core Web Vitals to the console (dev) or to a lightweight analytics endpoint (prod).scripts/check-bundle-size.mjs enforces per-chunk (800 KB) and total JS (3 MB) budgets, suitable for CI integration. Complements vite.config.ts chunkSizeWarningLimit.bundlesize or Vite's built-in build.chunkSizeWarningLimit.e2e/memory-leak.spec.ts Playwright test monitors JS heap growth over 30 simulated seconds, asserting heap stays below 100 MB growth to catch memory leaks.src/utils/perf-monitor.ts provides measurePanelRender(panelId, fn) which uses performance.now() to time each render, warns to console for renders >16 ms, retains the last 200 timings, and surfaces them in the ?debug=perf overlay.Panel.setContent() with performance.mark() / performance.measure(). Log panels that take >16ms to render.