mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-09 12:16:01 +02:00
fix: polish v0.9.7 micro update
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
"{pred.title}"
|
||||
@@ -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
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -635,6 +635,8 @@ export interface NewsArticle {
|
||||
kalshi_pct: number | null;
|
||||
consensus_pct: number | null;
|
||||
match_score: number;
|
||||
slug?: string;
|
||||
kalshi_ticker?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user