v0.9.0: in-app auto-updater, ship toggle split, stable entity IDs, performance fixes

New features:
- In-app auto-updater with confirmation dialog, manual download fallback,
  restart polling, and protected file safety net
- Ship layers split into 4 independent toggles (Military/Carriers, Cargo/Tankers,
  Civilian, Cruise/Passenger) with per-category counts
- Stable entity IDs using MMSI/callsign instead of volatile array indices
- Dismissible threat alert bubbles (session-scoped, survives data refresh)

Performance:
- GDELT title fetching is now non-blocking (background enrichment)
- Removed duplicate startup fetch jobs
- Docker healthcheck start_period 15s → 90s

Bug fixes:
- Removed fake intelligence assessment generator (OSINT-only policy)
- Fixed carrier tracker GDELT 429/TypeError crash
- Fixed ETag collision (full payload hash)
- Added concurrent /api/refresh guard

Contributors: @imqdcr (ship split + stable IDs), @csysp (dismissible alerts, PR #48)

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

Former-commit-id: a2c4c67da54345393f70a9b33b52e7e4fd6c049f
This commit is contained in:
anoracleofra-code
2026-03-13 11:32:16 -06:00
co-authored by Claude Opus 4.6
parent 1eb2b21647
commit fc9eff865e
62 changed files with 4930 additions and 4852 deletions
+30
View File
@@ -72,6 +72,36 @@ body {
scrollbar-width: thin;
}
/* Map popup shared utilities */
.map-popup {
background: rgba(10, 14, 26, 0.95);
border-radius: 6px;
padding: 10px 14px;
color: #e0e6f0;
font-family: monospace;
font-size: 11px;
min-width: 220px;
max-width: 320px;
}
.map-popup-title {
font-weight: 700;
font-size: 13px;
margin-bottom: 6px;
letter-spacing: 1px;
}
.map-popup-row {
margin-bottom: 4px;
}
.map-popup-subtitle {
font-size: 9px;
margin-bottom: 6px;
letter-spacing: 1.5px;
text-transform: uppercase;
}
/* MapLibre Popup Overrides */
.maplibregl-popup-content {
background: transparent !important;
+1 -4
View File
@@ -25,10 +25,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<head>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.115/Build/Cesium/Cesium.js" async></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.115/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
</head>
<head />
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-[var(--bg-primary)]`}
suppressHydrationWarning
+36 -16
View File
@@ -16,6 +16,7 @@ import SettingsPanel from "@/components/SettingsPanel";
import MapLegend from "@/components/MapLegend";
import ScaleBar from "@/components/ScaleBar";
import ErrorBoundary from "@/components/ErrorBoundary";
import { DashboardDataProvider } from "@/lib/DashboardDataContext";
import OnboardingModal, { useOnboarding } from "@/components/OnboardingModal";
import ChangelogModal, { useChangelog } from "@/components/ChangelogModal";
@@ -135,7 +136,8 @@ export default function Dashboard() {
military: true,
tracked: true,
satellites: true,
ships_important: true,
ships_military: true,
ships_cargo: true,
ships_civilian: false,
ships_passenger: true,
earthquakes: true,
@@ -366,6 +368,7 @@ export default function Dashboard() {
}, []);
return (
<DashboardDataProvider data={data} selectedEntity={selectedEntity} setSelectedEntity={setSelectedEntity}>
<main className="fixed inset-0 w-full h-full bg-[var(--bg-primary)] overflow-hidden font-sans">
{/* MAPLIBRE WEBGL OVERLAY */}
@@ -435,10 +438,14 @@ export default function Dashboard() {
{/* LEFT HUD CONTAINER */}
<div className="absolute left-6 top-24 bottom-6 w-80 flex flex-col gap-6 z-[200] pointer-events-none">
{/* LEFT PANEL - DATA LAYERS */}
<WorldviewLeftPanel data={data} activeLayers={activeLayers} setActiveLayers={setActiveLayers} onSettingsClick={() => setSettingsOpen(true)} onLegendClick={() => setLegendOpen(true)} gibsDate={gibsDate} setGibsDate={setGibsDate} gibsOpacity={gibsOpacity} setGibsOpacity={setGibsOpacity} onEntityClick={setSelectedEntity} onFlyTo={(lat, lng) => setFlyToLocation({ lat, lng, ts: Date.now() })} />
<ErrorBoundary name="WorldviewLeftPanel">
<WorldviewLeftPanel data={data} activeLayers={activeLayers} setActiveLayers={setActiveLayers} onSettingsClick={() => setSettingsOpen(true)} onLegendClick={() => setLegendOpen(true)} gibsDate={gibsDate} setGibsDate={setGibsDate} gibsOpacity={gibsOpacity} setGibsOpacity={setGibsOpacity} onEntityClick={setSelectedEntity} onFlyTo={(lat, lng) => setFlyToLocation({ lat, lng, ts: Date.now() })} />
</ErrorBoundary>
{/* LEFT BOTTOM - DISPLAY CONFIG */}
<WorldviewRightPanel effects={effects} setEffects={setEffects} setUiVisible={setUiVisible} />
<ErrorBoundary name="WorldviewRightPanel">
<WorldviewRightPanel effects={effects} setEffects={setEffects} setUiVisible={setUiVisible} />
</ErrorBoundary>
</div>
{/* RIGHT HUD CONTAINER */}
@@ -466,29 +473,37 @@ export default function Dashboard() {
{/* TOP RIGHT - MARKETS */}
<div className="flex-shrink-0">
<MarketsPanel data={data} />
<ErrorBoundary name="MarketsPanel">
<MarketsPanel data={data} />
</ErrorBoundary>
</div>
{/* SIGINT & RADIO INTERCEPTS */}
<div className="flex-shrink-0">
<RadioInterceptPanel
data={data}
isEavesdropping={isEavesdropping}
setIsEavesdropping={setIsEavesdropping}
eavesdropLocation={eavesdropLocation}
cameraCenter={cameraCenter}
selectedEntity={selectedEntity}
/>
<ErrorBoundary name="RadioInterceptPanel">
<RadioInterceptPanel
data={data}
isEavesdropping={isEavesdropping}
setIsEavesdropping={setIsEavesdropping}
eavesdropLocation={eavesdropLocation}
cameraCenter={cameraCenter}
selectedEntity={selectedEntity}
/>
</ErrorBoundary>
</div>
{/* DATA FILTERS */}
<div className="flex-shrink-0">
<FilterPanel data={data} activeFilters={activeFilters} setActiveFilters={setActiveFilters} />
<ErrorBoundary name="FilterPanel">
<FilterPanel data={data} activeFilters={activeFilters} setActiveFilters={setActiveFilters} />
</ErrorBoundary>
</div>
{/* BOTTOM RIGHT - NEWS FEED (fills remaining space) */}
<div className="flex-1 min-h-0 flex flex-col">
<NewsFeed data={data} selectedEntity={selectedEntity} regionDossier={regionDossier} regionDossierLoading={regionDossierLoading} />
<ErrorBoundary name="NewsFeed">
<NewsFeed data={data} selectedEntity={selectedEntity} regionDossier={regionDossier} regionDossierLoading={regionDossierLoading} />
</ErrorBoundary>
</div>
</div>
@@ -589,10 +604,14 @@ export default function Dashboard() {
<div className="absolute inset-0 pointer-events-none z-[3] opacity-5 bg-[linear-gradient(rgba(255,255,255,0.1)_1px,transparent_1px)]" style={{ backgroundSize: '100% 4px' }}></div>
{/* SETTINGS PANEL */}
<SettingsPanel isOpen={settingsOpen} onClose={() => setSettingsOpen(false)} />
<ErrorBoundary name="SettingsPanel">
<SettingsPanel isOpen={settingsOpen} onClose={() => setSettingsOpen(false)} />
</ErrorBoundary>
{/* MAP LEGEND */}
<MapLegend isOpen={legendOpen} onClose={() => setLegendOpen(false)} />
<ErrorBoundary name="MapLegend">
<MapLegend isOpen={legendOpen} onClose={() => setLegendOpen(false)} />
</ErrorBoundary>
{/* ONBOARDING MODAL */}
{showOnboarding && (
@@ -617,5 +636,6 @@ export default function Dashboard() {
)}
</main>
</DashboardDataProvider>
);
}