fix: polish v0.9.7 micro update

This commit is contained in:
BigBodyCobain
2026-05-02 02:13:36 -06:00
parent 3a8db7f9cd
commit d515aba450
11 changed files with 247 additions and 55 deletions
@@ -1,7 +1,7 @@
'use client';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { Terminal, Radio, Globe, Key, LogOut, Activity, Vote, User, ArrowRightLeft, Briefcase, Mail, Brain, GitBranch, Cpu, KeyRound } from 'lucide-react';
import { Terminal, Radio, Globe, Key, Activity, Vote, User, ArrowRightLeft, Briefcase, Mail, Brain, GitBranch, Cpu, KeyRound } from 'lucide-react';
import { getNodeIdentity, getWormholeIdentityDescriptor } from '@/mesh/meshIdentity';
import {
activateWormholeGatePersona,
@@ -128,7 +128,6 @@ const SECTIONS = [
{ name: 'EXCHANGE', icon: <ArrowRightLeft size={14} className="mr-2" /> },
{ name: 'PROFILE', icon: <User size={14} className="mr-2" /> },
{ name: 'MESSAGES', icon: <Mail size={14} className="mr-2" /> },
{ name: 'EXIT', icon: <LogOut size={14} className="mr-2" /> },
];
interface CommandHistory {
@@ -32,12 +32,22 @@ export default function NetworkStats() {
fetchInfonetNodeStatusSnapshot(true).catch(() => null),
]);
if (!alive) return;
const knownNodes = Number(infonet?.known_nodes || 0);
const syncPeerCount = Number(infonet?.bootstrap?.sync_peer_count || 0);
const defaultSyncPeerCount = Number(infonet?.bootstrap?.default_sync_peer_count || 0);
const lastPeerUrl = String(infonet?.sync_runtime?.last_peer_url || '').trim();
const visibleInfonetNodes = Math.max(
knownNodes,
syncPeerCount,
defaultSyncPeerCount,
lastPeerUrl ? 1 : 0,
);
setStats({
meshtastic: Number(channelsRes?.total_live || channelsRes?.total_nodes || meshRes?.signal_counts?.meshtastic || 0),
aprs: Number(meshRes?.signal_counts?.aprs || 0),
infonetNodes: Number(infonet?.known_nodes || 0),
infonetNodes: visibleInfonetNodes,
infonetEvents: Number(infonet?.total_events || 0),
syncPeers: Number(infonet?.bootstrap?.sync_peer_count || 0),
syncPeers: syncPeerCount,
nodeEnabled: Boolean(infonet?.node_enabled),
syncOutcome: String(infonet?.sync_runtime?.last_outcome || 'offline').toLowerCase(),
});
@@ -2,7 +2,7 @@
import React, { useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { X, Minus } from 'lucide-react';
import { X } from 'lucide-react';
import InfonetShell from './InfonetShell';
interface InfonetTerminalProps {
@@ -55,13 +55,6 @@ export default function InfonetTerminal({
</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={onClose}
className="p-1 text-gray-600 hover:text-gray-300 transition-colors"
title="Minimize"
>
<Minus size={14} />
</button>
<button
onClick={onClose}
className="p-1 text-gray-600 hover:text-red-400 transition-colors"
+69 -15
View File
@@ -16,7 +16,7 @@ import 'maplibre-gl/dist/maplibre-gl.css';
import { computeNightPolygon } from '@/utils/solarTerminator';
import { darkStyle, lightStyle } from '@/components/map/styles/mapStyles';
import maplibregl from 'maplibre-gl';
import { AlertTriangle, Radio, Activity, Play, Satellite } from 'lucide-react';
import { AlertTriangle, Radio, Activity, Play, Satellite, ExternalLink, Info } from 'lucide-react';
import WikiImage from '@/components/WikiImage';
import FishingDestinationRoute from '@/components/map/FishingDestinationRoute';
import { useTheme } from '@/lib/ThemeContext';
@@ -293,6 +293,15 @@ function probeRasterTile(url: string): Promise<boolean> {
});
}
function buildPolymarketUrl(prediction: { slug?: string; title?: string } | null | undefined): string {
const slug = String(prediction?.slug || '').trim();
if (slug) return `https://polymarket.com/event/${encodeURIComponent(slug)}`;
const title = String(prediction?.title || '').trim();
return title
? `https://polymarket.com/search?query=${encodeURIComponent(title)}`
: 'https://polymarket.com/markets';
}
const MaplibreViewer = ({
activeLayers,
activeFilters,
@@ -5712,29 +5721,56 @@ const MaplibreViewer = ({
<div className="px-5 pb-3">
<div className="grid grid-cols-3 gap-2">
{/* Oracle Score */}
<div className={`border rounded p-3 text-center ${oTierBg || 'bg-black/40 border-cyan-800/30'}`}>
<div className="text-[9px] text-[var(--text-muted)] tracking-[0.15em] mb-1.5">ORACLE SCORE</div>
<label className={`border rounded p-3 text-center transition-colors hover:border-white/40 cursor-pointer ${oTierBg || 'bg-black/40 border-cyan-800/30'}`}>
<input type="checkbox" className="peer sr-only" aria-label="Explain Oracle Score" />
<div className="flex items-center justify-center gap-1 text-[9px] text-[var(--text-muted)] tracking-[0.15em] mb-1.5">
<span>ORACLE SCORE</span>
<Info size={10} />
</div>
<div className={`text-[28px] font-bold leading-none ${oTierColor || 'text-gray-500'}`}>
{oScore != null ? oScore.toFixed(1) : '—'}
</div>
{oTier && <div className={`text-[10px] font-bold ${oTierColor} mt-1`}>{oTier}</div>}
</div>
<div className="hidden peer-checked:block mt-2 border-t border-white/10 pt-2 text-left text-[10px] leading-relaxed text-cyan-100">
<div className="text-cyan-400 font-bold tracking-[0.16em] mb-1">SCALE</div>
<p>0-10 weighted signal score combining alert risk and source confidence.</p>
<p className="mt-1 text-[var(--text-muted)]">0-3 low, 4-5 moderate, 6-7 elevated, 8-10 critical.</p>
</div>
</label>
{/* Sentiment */}
<div className={`border rounded p-3 text-center ${sentBg || 'bg-black/40 border-cyan-800/30'}`}>
<div className="text-[9px] text-[var(--text-muted)] tracking-[0.15em] mb-1.5">SENTIMENT</div>
<label className={`border rounded p-3 text-center transition-colors hover:border-white/40 cursor-pointer ${sentBg || 'bg-black/40 border-cyan-800/30'}`}>
<input type="checkbox" className="peer sr-only" aria-label="Explain Sentiment" />
<div className="flex items-center justify-center gap-1 text-[9px] text-[var(--text-muted)] tracking-[0.15em] mb-1.5">
<span>SENTIMENT</span>
<Info size={10} />
</div>
<div className={`text-[28px] font-bold leading-none ${sentColor || 'text-gray-500'}`}>
{sent != null ? <>{sentArrow} {sent > 0 ? '+' : ''}{sent.toFixed(2)}</> : '—'}
</div>
{sentLabel && <div className={`text-[10px] font-bold ${sentColor} mt-1`}>{sentLabel}</div>}
</div>
<div className="hidden peer-checked:block mt-2 border-t border-white/10 pt-2 text-left text-[10px] leading-relaxed text-cyan-100">
<div className="text-cyan-400 font-bold tracking-[0.16em] mb-1">SCALE</div>
<p>-1.00 to +1.00 headline tone. Negative reads more adverse; positive reads more constructive.</p>
<p className="mt-1 text-[var(--text-muted)]">Below -0.10 negative, -0.10 to +0.10 neutral, above +0.10 positive. It measures tone, not truth.</p>
</div>
</label>
{/* Threat Level */}
<div className={`border rounded p-3 text-center ${rs >= 8 ? 'bg-red-500/10 border-red-500/30' : rs >= 6 ? 'bg-orange-500/10 border-orange-500/30' : rs >= 4 ? 'bg-yellow-500/10 border-yellow-500/30' : 'bg-green-500/10 border-green-500/30'}`}>
<div className="text-[9px] text-[var(--text-muted)] tracking-[0.15em] mb-1.5">RISK LEVEL</div>
<label className={`border rounded p-3 text-center transition-colors hover:border-white/40 cursor-pointer ${rs >= 8 ? 'bg-red-500/10 border-red-500/30' : rs >= 6 ? 'bg-orange-500/10 border-orange-500/30' : rs >= 4 ? 'bg-yellow-500/10 border-yellow-500/30' : 'bg-green-500/10 border-green-500/30'}`}>
<input type="checkbox" className="peer sr-only" aria-label="Explain Risk Level" />
<div className="flex items-center justify-center gap-1 text-[9px] text-[var(--text-muted)] tracking-[0.15em] mb-1.5">
<span>RISK LEVEL</span>
<Info size={10} />
</div>
<div className={`text-[28px] font-bold leading-none ${threatColor}`}>{rs}/10</div>
<div className={`text-[10px] font-bold ${threatColor} mt-1`}>
{rs >= 9 ? 'CRITICAL' : rs >= 7 ? 'HIGH' : rs >= 4 ? 'MEDIUM' : 'LOW'}
</div>
</div>
<div className="hidden peer-checked:block mt-2 border-t border-white/10 pt-2 text-left text-[10px] leading-relaxed text-cyan-100">
<div className="text-cyan-400 font-bold tracking-[0.16em] mb-1">SCALE</div>
<p>0-10 operational severity estimate based on source, topic, keywords, corroboration, and alert context.</p>
<p className="mt-1 text-[var(--text-muted)]">0-3 low, 4-6 medium, 7-8 high, 9-10 critical.</p>
</div>
</label>
</div>
</div>
@@ -5742,8 +5778,20 @@ const MaplibreViewer = ({
{pred && pred.consensus_pct != null && (
<div className="px-5 pb-3">
<div className="bg-purple-950/30 border border-purple-500/40 rounded p-4">
<div className="text-[10px] text-purple-400 tracking-[0.2em] font-bold mb-2">
PREDICTION MARKET ANALYSIS
<div className="flex items-center justify-between gap-3 mb-2">
<div className="text-[10px] text-purple-400 tracking-[0.2em] font-bold">
PREDICTION MARKET ANALYSIS
</div>
{pred.polymarket_pct != null && (
<button
type="button"
onClick={() => window.open(buildPolymarketUrl(pred), '_blank', 'noopener,noreferrer')}
className="inline-flex items-center gap-1 text-[10px] text-purple-200 hover:text-white border border-purple-500/30 hover:border-purple-300/70 px-2 py-1 rounded transition-colors"
title="Open this market on Polymarket"
>
POLYMARKET <ExternalLink size={10} />
</button>
)}
</div>
<div className="text-[14px] text-purple-200 font-bold leading-snug mb-3">
&quot;{pred.title}&quot;
@@ -5760,10 +5808,16 @@ const MaplibreViewer = ({
</div>
<div className="flex gap-6 text-[11px]">
{pred.polymarket_pct != null && (
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => window.open(buildPolymarketUrl(pred), '_blank', 'noopener,noreferrer')}
className="flex items-center gap-2 hover:text-white transition-colors"
title="Open this market on Polymarket"
>
<span className="text-purple-400/70">Polymarket</span>
<span className="text-white font-bold text-[13px]">{pred.polymarket_pct}%</span>
</div>
<ExternalLink size={10} className="text-purple-400/70" />
</button>
)}
{pred.kalshi_pct != null && (
<div className="flex items-center gap-2">
@@ -5834,7 +5888,7 @@ const MaplibreViewer = ({
onClick={() => window.open(item.link, '_blank', 'noopener,noreferrer')}
className={`${threatColor} hover:text-white text-[12px] font-bold underline underline-offset-2 cursor-pointer`}
>
VIEW FULL REPORT
GO TO ARTICLE
</button>
) : <span />}
<button
+33 -25
View File
@@ -4,7 +4,9 @@ import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, ExternalLink, Key, Shield, Radar, Globe, Satellite, Ship, Radio } from 'lucide-react';
const STORAGE_KEY = 'shadowbroker_onboarding_complete';
const CURRENT_ONBOARDING_VERSION = '0.9.7';
const STORAGE_KEY = `shadowbroker_onboarding_complete_v${CURRENT_ONBOARDING_VERSION}`;
const LEGACY_STORAGE_KEY = 'shadowbroker_onboarding_complete';
const API_GUIDES = [
{
@@ -62,11 +64,13 @@ const OnboardingModal = React.memo(function OnboardingModal({
const handleDismiss = () => {
localStorage.setItem(STORAGE_KEY, 'true');
localStorage.setItem(LEGACY_STORAGE_KEY, 'true');
onClose();
};
const handleOpenSettings = () => {
localStorage.setItem(STORAGE_KEY, 'true');
localStorage.setItem(LEGACY_STORAGE_KEY, 'true');
onClose();
onOpenSettings();
};
@@ -123,7 +127,7 @@ const OnboardingModal = React.memo(function OnboardingModal({
{/* Step Indicators */}
<div className="flex gap-2 px-6 pt-4">
{['Welcome', 'API Keys', 'Free Sources'].map((label, i) => (
{['API Keys', 'Trust Modes', 'Free Sources'].map((label, i) => (
<button
key={label}
onClick={() => setStep(i)}
@@ -140,36 +144,23 @@ const OnboardingModal = React.memo(function OnboardingModal({
{/* Content */}
<div className="flex-1 overflow-y-auto styled-scrollbar p-6">
{step === 0 && (
{step === 1 && (
<div className="space-y-4">
<div className="text-center py-4">
<div className="text-lg font-bold tracking-[0.3em] text-[var(--text-primary)] font-mono mb-2">
S H A D O W <span className="text-cyan-400">B R O K E R</span>
T R U S T <span className="text-cyan-400">M O D E S</span>
</div>
<p className="text-[11px] text-[var(--text-secondary)] font-mono leading-relaxed max-w-md mx-auto">
<p className="hidden">
Real-time OSINT dashboard aggregating 12+ live intelligence sources. Flights,
ships, satellites, earthquakes, conflicts, and more all on one map.
</p>
<p className="text-[11px] text-[var(--text-secondary)] font-mono leading-relaxed max-w-md mx-auto">
These modes explain what lane the network is using. Set up the API keys first,
then use this screen to understand public mesh versus private Wormhole paths.
</p>
</div>
<div className="bg-yellow-950/20 border border-yellow-500/20 p-4">
<div className="flex items-start gap-2">
<Key size={14} className="text-yellow-500 mt-0.5 flex-shrink-0" />
<div>
<p className="text-[11px] text-yellow-400 font-mono font-bold mb-1">
API Keys Required
</p>
<p className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
Two API keys are needed for full functionality:{' '}
<span className="text-cyan-400">OpenSky Network</span> (flights) and{' '}
<span className="text-blue-400">AIS Stream</span> (ships). Both are free.
Without them, some panels will show no data.
</p>
</div>
</div>
</div>
<div className="bg-green-950/20 border border-green-500/20 p-4">
<div className="hidden">
<div className="flex items-start gap-2">
<Globe size={14} className="text-green-500 mt-0.5 flex-shrink-0" />
<div>
@@ -216,8 +207,24 @@ const OnboardingModal = React.memo(function OnboardingModal({
</div>
)}
{step === 1 && (
{step === 0 && (
<div className="space-y-4">
<div className="bg-yellow-950/20 border border-yellow-500/20 p-4">
<div className="flex items-start gap-2">
<Key size={14} className="text-yellow-500 mt-0.5 flex-shrink-0" />
<div>
<p className="text-[11px] text-yellow-400 font-mono font-bold mb-1">
START HERE
</p>
<p className="text-sm text-[var(--text-secondary)] font-mono leading-relaxed">
OpenSky Network and AIS Stream are the free keys that make ShadowBroker
useful immediately: live aircraft and vessel tracking. Add these first;
trust modes and the no-key sources can come next.
</p>
</div>
</div>
</div>
{API_GUIDES.map((api) => (
<div
key={api.name}
@@ -272,7 +279,8 @@ const OnboardingModal = React.memo(function OnboardingModal({
<div className="space-y-3">
<p className="text-sm text-[var(--text-secondary)] font-mono mb-3">
These data sources are completely free and require no API keys. They activate
automatically on launch.
automatically on launch, while OpenSky and AIS Stream unlock the richer live
aviation and maritime experience.
</p>
<div className="grid grid-cols-2 gap-2">
{FREE_SOURCES.map((src) => (
+2
View File
@@ -635,6 +635,8 @@ export interface NewsArticle {
kalshi_pct: number | null;
consensus_pct: number | null;
match_score: number;
slug?: string;
kalshi_ticker?: string;
} | null;
}