Skip to content

fix: stronger cache hash (FNV-1a 64-bit) and GeoIP abort on timeout#210

Open
Rau1CS wants to merge 1096 commits intokoala73:mainfrom
Rau1CS:fix/stronger-hash-and-geoip-abort
Open

fix: stronger cache hash (FNV-1a 64-bit) and GeoIP abort on timeout#210
Rau1CS wants to merge 1096 commits intokoala73:mainfrom
Rau1CS:fix/stronger-hash-and-geoip-abort

Conversation

@Rau1CS
Copy link
Contributor

@Rau1CS Rau1CS commented Feb 20, 2026

Summary

  • Replace 32-bit hash with stronger hash for cache keys #180: Replaced the 32-bit DJB2 hashString() in _upstash-cache.js with FNV-1a 64-bit using BigInt. The birthday-bound collision threshold goes from ~65K to ~4B entries — a 65,000x improvement while remaining synchronous and edge-runtime compatible. All 5 caller files are unchanged.
  • Cancel background GeoIP workers on timeout #196: The GeoIP worker pool in cyber-threats.js used Promise.race to enforce an overall timeout, but the losing workers (and their in-flight fetch calls) kept running. Now an AbortController signal is threaded through hydrateThreatCoordinates → geolocateIp → fetchGeoIp → fetchJsonWithTimeout, so all pending requests are cancelled the moment the overall timeout fires.

Changes

File What changed
api/_upstash-cache.js hashString(): DJB2 32-bit → FNV-1a 64-bit (BigInt)
api/cyber-threats.js hydrateThreatCoordinates: create AbortController, pass signal through worker chain; fetchJsonWithTimeout: forward external abort signal; fetchGeoIp / geolocateIp: accept + pass signal parameter

Checklist

  • npx tsc --noEmit passes
  • No new dependencies
  • No secrets or API keys
  • Edge-runtime compatible (no node:crypto, uses BigInt + standard AbortController)
  • Backwards-compatible (hashString signature unchanged; new signal params are optional)

Closes #180, closes #196

🤖 Generated with Claude Code

SebastienMelki and others added 30 commits February 16, 2026 17:17
…getCSSColor()

