Skip to content
This repository was archived by the owner on Jul 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 106 additions & 70 deletions src/components/metagraphed/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useQuery } from "@tanstack/react-query";
import { ChevronRight, Compass, Github, Menu, X } from "lucide-react";
import { API_BASE, DISCORD_URL, GITHUB_REPO } from "@/lib/metagraphed/config";
import { useApiBase } from "@/hooks/use-api-base";
import { useEndpointHealth, type EndpointHealth } from "@/hooks/use-endpoint-health";
import { NetworkSwitcher } from "./network-switcher";
import { CopyableCode } from "./copyable-code";
import { SettingsPopover } from "./settings-popover";
Expand Down Expand Up @@ -267,7 +268,7 @@ function SiteFooter() {
className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-accent/60 to-transparent"
/>
<div className="max-w-[1400px] mx-auto px-4 md:px-10 py-14 grid gap-10 md:grid-cols-4 text-[12px] text-ink-muted">
<div className="md:col-span-1">
<div className="md:col-span-2">
<div className="font-display text-base font-semibold text-ink-strong inline-flex items-baseline gap-1">
Metagraphed
<span
Expand All @@ -279,6 +280,28 @@ function SiteFooter() {
Unofficial public-interface registry and developer block explorer for Bittensor. All
data is public, chain-direct, and verifiable.
</p>
<div className="mt-4 flex items-center gap-1">
<a
href={GITHUB_REPO}
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub repository"
title="Open source on GitHub"
className="inline-flex items-center justify-center rounded-md size-8 text-ink-muted hover:text-ink-strong hover:bg-surface transition-colors"
>
<Github className="size-4" />
</a>
<a
href={DISCORD_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Discord community"
title="Join us on Discord"
className="inline-flex items-center justify-center rounded-md size-8 text-ink-muted hover:text-ink-strong hover:bg-surface transition-colors"
>
<DiscordIcon className="size-4" />
</a>
</div>
</div>
<FooterCol title="Registry">
<FooterLink to="/subnets">Subnets</FooterLink>
Expand All @@ -295,40 +318,6 @@ function SiteFooter() {
<FooterLink to="/agents">For agents</FooterLink>
<FooterLink to="/about">About</FooterLink>
</FooterCol>
<FooterCol title="Data">
<a
href={safeExternalUrl(`${API_BASE}/api/v1`)}
target="_blank"
rel="noopener noreferrer"
className="hover:text-ink-strong transition-colors font-mono break-all"
>
{API_BASE.replace(/^https?:\/\//, "")}/api/v1
</a>
<a
href={safeExternalUrl(`${API_BASE}/api/v1/openapi.json`)}
target="_blank"
rel="noopener noreferrer"
className="hover:text-ink-strong transition-colors"
>
OpenAPI spec
</a>
<a
href={GITHUB_REPO}
target="_blank"
rel="noopener noreferrer"
className="hover:text-ink-strong transition-colors inline-flex items-center gap-1"
>
<Github className="size-3" /> GitHub
</a>
<a
href={DISCORD_URL}
target="_blank"
rel="noopener noreferrer"
className="hover:text-ink-strong transition-colors inline-flex items-center gap-1"
>
<DiscordIcon className="size-3" /> Discord
</a>
</FooterCol>
</div>
<div className="border-t border-border/70">
<div className="max-w-[1400px] mx-auto px-4 md:px-10 py-3">
Expand Down Expand Up @@ -356,47 +345,94 @@ function RegistryPulseStrip() {
const stale = f?.stale_count ?? 0;
const sources = f?.sources?.length ?? 0;
return (
<div className="mg-ticker">
<span className="inline-flex items-center gap-1.5">
<span className="mg-live-dot" aria-hidden />
<span className="uppercase tracking-[0.22em] text-[10px]">registry pulse</span>
</span>
<span>·</span>
<span>
<span className="text-ink-muted">sources</span>{" "}
<span className="text-ink-strong">{sources}</span>
</span>
<span>·</span>
<span>
<span className="text-ink-muted">stale</span>{" "}
<span
className={classNames("tabular-nums", stale ? "text-health-warn" : "text-ink-strong")}
>
{stale}
<div className="flex items-center justify-between gap-4">
<div className="mg-ticker min-w-0">
<span className="inline-flex items-center gap-1.5">
<span className="mg-live-dot" aria-hidden />
<span className="uppercase tracking-[0.22em] text-[10px]">registry pulse</span>
</span>
</span>
{b?.version ? (
<>
<span>·</span>
<span>
<span className="text-ink-muted">build</span>{" "}
<span className="text-ink-strong">{b.version}</span>
<span>·</span>
<span>
<span className="text-ink-muted">sources</span>{" "}
<span className="text-ink-strong">{sources}</span>
</span>
<span>·</span>
<span>
<span className="text-ink-muted">stale</span>{" "}
<span
className={classNames("tabular-nums", stale ? "text-health-warn" : "text-ink-strong")}
>
{stale}
</span>
</>
) : null}
<span>·</span>
<a
href={safeExternalUrl(`${API_BASE}/api/v1/openapi.json`)}
target="_blank"
rel="noopener noreferrer"
className="hover:text-ink-strong transition-colors"
>
openapi
</a>
</span>
{b?.version ? (
<>
<span>·</span>
<span>
<span className="text-ink-muted">build</span>{" "}
<span className="text-ink-strong">{b.version}</span>
</span>
</>
) : null}
<span>·</span>
<a
href={safeExternalUrl(`${API_BASE}/api/v1/openapi.json`)}
target="_blank"
rel="noopener noreferrer"
className="hover:text-ink-strong transition-colors"
>
openapi
</a>
</div>
<EndpointHealthPill />
</div>
);
}

// Map the live endpoint-health tier to a design-system health token. Only the
// dot is coloured (via currentColor); the text stays neutral for AA contrast.
const ENDPOINT_TONE: Record<EndpointHealth, string> = {
checking: "text-ink-muted",
ok: "text-health-ok",
slow: "text-health-warn",
bad: "text-health-bad",
down: "text-health-down",
};

const ENDPOINT_LABEL: Record<EndpointHealth, string> = {
checking: "checking…",
ok: "healthy",
slow: "slow",
bad: "degraded",
down: "down",
};

// Right side of the pulse strip: the live API endpoint with a glowing dot that
// reflects round-trip health (green ok · yellow slow · orange bad · red down).
function EndpointHealthPill() {
const { base } = useApiBase();
const { status, ms } = useEndpointHealth();
const tone = ENDPOINT_TONE[status];
const endpoint = `${base.replace(/^https?:\/\//, "")}/api/v1`;
const detail = ms != null ? `${ENDPOINT_LABEL[status]} · ${ms} ms` : ENDPOINT_LABEL[status];
return (
<a
href={safeExternalUrl(`${base}/api/v1`)}
target="_blank"
rel="noopener noreferrer"
title={`API endpoint · ${detail}`}
className="shrink-0 inline-flex items-center gap-2 font-mono text-[11px] text-ink-muted hover:text-ink-strong transition-colors"
>
<span className={classNames("mg-health-dot", tone)} aria-hidden />
<span className="hidden sm:inline">{endpoint}</span>
<span className="text-ink-subtle" aria-hidden>
·
</span>
<span>{detail}</span>
</a>
);
}

function FooterCol({ title, children }: { title: string; children: ReactNode }) {
return (
<div>
Expand Down
64 changes: 64 additions & 0 deletions src/hooks/use-endpoint-health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useEffect, useState } from "react";
import { useApiBase } from "./use-api-base";

export type EndpointHealth = "checking" | "ok" | "slow" | "bad" | "down";

export interface EndpointHealthState {
status: EndpointHealth;
ms: number | null;
}

// Round-trip latency tiers for the footer's API-endpoint health dot.
const SLOW_MS = 300; // ok → slow (yellow)
const BAD_MS = 800; // slow → bad (orange)
const REFRESH_MS = 30_000;

function classify(ms: number | null): EndpointHealth {
if (ms == null) return "down";
if (ms > BAD_MS) return "bad";
if (ms > SLOW_MS) return "slow";
return "ok";
}

async function pingMs(url: string, signal: AbortSignal): Promise<number | null> {
const start = performance.now?.() ?? Date.now();
try {
const res = await fetch(url, { headers: { Accept: "application/json" }, signal });
if (!res.ok) return null;
return Math.round((performance.now?.() ?? Date.now()) - start);
} catch {
return null; // network error / abort → treated as down by the caller
}
}

/**
* Polls the live API origin and buckets round-trip latency into health tiers
* (ok / slow / bad / down) for the footer pulse-strip dot. Re-checks every 30s
* and whenever the runtime API base changes. Read-only — mutates no app state.
*/
export function useEndpointHealth(): EndpointHealthState {
const { base } = useApiBase();
const [state, setState] = useState<EndpointHealthState>({ status: "checking", ms: null });

useEffect(() => {
const url = `${base.replace(/\/$/, "")}/api/v1/coverage`;
let active = true;
const controller = new AbortController();

async function check() {
const ms = await pingMs(url, controller.signal);
if (active) setState({ status: classify(ms), ms });
}

setState({ status: "checking", ms: null });
void check();
const id = window.setInterval(() => void check(), REFRESH_MS);
return () => {
active = false;
controller.abort();
window.clearInterval(id);
};
}, [base]);

return state;
}
26 changes: 25 additions & 1 deletion src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
for small "warn" *text* on paper/card, which needs 4.5:1 (light theme only —
dark-theme --health-warn already passes). */
--color-health-warn-text: var(--health-warn-text);
--color-health-bad: var(--health-bad);
--color-health-down: var(--health-down);
--color-health-unknown: var(--health-unknown);

Expand Down Expand Up @@ -144,6 +145,7 @@
/* AA text variant of --health-warn (same hue/chroma, darker L). 4.58:1 on
paper, 4.92:1 on card. Used only where --health-warn styles small text. */
--health-warn-text: oklch(0.55 0.12 80);
--health-bad: oklch(0.66 0.18 50); /* orange — between warn (75) and down (28) */
--health-down: oklch(0.58 0.22 28);
--health-unknown: oklch(0.66 0.01 200);

Expand Down Expand Up @@ -215,6 +217,7 @@
/* Dark --health-warn already clears AA as text (≈10.6:1), so the text variant
is the shared token itself — no visual change in dark mode. */
--health-warn-text: var(--health-warn);
--health-bad: oklch(0.75 0.18 52); /* orange — between warn (75) and down (28) */
--health-down: oklch(0.72 0.2 28);
--health-unknown: oklch(0.52 0.005 250);

Expand Down Expand Up @@ -536,9 +539,30 @@ html[data-density="compact"] .bg-card > table td {
animation: mg-pulse 1.8s ease-in-out infinite;
}

/* Health dot — like mg-live-dot, but colour follows currentColor so it can
reflect endpoint health (text-health-ok / -warn / -bad / -down). */
.mg-health-dot {
position: relative;
display: inline-block;
width: 0.45rem;
height: 0.45rem;
border-radius: 9999px;
background: currentColor;
}
.mg-health-dot::after {
content: "";
position: absolute;
inset: -3px;
border-radius: 9999px;
background: currentColor;
opacity: 0.3;
animation: mg-pulse 1.8s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
.mg-fade-in,
.mg-live-dot::after {
.mg-live-dot::after,
.mg-health-dot::after {
animation: none;
}
}
Expand Down
Loading