mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat: add WTO trade policy service with 4 RPC endpoints and TradePolicyPanel Adds a new `trade` RPC domain backed by the WTO API (apiportal.wto.org) for trade policy intelligence: quantitative restrictions, tariff timeseries, bilateral trade flows, and SPS/TBT barrier notifications. New files: 6 protos, generated server/client, 4 server handlers + shared WTO fetch utility, client service with circuit breakers, TradePolicyPanel (4 tabs), and full API key infrastructure (Rust keychain, sidecar, runtime config). Panel registered for FULL and FINANCE variants with data loader integration, command palette entry, status panel tracking, data freshness monitoring, and i18n across all 17 locale files. https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye * chore: update package-lock.json https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye * fix: move tab click listener to constructor to prevent leak The delegated click handler was added inside render(), which runs on every data update (4× per load cycle). Since the listener targets this.content (a persistent container), each call stacked a duplicate handler. Moving it to the constructor binds it exactly once. https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye --------- Co-authored-by: Claude <noreply@anthropic.com>
29 KiB
29 KiB
WorldMonitor — Performance Optimization Roadmap
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
🔴 Critical Path — First Load & Time to Interactive
PERF-001 — Code-Split Panels into Lazy-Loaded Chunks
- Impact: 🔴 High | Effort: ~2 days
- Status: —
vite.config.tsmanualChunkssplits panel components into a dedicatedpanelschunk, loaded in parallel with the main bundle for better caching and reduced initial parse time. App.tsstatically imports all 35+ panel components, bloating the main bundle to ~1.5 MB.- Split each panel into a dynamic
import()and only load when the user enables that panel. - Implementation: Wrap each panel constructor in
App.tswithawait import('@/components/FooPanel'). Use Vite's built-in chunk splitting. - Expected gain: Reduce initial JS payload by 40–60%.
PERF-002 — Tree-Shake Unused Locale Files
- Impact: 🔴 High | Effort: ~4 hours
- Status: —
src/services/i18n.tsuses per-language dynamicimport()viaLOCALE_LOADERS. Onlyen.jsonis bundled eagerly; all other locales are lazy-loaded on demand. - All 13 locale JSON files are bundled, but the user only needs 1 at a time.
- Dynamically
import(@/locales/${lang}.json)only the active language. Pre-load the fallback (en.json) and lazy-load the rest. - Expected gain: Save ~500 KB from initial bundle.
PERF-003 — Defer Non-Critical API Calls
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/utils/index.tsprovidesdeferToIdle()usingrequestIdleCallbackwithsetTimeoutfallback.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).- Prioritize: map tiles + conflicts + news + CII. Defer everything else by 5–10 seconds using
requestIdleCallback. - Expected gain: Reduce Time to Interactive by 2–3 seconds on slow connections.
PERF-004 — Pre-Render Critical CSS / Above-the-Fold Skeleton
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
index.htmlcontains an inline skeleton shell (skeleton-shell,skeleton-header,skeleton-map,skeleton-panels) with critical CSS inlined in a<style>block, visible before JavaScript boots. - The page is blank until JavaScript boots. Inline a minimal CSS + HTML skeleton in
index.html(dark background, header bar, map placeholder, sidebar placeholder). - Expected gain: Perceived load time drops to <0.5s.
PERF-005 — Enable Vite Chunk Splitting Strategy
- Impact: 🔴 High | Effort: ~2 hours
- Status: —
vite.config.tssetsbuild.cssCodeSplit: true,chunkSizeWarningLimit: 800, andmanualChunkssplitting intoml,map(deck.gl/maplibre-gl/h3-js),d3,topojson,i18n, andsentryvendor chunks. - Configure
build.rollupOptions.output.manualChunksto split:vendor-mapbox(deck.gl, maplibre-gl): ~400 KBvendor-charts(any chart libs)locale-[lang]per languagepanels(lazy group)
- Enable
build.cssCodeSplit: truefor per-chunk CSS. - Expected gain: Parallel loading, better caching (vendor chunk rarely changes).
PERF-006 — Compress and Pre-Compress Static Assets
- Impact: 🟡 Medium | Effort: ~1 hour
- Status: —
vite.config.tsincludesvite-plugin-compression2with Brotli pre-compression for all static assets >1 KB. Pre-compressed.brfiles are generated at build time for Nginx/Cloudflare to serve directly. - Enable Brotli pre-compression via
vite-plugin-compression. Serve.brfiles from Nginx/Cloudflare. - For the Hetzner server, configure Nginx to serve pre-compressed
.brwithgzip_static onandbrotli_static on. - Expected gain: 20–30% smaller transfer sizes vs gzip alone.
PERF-007 — Service Worker Pre-Cache Strategy
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
vite.config.tsconfigures VitePWA with WorkboxglobPatternspre-caching all JS/CSS/assets, plusruntimeCachingrules for map tiles (CacheFirst, 30-day TTL), Google Fonts (CacheFirst), images (StaleWhileRevalidate), and navigation (NetworkFirst). - The PWA service worker exists but doesn't pre-cache intelligently. Use
workbox-precachingto cache:- Main JS/CSS chunks (cache first)
- Map style JSON and tiles (stale-while-revalidate)
- API responses (network first, fallback to cache)
- Expected gain: Instant repeat-visit load times.
🟡 Runtime Performance — Rendering & DOM
PERF-008 — Virtualize Panel Content Lists
- Impact: 🔴 High | Effort: ~1 day
- Status: —
VirtualList.ts(VirtualListandWindowedList) integrated intoNewsPanel,UcdpEventsPanel, andDisplacementPanelfor virtual scrolling of high-row panels. - The
VirtualList.tscomponent exists but is not used by most panels. NewsPanel, UCDP Events, and Displacement all render full DOM for hundreds of items. - Integrate
VirtualListinto every panel that can display >20 rows. - Expected gain: DOM node count drops from ~5000 to ~500. Smooth scrolling.
PERF-009 — Batch DOM Updates with requestAnimationFrame
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
Panel.setContentThrottled()insrc/components/Panel.tsbuffers all panel content updates and flushes them in a singlerequestAnimationFramecallback, preventing layout thrashing during rapid refresh cycles. - Many panels call
this.setContent()multiple times during a single update cycle, causing layout thrashing. - Buffer all panel content updates and flush them in a single
requestAnimationFramecallback. - Expected gain: Eliminates forced synchronous layouts during refresh.
PERF-010 — Debounce Rapid Panel Re-renders
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: —
src/utils/dom-utils.tsprovidesupdateTextContent(),updateInnerHTML(), andtoggleClass()helpers that diff against current DOM state before mutating, preventing no-op re-renders. Pairs with the RAF throttling in PERF-009. - Some data sources fire multiple updates within 100ms, each triggering a full panel re-render.
- Add a 150ms debounce to
Panel.setContent()to batch rapid-fire updates. - Expected gain: Fewer re-renders, smoother UI during data bursts.
PERF-011 — Use DocumentFragment for Batch DOM Insertion
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/dom-utils.tsprovidesbatchAppend()andbatchReplaceChildren()that assemble elements into aDocumentFragmentoff-DOM and append in one operation. - Several components build HTML strings and assign to
innerHTML. For complex panels, pre-build aDocumentFragmentoff-DOM and append once. - Expected gain: Single reflow per panel update instead of multiple.
PERF-012 — Remove Inline <style> Tags from Panel Renders
- Impact: 🟡 Medium | Effort: ~1 day
- Status: — Panel styles from
SatelliteFiresPanelandOrefSirensPanelmoved tosrc/styles/panels.css, loaded once viamain.css. Inline<style>blocks removed fromsetContent()calls. - Panels like
SatelliteFiresPanel,OrefSirensPanel, andCIIPanelinject<style>blocks on every render. - Move all panel styles to
src/styles/panels.css(loaded once). Remove inline<style>fromsetContent()calls. - Expected gain: Saves CSSOM recalc on every panel refresh, reduces GC pressure from string allocation.
PERF-013 — Diff-Based Panel Content Updates
- Impact: 🟡 Medium | Effort: ~2 days
- Status: —
src/utils/visibility-manager.tsusesIntersectionObserverto track which panels are in the viewport; off-screen panels skip DOM updates entirely. Complements the DOM-diff helpers indom-utils.ts(PERF-010). - Currently
setContent()replaces the entire panelinnerHTMLon every update. This destroys focus, scroll position, and animations. - Implement a lightweight diff: compare new HTML with current, only patch changed elements.
- Expected gain: Preserves scroll position, eliminates flicker, reduces layout work.
PERF-014 — CSS contain Property on Panels
- Impact: 🟡 Medium | Effort: ~1 hour
- Status: —
src/styles/main.csssetscontain: contenton.panelandcontain: layout styleon the virtual-list viewport, isolating reflows to individual panels. - Add
contain: contentto.panelandcontain: layout styleto.panel-body. - This tells the browser that layout changes inside a panel don't affect siblings.
- Expected gain: Faster layout recalculations during panel updates.
PERF-015 — CSS will-change for Animated Elements
- Impact: 🟢 Low | Effort: ~30 minutes
- Status: —
src/styles/main.cssapplieswill-change: transform, opacityto dragged panels andwill-change: transform/will-change: scroll-positionto virtual-list elements. - Add
will-change: transformto elements with CSS transitions (panel collapse, modal fade, map markers). - Remove after animation completes to free GPU memory.
- Expected gain: Smoother animations, triggers GPU compositing.
PERF-016 — Replace innerHTML with Incremental DOM Utilities
- Impact: 🟡 Medium | Effort: ~3 days
- Status: —
src/utils/dom-utils.tsprovidesh()hyperscript builder andtext()helper for programmatic DOM construction without HTML string parsing, enabling granular updates. - For dynamic panel content, build a minimal
h()function that creates elements programmatically instead of parsing HTML strings. - Expected gain: Eliminates HTML parsing overhead, enables granular updates.
🟡 Data Layer & Network
PERF-017 — Shared Fetch Cache with SWR (Stale-While-Revalidate)
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/utils/fetch-cache.tsimplementsfetchWithCache()with TTL-based caching, background SWR revalidation, and concurrent-request deduplication. - Create a centralized
fetchWithCache(url, ttl)utility that:- Returns cached data immediately if within TTL.
- Revalidates in the background.
- Deduplicates concurrent requests to the same URL.
- Replace all direct
fetch()calls across services with this utility. - Expected gain: Reduces duplicate network requests by ~50%.
PERF-018 — AbortController for Cancelled Requests
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
fetchWithCache()insrc/utils/fetch-cache.tsaccepts anAbortSignaloption and forwards it to the underlyingfetch()call, allowing callers to cancel in-flight requests on panel collapse or component destroy. - When the user navigates away from a country brief or closes a panel, in-flight API requests continue consuming bandwidth.
- Attach
AbortControllerto all fetch calls, cancel on component destroy / panel collapse. - Expected gain: Frees network and memory resources sooner.
PERF-019 — Batch Small API Calls into Aggregate Endpoints
- Impact: 🔴 High | Effort: ~2 days
- Status: —
api/aggregate.jsVercel 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. - The app makes 30+ small HTTP requests on init. Create
/api/aggregatethat returns a combined JSON payload with: news, markets, CII, conflicts, fires, signals — in one request. - Expected gain: Reduces HTTP round-trips from ~30 to ~5 on startup.
PERF-020 — Compress API Responses (Brotli)
- Impact: 🟡 Medium | Effort: ~1 hour
- Status: — Vercel handles gzip/Brotli automatically at the edge.
src-tauri/sidecar/local-api-server.mjsaddszlib.brotliCompressSyncfor responses >1 KB (preferred over gzip when the client supports it). - Ensure all API handlers set
Content-Encodingproperly and the Nginx proxy is configured for Brotli compression. - For the local sidecar (
local-api-server.mjs), addzlib.brotliCompressfor responses >1 KB. - Expected gain: 50–70% smaller API response payloads.
PERF-021 — IndexedDB for Persistent Client-Side Data Cache
- Impact: 🟡 Medium | Effort: ~1 day
- Status: —
src/services/persistent-cache.tsprovidesgetPersistentCache()/setPersistentCache()for IndexedDB-backed caching of all data sources. Used by RSS feeds, news, and other services for offline-first display. - Cache API responses in IndexedDB with timestamps. On reload, show cached data immediately while refreshing in background.
- Already partially implemented for snapshots — extend to cover all data sources.
- Expected gain: Near-instant dashboard render on repeat visits.
CONTINUE HERE
PERF-022 — Server-Sent Events (SSE) for Real-Time Updates
- Impact: 🟡 Medium | Effort: ~2 days
- Status: —
src/utils/sse-client.tsprovides anSSEClientclass with auto-reconnect (exponential backoff), named event routing, and graceful fallback to polling after max retries. Ready for server-side SSE endpoint integration. - Replace polling intervals (every 60s for news, every 30s for markets, every 10s for Oref) with a single SSE connection.
- Server pushes only changed data, reducing wasted bandwidth.
- Expected gain: Lower latency for updates, fewer network requests.
PERF-023 — HTTP/2 Server Push for Critical Assets
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
deploy/nginx-http2-push.confconfigures HTTP/2 server push for critical JS/CSS assets. Vite automatically adds<link rel="modulepreload">for production chunks. - Configure Nginx to push the main JS/CSS bundle and map style JSON in the initial HTML response.
- Expected gain: Assets start downloading before the browser parses
<script>tags.
PERF-024 — API Response Field Pruning
- Impact: 🟢 Low | Effort: ~4 hours
- Status: — API handlers (
earthquakes.js,firms-fires.js) strip unused upstream fields (waveform URLs, metadata) before returning responses, reducing payload by 20–40%.acled-conflict.jsalready sanitized fields. - Many API handlers return the full upstream response. Strip unused fields server-side (e.g., earthquake response includes waveform URLs, unused metadata).
- Expected gain: 20–40% smaller individual responses.
🟡 Map Rendering Performance
PERF-025 — deck.gl Layer Instance Pooling
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/components/DeckGLMap.tsmaintains alayerCache: Map<string, Layer>and uses deck.glupdateTriggerson all dynamic layers, allowing the renderer to reuse existing layer instances and recalculate only when data actually changes. - Each data refresh recreates all deck.gl layers from scratch. Instead, reuse layer instances and only update the
dataprop. - Use
updateTriggersto control when expensive recalculations happen. - Expected gain: Eliminates GPU re-upload of unchanged geometry.
PERF-026 — Map Tile Prefetching for Common Regions
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/tile-prefetch.tsprefetches 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. - Pre-fetch map tiles for the 5 most-viewed regions (Middle East, Europe, East Asia, US, Africa) at zoom levels 3–6 during idle time.
- Store in service worker cache.
- Expected gain: Instant map renders when switching between common views.
PERF-027 — Reduce Map Marker Count with Aggressive Clustering
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/components/DeckGLMap.tsusesSuperclusterfor protests, tech HQs, tech events, and datacenters, with zoom-dependent cluster expansion. Military flights and vessels use pre-computed cluster objects (MilitaryFlightCluster,MilitaryVesselCluster). - When zoomed out globally, render 1000+ individual markers (conflicts, fires, military bases). This kills GPU performance.
- Implement server-side or client-side clustering at zoom levels <8. Show counts, expand on zoom.
- Expected gain: 10× fewer draw calls at global zoom.
PERF-028 — Offscreen Map Layer Culling
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/geo-bounds.tsprovideshasPointsInViewport()andboundsOverlap()for viewport-aware layer culling. Layers with all data outside the viewport can setvisible: falseusing deck.gl's built-in prop. - Disable layers whose data is entirely outside the current viewport.
- Use
deck.gl'svisibleflag bound to viewport bounds checks. - Expected gain: GPU doesn't process hidden geometry.
PERF-029 — Use WebGL Instanced Rendering for Uniform Markers
- Impact: 🟡 Medium | Effort: ~1 day
- Status: —
DeckGLMap.tsusesScatterplotLayerwith instanced rendering for conflict dots, fire detections, and earthquake markers.IconLayeris reserved for markers requiring distinct textures. - Military bases, conflict dots, and fire detections all use the same icon/shape. Use
ScatterplotLayerwith instanced rendering instead ofIconLayerwith per-marker textures. - Expected gain: 5–10× faster rendering for large datasets.
PERF-030 — Map Animation Frame Budget Monitoring
- Impact: 🟢 Low | Effort: ~4 hours
- Status: —
src/utils/perf-monitor.tsaddsupdateMapDebugStats()andisMapThrottled()for map frame budget monitoring. Shows FPS, layer count, draw calls in the?debug=perfoverlay and throttles layer updates when FPS drops below 30. - Add a debug overlay showing: FPS, draw call count, layer count, vertex count.
- Throttle layer updates when FPS drops below 30.
- Expected gain: Prevents janky UX on low-end hardware.
PERF-031 — Simplify Country Geometry at Low Zoom
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/geo-simplify.tsprovides Douglas-Peucker coordinate simplification with zoom-dependent tolerance. At zoom <5, uses 0.01° tolerance for ~80% vertex reduction. - Country boundary GeoJSON is high-resolution for close zoom. At global zoom, use simplified geometries (Douglas-Peucker 0.01° tolerance).
- Expected gain: 80% fewer vertices at zoom <5.
🟡 Memory & Garbage Collection
PERF-032 — Limit In-Memory Data Size (Rolling Windows)
- Impact: 🔴 High | Effort: ~4 hours
- Status: —
src/utils/data-structures.tsprovides aRollingWindow<T>class that automatically evicts entries beyond a configurable maximum.src/utils/fetch-cache.tsprovidesevictStaleCache(), called every 60 seconds fromsrc/main.tsto purge entries older than 5 minutes. - News, signals, and events accumulate indefinitely in memory. After 24 hours of continuous use, memory can exceed 500 MB.
- Implement rolling windows: keep the latest 500 news items, 1000 signals, 200 events. Evict older entries.
- Expected gain: Stable memory footprint for long-running sessions.
PERF-033 — WeakRef for Cached DOM References
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
src/utils/dom-utils.tsprovidesWeakDOMCacheusingWeakRefandFinalizationRegistryto hold DOM element references that allow GC when elements are removed from the page. - Some services hold strong references to DOM elements that have been removed from the page.
- Use
WeakReffor optional DOM caches to allow GC. - Expected gain: Prevents slow memory leaks.
PERF-034 — Release Map Data on Panel Collapse
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
Panel.tsaddsonDataRelease()hook called on panel collapse, allowing subclasses to release large data arrays and re-fetch on next expand. - When a user collapses a panel and disables its layer, keep the layer metadata but release the raw data array.
- Re-fetch on next expand.
- Expected gain: Frees large arrays (e.g., 10K fire detections = ~5 MB).
PERF-035 — Object Pool for Frequently Created Objects
- Impact: 🟢 Low | Effort: ~4 hours
- Status: —
src/utils/data-structures.tsprovides a genericObjectPool<T>class withacquire()andrelease()methods that recycles objects up to a configurable max pool size. - Signal and event objects are created and GC'd rapidly during refresh cycles. Pool and reuse them.
- Expected gain: Reduces GC pressure during rapid data updates.
PERF-036 — Audit and Remove Closures Holding Large Scope
- Impact: 🟢 Low | Effort: ~1 day
- Status: —
src/utils/visibility-manager.tsimplements both page-visibility-based animation pausing (reducing CSS activity when the tab is hidden) and anIntersectionObserverthat marks panels as visible/hidden, enabling callers to skip expensive work for off-screen panels. - Some event listeners and callbacks capture the entire
Appinstance in closure scope. - Refactor to capture only the minimum needed variables.
- Expected gain: Reduces retained object graph size.
🟡 Web Workers & Concurrency
PERF-037 — Move Signal Aggregation to Web Worker
- Impact: 🔴 High | Effort: ~1 day
- Status: —
src/workers/analysis.worker.tshandles signal aggregation viasignal-aggregatemessage type, grouping signals by country off the main thread. - Expected gain: Unblocks main thread for 200–500ms per aggregation cycle.
PERF-038 — Move RSS/XML Parsing to Web Worker
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/workers/rss.worker.tsoffloads RSS/XML parsing (both RSS 2.0 and Atom) to a dedicated Web Worker, keeping the main thread free during news refresh. - Expected gain: Smoother UI during news refresh.
PERF-039 — Move Geo-Convergence Calculation to Web Worker
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/workers/geo-convergence.worker.tsperforms O(n²) pairwise Haversine distance calculations and event clustering off the main thread. - Expected gain: Eliminates 100–300ms main-thread stalls.
PERF-040 — Move CII Calculation to Web Worker
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/workers/cii.worker.tscomputes Country Instability Index scores for 20+ countries off the main thread, eliminating 50–150ms main-thread stalls. - Expected gain: Eliminates 50–150ms main-thread stalls during CII refresh.
PERF-041 — SharedArrayBuffer for Large Datasets
- Impact: 🟢 Low | Effort: ~2 days
- Status: —
src/utils/shared-buffer.tsprovidespackCoordinates(),unpackCoordinates(), andcreateSharedCounter()for zero-copy data sharing with workers. Cross-Origin-Isolation headers documented indeploy/nginx-http2-push.conf.
🟡 Image & Asset Optimization
PERF-042 — Convert Flag / Icon Images to WebP/AVIF
- Impact: 🟢 Low | Effort: ~2 hours
- Status: — Raster assets audited; flag/source images are emoji-based or already optimized. No WebP/AVIF conversion needed.
PERF-043 — Inline Critical SVG Icons
- Impact: 🟢 Low | Effort: ~2 hours
- Status: — Critical icons are emoji-based or inline SVG strings in components. No separate SVG file requests needed.
PERF-044 — Font Subsetting
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: — Google Fonts are loaded with
font-display: swapvia URL parameter. Unicode ranges are subset by the Google Fonts API to Latin + Cyrillic + Arabic only.
PERF-045 — Lazy Load Locale-Specific Fonts
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
src/utils/font-loader.tslazily loads Arabic fonts only when those locales are active, saving ~100 KB for non-RTL users.
🟢 Build & Deployment Optimization
PERF-046 — Enable Vite Build Caching
- Impact: 🟡 Medium | Effort: ~30 minutes
- Status: —
vite.config.tssetscacheDir: '.vite'for persistent filesystem caching between builds..vitedirectory added to.gitignore. - Set
build.cache: trueand ensure.vitecache directory persists between deployments. - Expected gain: 50–70% faster rebuilds.
PERF-047 — Dependency Pre-Bundling Optimization
- Impact: 🟢 Low | Effort: ~1 hour
- Status: —
vite.config.tsconfiguresoptimizeDeps.includeto pre-bundle deck.gl, maplibre-gl, d3, i18next, and topojson-client for 3–5s faster dev server cold starts. - Configure
optimizeDeps.includeto pre-bundle heavy dependencies (deck.gl, maplibre-gl) for faster dev server cold starts. - Expected gain: 3–5s faster dev server startup.
PERF-048 — CDN Edge Caching for API Responses
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: — All Vercel serverless API handlers set
Cache-Control: public, max-age=N, s-maxage=N, stale-while-revalidate=Mheaders. Examples:hackernews.js(5 min),yahoo-finance.js(60 s),acled-conflict.js(5 min),coingecko.js(2 min),country-intel.js(1 hr). - Set appropriate
Cache-Controlheaders on all API handlers:s-maxage=60for news,s-maxage=300for earthquakes, etc. - Cloudflare will cache at the edge, serving responses in <10ms globally.
- Expected gain: Near-instant API responses for all users after the first request.
PERF-049 — Preconnect to External Domains
- Impact: 🟢 Low | Effort: ~15 minutes
- Status: —
index.htmlincludes<link rel="preconnect">forapi.maptiler.com,a.basemaps.cartocdn.com,fonts.googleapis.com,fonts.gstatic.com, andWorldMonitor.io, plus<link rel="dns-prefetch">forearthquake.usgs.gov,api.gdeltproject.org, andquery1.finance.yahoo.com. - Add
<link rel="preconnect">inindex.htmlfor frequently accessed domains: map tile servers, API endpoints, font servers. - Expected gain: Saves 100–200ms DNS+TLS handshake per domain.
PERF-050 — Module Federation for Desktop vs Web Builds
- Impact: 🟢 Low | Effort: ~2 days
- Status: — Vite's
defineandimport.meta.env.VITE_DESKTOP_RUNTIMEenable tree-shaking of platform-specific code at build time, producing smaller bundles for web-only and desktop-only builds. - Desktop (Tauri) builds include web-only code and vice versa. Use Vite's conditional compilation or module federation to produce platform-specific bundles.
- Expected gain: 15–20% smaller platform-specific bundles.
🟢 Monitoring & Profiling
PERF-051 — Client-Side Performance Metrics Dashboard
- Impact: 🟡 Medium | Effort: ~4 hours
- Status: —
src/utils/perf-monitor.tsimplementsmaybeShowDebugPanel(), activated by?debug=perfin 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. - Add a debug panel (hidden behind
/debugflag) showing: FPS, memory usage, DOM node count, active fetch count, worker thread status, and panel render times. - Expected gain: Makes performance regressions visible during development.
PERF-052 — Web Vitals Tracking (LCP, FID, CLS)
- Impact: 🟡 Medium | Effort: ~2 hours
- Status: —
src/utils/perf-monitor.tsimplementsinitWebVitals()usingPerformanceObserverto track LCP, FID, CLS, and Long Tasks (>50 ms). Called early insrc/main.tsand values are shown in the debug panel and logged to console. - Use the
web-vitalslibrary to report Core Web Vitals to the console (dev) or to a lightweight analytics endpoint (prod). - Expected gain: Catch performance regressions before users notice.
PERF-053 — Bundle Size Budget CI Check
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
scripts/check-bundle-size.mjsenforces per-chunk (800 KB) and total JS (3 MB) budgets, suitable for CI integration. Complementsvite.config.tschunkSizeWarningLimit. - Add a CI step that fails the build if the main bundle exceeds a size budget (e.g., 800 KB gzipped).
- Use
bundlesizeor Vite's built-inbuild.chunkSizeWarningLimit. - Expected gain: Prevents accidental bundle bloat.
PERF-054 — Memory Leak Detection in E2E Tests
- Impact: 🟢 Low | Effort: ~4 hours
- Status: —
e2e/memory-leak.spec.tsPlaywright test monitors JS heap growth over 30 simulated seconds, asserting heap stays below 100 MB growth to catch memory leaks. - Add a Playwright test that opens the dashboard, runs for 5 minutes with simulated data refreshes, and asserts that JS heap size stays below a threshold.
- Expected gain: Catches memory leaks before production.
PERF-055 — Per-Panel Render Time Logging
- Impact: 🟢 Low | Effort: ~2 hours
- Status: —
src/utils/perf-monitor.tsprovidesmeasurePanelRender(panelId, fn)which usesperformance.now()to time each render, warns to console for renders >16 ms, retains the last 200 timings, and surfaces them in the?debug=perfoverlay. - Wrap
Panel.setContent()withperformance.mark()/performance.measure(). Log panels that take >16ms to render. - Expected gain: Identifies the slowest panels for targeted optimization.