Release validation: tests green, USB pack, fleet UX and API hardening.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { ReactNode, memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import AmbientBackground from '../Ambient/AmbientBackground';
|
||||
import SystemStatusBar from '../Visual/SystemStatusBar';
|
||||
@@ -15,7 +15,7 @@ import { resolvePageWeather } from '../../help/pageWeather';
|
||||
import { api } from '../../api/client';
|
||||
import { usePresence } from '../../context/PresenceContext';
|
||||
import ComradeAvatar from '../Presence/ComradeAvatar';
|
||||
import type { ServerConfig } from '../../types';
|
||||
import type { ServerConfig, ServerInfo } from '../../types';
|
||||
import '../Presence/Presence.css';
|
||||
import './Layout.css';
|
||||
import './MobileNav.css';
|
||||
@@ -145,12 +145,19 @@ function formatHashrate(hs: number): string {
|
||||
return `${hs.toFixed(0)} H/s`;
|
||||
}
|
||||
|
||||
function FleetReadout() {
|
||||
const FleetReadout = memo(function FleetReadout() {
|
||||
const { agents } = useWebSocket();
|
||||
const online = agents.filter((a) => a.status === 'online').length;
|
||||
const total = agents.length;
|
||||
const totalHashrate = agents.reduce((sum, a) => sum + (a.hashrate_15s ?? a.hashrate_15m ?? 0), 0);
|
||||
const fillPct = total > 0 ? Math.round((online / total) * 100) : 0;
|
||||
const { online, total, totalHashrate, fillPct } = useMemo(() => {
|
||||
const on = agents.filter((a) => a.status === 'online').length;
|
||||
const tot = agents.length;
|
||||
const hr = agents.reduce((sum, a) => sum + (a.hashrate_15s ?? a.hashrate_15m ?? 0), 0);
|
||||
return {
|
||||
online: on,
|
||||
total: tot,
|
||||
totalHashrate: hr,
|
||||
fillPct: tot > 0 ? Math.round((on / tot) * 100) : 0,
|
||||
};
|
||||
}, [agents]);
|
||||
|
||||
// Tick animation: flash hashrate value whenever it meaningfully changes
|
||||
const [flash, setFlash] = useState(false);
|
||||
@@ -191,13 +198,18 @@ function FleetReadout() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function MobileTopStats() {
|
||||
const MobileTopStats = memo(function MobileTopStats() {
|
||||
const { agents } = useWebSocket();
|
||||
const online = agents.filter((a) => a.status === 'online').length;
|
||||
const total = agents.length;
|
||||
const hr = agents.reduce((s, a) => s + (a.hashrate_15s ?? a.hashrate_15m ?? 0), 0);
|
||||
const { online, total, hr } = useMemo(() => {
|
||||
const on = agents.filter((a) => a.status === 'online').length;
|
||||
return {
|
||||
online: on,
|
||||
total: agents.length,
|
||||
hr: agents.reduce((s, a) => s + (a.hashrate_15s ?? a.hashrate_15m ?? 0), 0),
|
||||
};
|
||||
}, [agents]);
|
||||
return (
|
||||
<div className="mobile-top-stats">
|
||||
<strong>
|
||||
@@ -206,17 +218,23 @@ function MobileTopStats() {
|
||||
<span>{hr > 0 ? formatHashrate(hr) : 'IDLE'}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const location = useLocation();
|
||||
const isMobile = useIsMobileLayout();
|
||||
const { othersOnline, comrades } = usePresence();
|
||||
const [serverConfig, setServerConfig] = useState<ServerConfig | null>(null);
|
||||
const [serverInfo, setServerInfo] = useState<ServerInfo | null>(null);
|
||||
const [moreOpen, setMoreOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
api.getConfig().then(setServerConfig).catch(() => {});
|
||||
Promise.all([api.getConfig(), api.getServerInfo()])
|
||||
.then(([cfg, info]) => {
|
||||
setServerConfig(cfg);
|
||||
setServerInfo(info);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -232,7 +250,7 @@ export default function Layout({ children }: LayoutProps) {
|
||||
};
|
||||
}, [moreOpen]);
|
||||
|
||||
const setupStatus = getSetupStatus(serverConfig);
|
||||
const setupStatus = getSetupStatus(serverConfig, serverInfo);
|
||||
const pageWeather = resolvePageWeather(location.pathname);
|
||||
const moreActive = MOBILE_MORE.some((item) => location.pathname === item.to);
|
||||
const mobileShortLabel: Record<string, string> = {
|
||||
|
||||
@@ -170,6 +170,15 @@ export default function MatrixRain() {
|
||||
|
||||
let raf: number;
|
||||
let lastTime = 0;
|
||||
let paused = document.visibilityState === 'hidden';
|
||||
|
||||
const onVis = () => {
|
||||
paused = document.visibilityState === 'hidden';
|
||||
if (!paused && !raf) {
|
||||
raf = requestAnimationFrame(draw);
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVis);
|
||||
|
||||
const drawWordDrop = (wd: WordDrop, speedMult: number, intense: boolean) => {
|
||||
const H = canvas.height;
|
||||
@@ -204,6 +213,10 @@ export default function MatrixRain() {
|
||||
};
|
||||
|
||||
const draw = (ts: number) => {
|
||||
if (paused) {
|
||||
raf = 0;
|
||||
return;
|
||||
}
|
||||
raf = requestAnimationFrame(draw);
|
||||
const intense = forgingRef.current || crucibleRef.current;
|
||||
const targetFps = intense ? 50 : 24;
|
||||
@@ -317,6 +330,7 @@ export default function MatrixRain() {
|
||||
raf = requestAnimationFrame(draw);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', onVis);
|
||||
cancelAnimationFrame(raf);
|
||||
clearInterval(injectInterval);
|
||||
clearInterval(wordInterval);
|
||||
|
||||
Reference in New Issue
Block a user