diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx
index 7106e64..f0f5f1d 100644
--- a/src/components/layout/AppLayout.tsx
+++ b/src/components/layout/AppLayout.tsx
@@ -4,6 +4,7 @@ import { Outlet } from 'react-router-dom';
import { useOnNavigate, useSSE } from '../../hooks';
import LoadingPage from '../../pages/LoadingPage';
import { TopNav, Footer } from '../nav';
+import HaltBanner from './HaltBanner';
const AppLayout: React.FC = () => {
// Single SSE subscription for the whole app โ every routed page
@@ -22,6 +23,7 @@ const AppLayout: React.FC = () => {
}}
>
+
{
+ const { data: halt } = useHaltState();
+ if (!halt?.halted) return null;
+
+ return (
+
+
+ System is paused for maintenance. Swaps are not being initiated.
+ Emissions are recycling. Scoring will resume when the system is
+ unpaused.
+ {halt.asOfBlock != null && (
+
+ (as of block #{halt.asOfBlock.toLocaleString()})
+
+ )}
+
+
+ );
+};
+
+export default HaltBanner;
diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts
index b168660..0e93839 100644
--- a/src/components/layout/index.ts
+++ b/src/components/layout/index.ts
@@ -1,3 +1,4 @@
export { default as AppLayout } from './AppLayout';
+export { default as HaltBanner } from './HaltBanner';
export { default as Page } from './Page';
export type { PageProps } from './Page';
diff --git a/src/components/miners/StickyNetworkHeader.tsx b/src/components/miners/StickyNetworkHeader.tsx
index b8cf919..3256217 100644
--- a/src/components/miners/StickyNetworkHeader.tsx
+++ b/src/components/miners/StickyNetworkHeader.tsx
@@ -5,9 +5,24 @@ import { useCurrentCrown, useHaltState } from '../../api';
import CrownIcon from './CrownIcon';
import { FONTS } from '../../theme';
+// Mirrors allways/constants.py SCORING_WINDOW_BLOCKS โ the validator
+// flushes crown_holders / rate_history once per round, so most panels
+// only refresh at that cadence. The top-right indicator surfaces the
+// math (last/next) so we don't need to label every panel individually.
+const SCORING_WINDOW_BLOCKS = 600;
+
const StickyNetworkHeader: React.FC = () => {
const { data: crown } = useCurrentCrown();
const { data: halt } = useHaltState();
+ const head = halt?.asOfBlock ?? 0;
+ const lastRefresh =
+ head > 0
+ ? Math.floor(head / SCORING_WINDOW_BLOCKS) * SCORING_WINDOW_BLOCKS
+ : null;
+ const blocksUntilRefresh =
+ lastRefresh != null
+ ? Math.max(0, lastRefresh + SCORING_WINDOW_BLOCKS - head)
+ : null;
const segments: React.ReactNode[] = [];
if (crown) {
@@ -90,18 +105,49 @@ const StickyNetworkHeader: React.FC = () => {
{segments}
-
+
-
- {halted ? 'halted' : 'healthy'}
-
+ {halted ? (
+
+ paused
+
+ ) : lastRefresh != null ? (
+
+ last refresh #{lastRefresh.toLocaleString()}
+
+ ยท
+
+ next ~{blocksUntilRefresh} blocks
+
+ ) : (
+
+ healthy
+
+ )}