- Replace 8+ hardcoded rgba/hex colors with getCSSColor() CSS variable reads
- Convert tooltip, grid, axes, now-marker, empty labels, event circles
- Add theme-changed event listener for automatic re-render on theme toggle
- Keep semantic LANE_COLORS (protest/conflict/natural/military) unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update --map-country to warm cream (#f0e8d8) for Voyager-style land
- Update --map-stroke to warm border (#c8b8a8) complementing cream land
- Subtler blue grid (#b0c8d8) for light mode
- Add theme-changed event listener in Map.ts resetting baseRendered flag
- Dark theme map values unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace static COLORS constant with getOverlayColors() function
- Refresh COLORS at top of buildLayers() on each render cycle
- Conflict zone fills more transparent in light mode (alpha 60 vs 100)
- Conflict zone line color reduced alpha in light mode (120 vs 180)
- Displacement arc colors deeper/more saturated on light backgrounds
- Threat dots remain identical in both modes (user locked decision)
- Infrastructure markers unchanged (semantic category colors)
- Create 03-01-SUMMARY.md with execution results
- Update STATE.md with Phase 3 completion, metrics, and decisions
- Add wildcard 200ms ease transition on background-color, color, border-color, box-shadow
- Add .no-transition suppression rule with !important for FOUC prevention
- Add canvas/maplibregl/deck-canvas transition exclusion to prevent rendering artifacts
- Fix --text-muted in light theme from #8a8a8a to #767676 (WCAG AA 4.54:1 vs #f8f9fa)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- index.html and settings.html FOUC scripts add no-transition class to <html>
- src/main.ts removes no-transition after first paint via requestAnimationFrame
- src/settings-main.ts removes no-transition after first paint via requestAnimationFrame
- Prevents transition sweep from dark-to-light on page load while enabling smooth theme toggles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These are local development/planning files that should be gitignored.
Already covered by .gitignore entries for .planning/ and .idea/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add light-mode overrides for 12 semantic CSS variables that had
terrible contrast on light backgrounds (as low as 1.25:1 for #44ff88).
Uses Tailwind's light-friendly palette (green-600/700, amber-600,
orange-600/700, yellow-600, sky-600).

Also darken DeckGL overlay marker colors (startup hub, accelerator,
nuclear, datacenter) and their legend SVGs in light mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
overlay-heavy is rgba(255,255,255,0.2) in dark mode (lightening tint),
but 11 places originally used rgba(0,0,0,x) (darkening tint). This
caused visible regressions in dark mode (channel bar, CII cards, etc).

Adds --darken-light/medium/heavy CSS variables that stay black-tinted
in both themes, and applies them to the affected selectors.
…a73#84)

## Summary

Adds a complete dual-theme system to WorldMonitor. Users can toggle
between dark and light mode in Settings, with the preference persisted
across sessions. Every panel, the map, charts, and all chrome are fully
themed.

**41 files changed, +1,591 / -1,128 lines**

## What's included

### CSS Foundation (Phase 1)
- 124+ hardcoded colors centralized into CSS custom properties
- Theme colors separated from semantic colors (threat reds, DEFCON,
status badges stay identical)
- `getCSSColor()` runtime utility with theme-aware cache for dynamic TS
components

### Theme Core (Phase 2)
- `ThemeManager` module with `get/set/apply` theme functions and
localStorage persistence
- FOUC prevention inline scripts in both HTML entry points
- Dark/Light radio toggle in Settings → Appearance section

### Map & Visualization Theming (Phase 3)
- Map auto-swaps between dark CARTO and light Voyager tiles (no flash)
- Deck.GL overlay layers adjust opacity/saturation per theme for
readability
- D3 CountryTimeline chart colors converted to theme-aware
`getCSSColor()`

### Polish & Accessibility (Phase 4)
- Smooth 200ms CSS transitions for all theme switching
- WCAG AA contrast compliance (`--text-muted` #767676, 4.54:1 ratio)
- Both build variants (full geopolitical + tech/startup) verified
working

## Demo

### Toggle dark ↔ light


https://github.com/user-attachments/assets/ce8d3bbe-fb5e-49cb-9bbd-5da5d900a15a

### Dark mode (unchanged)

<img width="5120" height="2648" alt="image"
src="https://github.com/user-attachments/assets/6a055ae6-e9a9-4d85-b328-a035b7bcd165"
/>

### Light mode

<img width="5120" height="2650" alt="image"
src="https://github.com/user-attachments/assets/3531c952-a801-43a5-8ef3-68c160fb85b8"
/>


## Review focus areas

1. **`src/styles/main.css`** — Bulk of the CSS variable conversion (~12K
lines). Check that `var()` references and `[data-theme="light"]`
overrides look correct.
2. **`src/utils/theme-manager.ts`** — New module. Simple but critical:
localStorage read/write, event dispatch, FOUC prevention.
3. **`src/utils/theme-colors.ts`** — `getCSSColor()` cache utility.
Verify cache invalidation on theme change is correct.
4. **`src/components/DeckGLMap.ts`** — Basemap tile swap logic and
`getOverlayColors()` per-theme adjustments.
5. **`index.html` / `settings.html`** — Inline FOUC prevention scripts.
CSP updated with `unsafe-inline` for script-src.
6. **`src/App.ts`** — Theme toggle UI wiring in settings modal. Check
event listener patterns.

## What NOT to worry about

- ~99 remaining hardcoded colors are documented acceptable exclusions
(canvas drawing, console.log, deprecated constants with migration paths,
category identifier colors)
- Dark mode is visually identical to before — no regressions

## Test plan

- [x] Run `npm run build` — builds without errors for both variants
- [x] Open app — dark mode loads by default, no FOUC
- [x] Go to Settings → Appearance → select Light — all panels, header,
sidebar switch smoothly
- [x] Verify map switches from dark CARTO to light Voyager tiles (no
blank flash)
- [x] Refresh page — light mode persists, no FOUC
- [x] Switch back to dark — everything returns to original look
- [x] Check semantic colors (threat dots, DEFCON badges, LIVE
indicators) identical in both themes
- [x] Open DevTools → inspect text contrast in light mode (should be
≥4.5:1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Remove the duplicate time display from the header bar and add a
theme toggle button (sun/moon icon) that hooks into the existing
theme-manager. Bidirectional sync with settings modal radios.
Now that the header has a sun/moon dark/light toggle, the Appearance
section in the settings modal is redundant. Revert modal title from
"Settings" back to "Panels" and remove theme radio buttons and CSS.
Full light mode theme, header dark/light toggle, desktop update checker,
bundled Node.js in installer, CORS fixes, and panel defaults update.
Add get_all_secrets command that reads all 18 keys in a single IPC call.
This triggers one Keychain prompt instead of 18 on fresh installs.
Differentiate missing API key, upstream down, and empty data states.
Name the actual data source in error messages instead of generic text.
- Add missing RSS proxy domains (seekingalpha, coindesk, cointelegraph)
- Fix operator precedence in finance marker zoom checks (explicit parens)
- Add Number.isFinite() NaN guards on all finance marker projections
- Include finance variant in aggregated test:e2e script
koala73 and others added 27 commits February 20, 2026 08:46
## Summary

Add CSS `contain: content` property to panel elements to enable browser
optimizations and improve rendering performance by establishing a new
stacking context and limiting layout recalculations.

## Type of change

- [x] Refactor / code cleanup

## Affected areas

- [x] Other: CSS performance optimization

## Details

This change adds the `contain: content` CSS property to panel elements
(`.panel` class). This CSS containment property:

- Enables the browser to optimize rendering by limiting the scope of
layout, style, and paint calculations
- Establishes a new stacking context, which can improve performance when
panels are frequently repositioned or resized
- Has no visual impact on the UI

This is a low-risk performance optimization that leverages modern CSS
capabilities to improve responsiveness, especially when dealing with
multiple panels on the dashboard.

## Checklist

- [x] No API keys or secrets committed
- [x] TypeScript compiles without errors

## Testing

No testing needed - this is a CSS-only performance optimization with no
behavioral changes.

https://claude.ai/code/session_01E9FpgiebjEuUPhNt8mwX9U
### Motivation
- Panels can receive multiple content updates in quick succession,
causing many DOM writes and janky re-renders during data bursts.
- Pairing a debounce with the existing DOM diffing/RAF throttling
reduces unnecessary work and smooths the UI.

### Description
- Added a 150ms debounce to `Panel.setContent()` and new private fields
(`contentDebounceMs`, `pendingContentHtml`, `contentDebounceTimer`) to
buffer rapid updates and apply only the latest payload.
- `setContent()` now skips no-op updates when the pending or current
HTML already matches and resets the timer on rapid calls.
- Introduced `setContentImmediate()` and switched `showLoading()`,
`showError()`, and `showConfigError()` to use it so immediate UI states
are not overwritten by stale debounced writes.
- Extended `destroy()` to clear any pending debounce timers and pending
content to avoid delayed updates after teardown.

### Testing
- Ran `npm run typecheck` (i.e. `tsc --noEmit`) and it completed
successfully.

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_699782a113a88333ab664a828cb00b02)
## Summary

Optimize GPU memory usage by strategically applying and removing the
`will-change` CSS property on animated elements. This prevents
unnecessary GPU memory allocation for elements that don't require
continuous optimization, while maintaining smooth animations through the
transition/animation lifecycle.

## Type of change

- [x] Refactor / code cleanup
- [x] Performance optimization

## Affected areas

- [x] Map / Globe
- [x] Other: UI animations and modals

## Changes

### CSS Updates (`src/styles/main.css`)
- Added `will-change: transform, opacity;` to `.panel-header` (hover
state)
- Added `will-change: transform, opacity;` to `.signal-modal` (entrance
animation)
- Added `will-change: transform;` to `.map-popup` (slide-in transition)
- Added `will-change: transform, opacity;` to `.mobile-warning-modal`
(entrance animation)
- Added `will-change: transform;` to `.tech-event-marker` (transform
animations)

### JavaScript Updates
- **MapPopup.ts**: Added listener to remove `will-change` after slide-in
transition completes
- **MobileWarningModal.ts**: Added listener to remove `will-change`
after entrance animation completes
- **SignalModal.ts**: Added listener to remove `will-change` after
entrance animation completes

## Rationale

The `will-change` property hints to the browser to prepare for
animations, but maintaining it indefinitely wastes GPU memory. By
removing it after animations complete (via `transitionend` or
`animationend` events), we free up resources while preserving animation
performance.

## Checklist

- [x] TypeScript compiles without errors
- [x] No API keys or secrets committed

https://claude.ai/code/session_01DDhT6Ex596eX1CtSb6mHdH
## Summary

Adds a pre-rendered skeleton loading screen that displays instantly
before JavaScript boots, eliminating flash of unstyled content (FOUC).
The skeleton mimics the final layout with animated shimmer effects and
automatically replaces when the app initializes.

## Type of change

- [x] New feature
- [ ] Bug fix
- [ ] Refactor / code cleanup
- [ ] Other

## Affected areas

- [x] Other: Loading UX / Initial page render

## Details

This change improves the perceived performance and user experience by:

1. **Inlining critical skeleton CSS** in the `<head>` to ensure it
renders before any external stylesheets load
2. **Pre-rendering skeleton HTML** in the `#app` div that displays
instantly on page load
3. **Supporting both themes** with dark mode as default and light mode
variants via `[data-theme="light"]` selectors
4. **Adding shimmer animation** to skeleton lines for visual feedback
that content is loading
5. **Marking skeleton as hidden from accessibility** with
`aria-hidden="true"` so screen readers skip it

The skeleton layout includes:
- Header with logo, filters, and controls
- Map section with toolbar
- Grid of 6 data panels with headers and content lines

When the Vue app initializes and calls `renderLayout()`, the skeleton is
automatically replaced with the actual content.

## Checklist

- [x] No API keys or secrets committed
- [x] Tested theme switching (dark/light mode skeleton variants)
- [x] Skeleton is properly hidden from screen readers

https://claude.ai/code/session_01Fxk8GMRn2cEUq3ThC2a8e5
Closes koala73#169 — the `ml-*` pattern missed chunks without a hyphen,
causing ~60 MB of ML code to be precached by the service worker.
Closes koala73#139 — pass DependencyGraph as parameter (no redundant rebuild),
check direct edges first, then walk BFS dependency chain for indirect
impacts (chokepoint → port → country). Returns 0 for truly unaffected.
…se caching (koala73#171)

### Motivation
- Improve repeat-visit performance by persisting API responses and
data-source payloads client-side so the dashboard can show cached data
immediately while refreshing in the background (PERF-021).
- Provide a robust multi-tier storage strategy: prefer desktop/Tauri
storage, then IndexedDB in browser runtime, and fall back to
`localStorage` if needed.

### Description
- Upgrade `src/services/persistent-cache.ts` to use an IndexedDB store
(`worldmonitor_persistent_cache`) with
`getFromIndexedDb`/`setInIndexedDb` while keeping Tauri RPC and
`localStorage` fallbacks.
- Add stale-while-revalidate API response caching to
`src/utils/proxy.ts` via `fetchWithProxy`, which returns cached `/api/*`
responses immediately and refreshes them in the background (persisted
under `api-response:<url>`).
- Route key data-source fetchers through the new proxy cache by
switching `fetch` -> `fetchWithProxy` in `src/services/arxiv.ts`,
`src/services/hackernews.ts`, `src/services/github-trending.ts`, and
`src/services/earthquakes.ts` so they inherit persistent response
hydration.
- Preserve existing behaviors and fallbacks (desktop storage first,
IndexedDB where available, then `localStorage`) and avoid breaking
existing circuit-breaker logic in services.

### Testing
- Ran `npm run typecheck` (`tsc --noEmit`) and it succeeded.
- Ran `npm run test:data` and the suite executed; most tests passed but
one existing `deploy/cache` guardrail subtest failed (pre-existing
assertion about a precache glob pattern), which is unrelated to the
caching changes.

------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_6997871eb7648333a131f041010684de)
Replace naive string concatenation for Vary header with appendVary()
that parses existing tokens and deduplicates case-insensitively.
Prevents duplicate Vary tokens when both Origin and Accept-Encoding
are added.

Closes koala73#170

Co-authored-by: Lawyered <4802498+lawyered0@users.noreply.github.com>
Add manual chunk rules for i18next → i18n, @sentry/* → sentry, and
*Panel.ts → panels. Improves caching (stable vendor chunks) and
reduces main bundle size via parallel loading.

Closes koala73#148
…s (PERF-012) (koala73#154)

Inline <style> blocks were re-injected on every render in 6 components,
causing CSSOM recalc and GC pressure from repeated string allocation.
Extracted all panel styles into src/styles/panels.css, imported once via
main.css. Affected components: SatelliteFiresPanel, PopulationExposurePanel,
ClimateAnomalyPanel, DisplacementPanel, UcdpEventsPanel, DownloadBanner.

https://claude.ai/code/session_01Erkji3Bg2NwawE25NFC6vh

Co-authored-by: Claude <noreply@anthropic.com>
…ll id

- Add /webkitEnterFullscreen/ to ignoreErrors for Safari internal fullscreen crash
- Add "id" to beforeSend property alternation for deck.gl _drawLayers null access
- Add AbortController to Panel base class, abort on destroy()
- Wire signal into 7 panel fetch calls with AbortError guard
- Add signal param to fetchCachedRiskScores/fetchCachedTheaterPosture
- Add abort lifecycle to CountryBriefPage (show/hide)
- Fix StrategicRiskPanel missing super.destroy() call

Co-authored-by: koala73 <koala73@users.noreply.github.com>
Add dom-utils.ts with h(), replaceChildren(), fragment(), rawHtml()
helpers. Convert 8 components from innerHTML string concatenation to
type-safe DOM construction, eliminating XSS surface and HTML parsing.

Components: Panel, CIIPanel, GdeltIntelPanel, MonitorPanel,
PizzIntIndicator, ServiceStatusPanel, StatusPanel, TechEventsPanel.
Event listeners now inline via onClick props (removes post-render
querySelector+addEventListener pattern). Fix ServiceStatusPanel
missing super.destroy() call.
Add broader filter: any TypeError where all in-app frames are in the
maplibre map chunk is suppressed. Catches hash-parsing crashes like
'o.includes' that are entirely inside maplibre-gl internals.
Filter window.android.* TypeError from Huawei/Android WebView
shell injections — not our code.
- InvestmentsPanel: replace per-element listeners (destroyed by debounced
  innerHTML) with event delegation on stable this.content
- IntelligenceGapBadge: null guard on context.actionableInsight for
  environments where i18n is not initialized
- keyword-spike test: restructure headlines so spike term appears
  mid-sentence (isLikelyProperNoun skips index-0), add initI18n
- investments-panel test: add pollUntil helper, re-query DOM elements
  after debounced render cycles
- map-harness tests: increase WebGL harness init and poll timeouts,
  relax golden screenshot pixel diff threshold
- cached-risk-scores/cached-theater-posture: shared fetch must not be
  canceled by one caller's AbortSignal; use withCallerAbort wrapper so
  individual callers can abort without killing the shared promise
- StrategicPosturePanel: remove isMilitaryVesselTrackingConfigured guard
  that blocked USNI fleet data when AIS key was absent
- Panel: use rawHtml for info tooltip to render HTML content correctly
…uilds

- vercel.json: add ignoreCommand to skip builds when only docs/tests/CI change
- service-status: extend edge cache 60s→300s (46 service checks, slow-moving)
- cyber-threats: extend edge cache 5min→1hr (threat intel updates hourly)
- theater-posture: extend edge cache 60s→300s, stale fallback 30s→120s
- markets/crypto polling: 2min→4min (reduce edge requests by ~50%)
…config constant

DRY extraction of identical map defined in both syncDataFreshnessWithLayers()
and setupMapLayerHandlers(). Single source of truth in config/panels.ts.
Supersedes PR koala73#145 which was based on stale main (missing gdelt_doc).
Block crawlers/scrapers from /api/* routes via Edge Middleware (403 for
bot user-agents and missing/short UAs). Social preview bots (Twitter,
Facebook, LinkedIn, Slack, Discord) are allowed on /api/story and
/api/og-story for OG previews. robots.txt reinforces the same policy.
- fred-data: batch mode (comma-separated series_id) reduces 7 edge
  function invocations to 1; cap at 15 series; propagate upstream
  502s instead of masking as empty 200; add X-Data-Status header
- ucdp-events: parallelize page fetches; track failed pages and use
  short cache TTL for partial results instead of caching at full 6h
- ucdp: add OPTIONS/method guard matching ucdp-events pattern
- middleware: exact-match social bot paths instead of startsWith
- vercel.json: use VERCEL_GIT_PREVIOUS_SHA for multi-commit diffs;
  add middleware.ts, settings.html, vercel.json to watch list
- Panel.ts: use safeHtml() allowlist sanitizer for tooltip content
- dom-utils: add safeHtml() with tag/attribute allowlist and
  javascript: URI blocking
…rs on timeout

hashString() used a 32-bit DJB2 hash prone to collisions at scale; replaced
with FNV-1a 64-bit (BigInt) for ~2^32 birthday-bound collision resistance
while staying synchronous and edge-runtime compatible.

GeoIP worker pool in cyber-threats used Promise.race without cancelling
in-flight fetches on timeout. Now threads AbortController signal through
workers → geolocateIp → fetchGeoIp → fetchJsonWithTimeout so all pending
requests are aborted when the overall timeout fires.

Closes koala73#180, closes koala73#196

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

vercel bot commented Feb 20, 2026

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

A member of the Team first needs to authorize it.

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.

Cancel background GeoIP workers on timeout Replace 32-bit hash with stronger hash for cache keys

10 participants