diff --git a/src/components/CVMHealthCheck.tsx b/src/components/CVMHealthCheck.tsx
new file mode 100644
index 0000000..b5597c5
--- /dev/null
+++ b/src/components/CVMHealthCheck.tsx
@@ -0,0 +1,191 @@
+/**
+ * CVMHealthCheck — owner-only status probe for the SkateSpots ContextVM server.
+ *
+ * Renders NOTHING unless the logged-in user is one of the two platform owners
+ * (PLATFORM_OWNER_PUBKEYS). Pings the ContextVM relay for the server's own
+ * events and reports online / stale / offline with a last-seen timestamp.
+ */
+
+import { useCurrentUser } from '@/hooks/useCurrentUser';
+import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
+import {
+ useCVMServerHealth,
+ CVM_SERVER_PUBKEY,
+ CVM_RELAY,
+ type CVMHealthStatus,
+} from '@/hooks/useCVMServerHealth';
+import { cn } from '@/lib/utils';
+import {
+ Activity,
+ CheckCircle2,
+ XCircle,
+ AlertTriangle,
+ Loader2,
+ ExternalLink,
+} from 'lucide-react';
+
+function timeAgo(unixSeconds: number): string {
+ const s = Math.floor(Date.now() / 1000 - unixSeconds);
+ if (s < 60) return `${s}s ago`;
+ const m = Math.floor(s / 60);
+ if (m < 60) return `${m}m ago`;
+ const h = Math.floor(m / 60);
+ if (h < 24) return `${h}h ago`;
+ const d = Math.floor(h / 24);
+ return `${d}d ago`;
+}
+
+const STATUS_META: Record<
+ CVMHealthStatus,
+ { label: string; color: string; ring: string; dot: string }
+> = {
+ idle: { label: 'Not checked', color: 'text-gray-400', ring: 'border-white/10', dot: 'bg-gray-500' },
+ checking: { label: 'Checking…', color: 'text-blue-300', ring: 'border-blue-500/30', dot: 'bg-blue-400' },
+ online: { label: 'Online', color: 'text-green-400', ring: 'border-green-500/30', dot: 'bg-green-400' },
+ stale: { label: 'Stale', color: 'text-amber-400', ring: 'border-amber-500/30', dot: 'bg-amber-400' },
+ offline: { label: 'Offline', color: 'text-red-400', ring: 'border-red-500/30', dot: 'bg-red-500' },
+ error: { label: 'Unreachable', color: 'text-red-400', ring: 'border-red-500/30', dot: 'bg-red-500' },
+};
+
+export function CVMHealthCheck() {
+ const { user } = useCurrentUser();
+ const health = useCVMServerHealth();
+
+ // Owner-only gate. Never render for anyone else.
+ const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
+ if (!isOwner) return null;
+
+ const meta = STATUS_META[health.status];
+
+ const StatusIcon =
+ health.status === 'checking'
+ ? Loader2
+ : health.status === 'online'
+ ? CheckCircle2
+ : health.status === 'stale'
+ ? AlertTriangle
+ : health.status === 'idle'
+ ? Activity
+ : XCircle;
+
+ return (
+
+
+
+
+
+ ContextVM Server
+
+ Owner
+
+
+
+ Health probe for the Sasquatch CVM data server
+
+
+
+
+ {meta.label}
+
+
+
+
+ {/* Status detail line */}
+
+
+
+ {health.status === 'idle' && (
+
+ Tap “Check server” to ping the ContextVM relay for the server’s heartbeat.
+
+ )}
+ {health.status === 'checking' && (
+ Querying {CVM_RELAY}…
+ )}
+ {health.status === 'online' && health.lastSeen && (
+
+ Server is announcing. Last event {timeAgo(health.lastSeen)}.
+
+ )}
+ {health.status === 'stale' && health.lastSeen && (
+
+ Server found, but last activity was {timeAgo(health.lastSeen)} — it may be
+ down or idle.
+
+ )}
+ {health.status === 'offline' && (
+
+ No events from the server on the relay. It’s likely not running.
+
+ )}
+ {health.status === 'error' && (
+ {health.error}
+ )}
+