Add scout constellation mode for APK venue persona packs.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Cluster 3+ scout_report hits on the same SSID within 10 minutes; server infers airport/campus/retail venue class and pushes persona spread_policy. Emberwake weather-map merges active scout biomes. Includes agent, server API, and Vitest coverage.
This commit is contained in:
AetherForge
2026-06-07 09:18:58 -07:00
parent 8b14582975
commit bbab38f8e1
60 changed files with 2610 additions and 43 deletions

View File

@@ -12,6 +12,7 @@ import { SacredMotif } from '../Visual/sacredGeometry/motifs';
import SetupBanner from '../SetupBanner';
import { getSetupStatus } from '../../help/setupStatus';
import { resolvePageWeather } from '../../help/pageWeather';
import { mergeScoutBiomeWeather, type ScoutConstellationSnapshot } from '../../help/scoutBiomeWeather';
import { isDashboardRoute } from '../../help/routeEffects';
import { api } from '../../api/client';
import { usePresence } from '../../context/PresenceContext';
@@ -40,10 +41,11 @@ function operatorDeckId(pathname: string): string {
if (path.startsWith('/lotl-timeline') || path.startsWith('/onion')) return 'lotl-timeline';
if (path.startsWith('/roi')) return 'roi';
if (path.startsWith('/activity')) return 'activity';
if (path.startsWith('/seer')) return 'seer';
return 'dashboard';
}
const NAV = [
const NAV_BASE = [
{ to: '/dashboard', label: 'Command Deck', icon: 'deck' },
{ to: '/crucible', label: 'Crucible', icon: 'crucible' },
{ to: '/activity', label: 'Activity Feed', icon: 'activity' },
@@ -57,6 +59,20 @@ const NAV = [
{ to: '/settings', label: 'Calibrate', icon: 'gear' },
] as const;
const SEER_NAV = { to: '/seer', label: 'Seer', icon: 'seer' } as const;
function buildNav(aiControlEnabled: boolean) {
if (!aiControlEnabled) {
return [...NAV_BASE];
}
const items = [...NAV_BASE];
const calibrateIdx = items.findIndex((i) => i.to === '/settings');
items.splice(calibrateIdx, 0, SEER_NAV);
return items;
}
const NAV = NAV_BASE;
const DOCS_HREF = '/docs/';
/** Primary tabs on mobile bottom bar — Deck, Crucible, Activity, ROI, Onion */
@@ -150,6 +166,14 @@ function NavIcon({ type }: { type: string }) {
<path d="M12 8v4" />
</svg>
);
case 'seer':
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
<ellipse cx="12" cy="12" rx="9" ry="5" />
<circle cx="12" cy="12" r="2.5" fill="currentColor" strokeWidth="0" />
<path d="M4 12c2-3 5-4.5 8-4.5s6 1.5 8 4.5" strokeOpacity="0.45" />
</svg>
);
case 'docs':
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
@@ -281,7 +305,19 @@ export default function Layout({ children }: LayoutProps) {
}, [moreOpen]);
const setupStatus = getSetupStatus(serverConfig, serverInfo);
const pageWeather = resolvePageWeather(location.pathname);
const { latestMessage } = useWebSocket();
const scoutBiome = useMemo(() => {
if (latestMessage?.type !== 'scout_constellations') return null;
return latestMessage.payload as ScoutConstellationSnapshot;
}, [latestMessage]);
const pageWeather = useMemo(() => {
const base = resolvePageWeather(location.pathname);
const path = location.pathname.split('?')[0].replace(/\/$/, '') || '/';
if (path === '/emberwake' || path === '/spread' || path === '/dashboard' || path === '/agents') {
return mergeScoutBiomeWeather(base, scoutBiome);
}
return base;
}, [location.pathname, scoutBiome]);
const showDeckEffects = isDashboardRoute(location.pathname);
const moreActive = MOBILE_MORE.some((item) => location.pathname === item.to);
const mobileShortLabel: Record<string, string> = {