feat(explorer): mission-control resilience, ops token labels, and CI validate
Some checks failed
Deploy Explorer Live / deploy (push) Failing after 14s
Validate Explorer / frontend (push) Successful in 1m34s
Validate Explorer / smoke-e2e (push) Failing after 1m26s

Add SSE reconnect with backoff, fallback REST polling, visibility-aware refresh,
extended token-list labels on operations pages, validate-on-pr workflow, and smoke coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-22 20:40:11 -07:00
parent e3ec87c324
commit 7a7dfca221
17 changed files with 268 additions and 42 deletions

View File

@@ -0,0 +1,13 @@
import { TOKEN_LIST_SURFACE_LABELS, type TokenListSurface } from '@/services/api/tokenListSurfaces'
interface TokenListSurfaceNoteProps {
surface?: TokenListSurface
className?: string
}
export default function TokenListSurfaceNote({
surface = 'extended',
className = 'text-sm text-gray-600 dark:text-gray-400',
}: TokenListSurfaceNoteProps) {
return <p className={className}>{TOKEN_LIST_SURFACE_LABELS[surface]}</p>
}

View File

@@ -3,7 +3,8 @@
import { useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { Card } from '@/libs/frontend-ui-primitives'
import { configApi, type TokenListResponse } from '@/services/api/config'
import { type TokenListResponse } from '@/services/api/config'
import { tokensApi } from '@/services/api/tokens'
import {
aggregateLiquidityPools,
featuredLiquiditySymbols,
@@ -22,6 +23,7 @@ import ActivityContextPanel from '@/components/common/ActivityContextPanel'
import FreshnessTrustNote from '@/components/common/FreshnessTrustNote'
import MarketEvidenceNote from '@/components/common/MarketEvidenceNote'
import SubsystemPosturePanel from '@/components/common/SubsystemPosturePanel'
import TokenListSurfaceNote from '@/components/common/TokenListSurfaceNote'
import { resolveEffectiveFreshness } from '@/utils/explorerFreshness'
import {
formatCurrency,
@@ -98,7 +100,7 @@ export default function LiquidityOperationsPage({
const load = async () => {
const [tokenListResult, routeMatrixResult, plannerCapabilitiesResult, planResult, statsResult, bridgeResult] =
await Promise.allSettled([
configApi.getTokenList(),
tokensApi.listForSurface('extended', 138).then(({ ok, data }) => ({ tokens: ok ? data : [] })),
routesApi.getRouteMatrix(),
plannerApi.getCapabilities(),
plannerApi.getInternalExecutionPlan(),
@@ -273,6 +275,7 @@ export default function LiquidityOperationsPage({
public route matrix, planner capabilities, and mission-control token pool inventory together
so integrators can inspect what Chain 138 is actually serving right now.
</p>
<TokenListSurfaceNote className="mt-3 text-sm text-gray-600 dark:text-gray-400" />
</div>
{loadingError ? (

View File

@@ -3,6 +3,7 @@ import Link from 'next/link'
import { Card } from '@/libs/frontend-ui-primitives'
import { explorerFeaturePages } from '@/data/explorerOperations'
import { configApi, type CapabilitiesResponse, type NetworksConfigResponse, type TokenListResponse } from '@/services/api/config'
import { tokensApi } from '@/services/api/tokens'
import { getMissionControlRelays, missionControlApi, type MissionControlBridgeStatusResponse } from '@/services/api/missionControl'
import { routesApi, type RouteMatrixResponse } from '@/services/api/routes'
import { useUiMode } from '@/components/common/UiModeContext'
@@ -10,6 +11,7 @@ import { summarizeChainActivity } from '@/utils/activityContext'
import ActivityContextPanel from '@/components/common/ActivityContextPanel'
import FreshnessTrustNote from '@/components/common/FreshnessTrustNote'
import SubsystemPosturePanel from '@/components/common/SubsystemPosturePanel'
import TokenListSurfaceNote from '@/components/common/TokenListSurfaceNote'
import { resolveEffectiveFreshness } from '@/utils/explorerFreshness'
import { statsApi, type ExplorerStats } from '@/services/api/stats'
@@ -88,7 +90,7 @@ export default function OperationsHubPage({
missionControlApi.getBridgeStatus(),
routesApi.getRouteMatrix(),
configApi.getNetworks(),
configApi.getTokenList(),
tokensApi.listForSurface('extended', 138).then(({ ok, data }) => ({ tokens: ok ? data : [] })),
configApi.getCapabilities(),
statsApi.get(),
])
@@ -248,6 +250,7 @@ export default function OperationsHubPage({
<div className="mt-2 text-sm text-gray-600 dark:text-gray-400">
Chains · {(tokenList?.tokens || []).length} tokens across {tokenChainCoverage} networks
</div>
<TokenListSurfaceNote className="mt-2 text-xs text-gray-500 dark:text-gray-400" />
</Card>
<Card className="border border-gray-200 dark:border-gray-700">

View File

@@ -3,7 +3,9 @@
import { useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { Card } from '@/libs/frontend-ui-primitives'
import { configApi, type TokenListResponse } from '@/services/api/config'
import { type TokenListResponse } from '@/services/api/config'
import { tokensApi } from '@/services/api/tokens'
import TokenListSurfaceNote from '@/components/common/TokenListSurfaceNote'
import {
aggregateLiquidityPools,
getRouteBackedPoolAddresses,
@@ -28,7 +30,7 @@ export default function PoolsOperationsPage() {
const load = async () => {
const [tokenListResult, routeMatrixResult] = await Promise.allSettled([
configApi.getTokenList(),
tokensApi.listForSurface('extended', 138).then(({ ok, data }) => ({ tokens: ok ? data : [] })),
routesApi.getRouteMatrix(),
])
@@ -100,6 +102,7 @@ export default function PoolsOperationsPage() {
This page now summarizes the live pool inventory discovered through mission-control token
pool endpoints and cross-checks it against the current route matrix.
</p>
<TokenListSurfaceNote className="mt-3 text-sm text-gray-600 dark:text-gray-400" />
</div>
{loadingError ? (

View File

@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { Card } from '@/libs/frontend-ui-primitives'
import { explorerFeaturePages } from '@/data/explorerOperations'
import { configApi, type CapabilitiesResponse, type NetworksConfigResponse, type TokenListResponse } from '@/services/api/config'
import { tokensApi } from '@/services/api/tokens'
import { getMissionControlRelays, missionControlApi, type MissionControlBridgeStatusResponse } from '@/services/api/missionControl'
import { routesApi, type RouteMatrixResponse } from '@/services/api/routes'
import { statsApi, type ExplorerStats } from '@/services/api/stats'
@@ -11,6 +12,7 @@ import OperationsPageShell, {
formatNumber,
relativeAge,
} from './OperationsPageShell'
import TokenListSurfaceNote from '@/components/common/TokenListSurfaceNote'
interface SystemOperationsPageProps {
initialBridgeStatus?: MissionControlBridgeStatusResponse | null
@@ -46,7 +48,7 @@ export default function SystemOperationsPage({
await Promise.allSettled([
missionControlApi.getBridgeStatus(),
configApi.getNetworks(),
configApi.getTokenList(),
tokensApi.listForSurface('extended', 138).then(({ ok, data }) => ({ tokens: ok ? data : [] })),
configApi.getCapabilities(),
routesApi.getRouteMatrix(),
statsApi.get(),
@@ -125,6 +127,7 @@ export default function SystemOperationsPage({
description={`${formatNumber(capabilities?.tracing?.supportedMethods?.length)} tracing methods published.`}
/>
</div>
<TokenListSurfaceNote className="mb-6 text-xs text-gray-500 dark:text-gray-400" />
<div className="mb-8 grid gap-6 lg:grid-cols-[1fr_1fr]">
<Card title="Topology Snapshot">

View File

@@ -32,6 +32,7 @@ import {
HOME_PRICE_FEED_REFRESH_MS,
resolveHomePriceFeedAddresses,
} from '@/utils/featuredTokens'
import { createVisibilityAwarePoller } from '@/utils/visibilityRefresh'
type HomeStats = ExplorerStats
@@ -180,14 +181,11 @@ export default function Home({
}, [chainId])
useEffect(() => {
loadDashboard()
const timer = window.setInterval(() => {
void loadDashboard()
}, HOME_DASHBOARD_REFRESH_MS)
return () => {
window.clearInterval(timer)
}
void loadDashboard()
return createVisibilityAwarePoller({
intervalMs: HOME_DASHBOARD_REFRESH_MS,
task: loadDashboard,
})
}, [loadDashboard])
useEffect(() => {
@@ -210,7 +208,6 @@ export default function Home({
useEffect(() => {
let cancelled = false
let timer: ReturnType<typeof setTimeout> | null = null
const refreshFeaturedPrices = async () => {
try {
@@ -221,20 +218,18 @@ export default function Home({
if (!cancelled && process.env.NODE_ENV !== 'production') {
console.warn('Failed to load featured token prices:', error)
}
} finally {
if (!cancelled) {
timer = setTimeout(() => {
void refreshFeaturedPrices()
}, HOME_PRICE_FEED_REFRESH_MS)
}
}
}
void refreshFeaturedPrices()
const stop = createVisibilityAwarePoller({
intervalMs: HOME_PRICE_FEED_REFRESH_MS,
task: refreshFeaturedPrices,
})
return () => {
cancelled = true
if (timer) clearTimeout(timer)
stop()
}
}, [loadFeaturedPrices])
@@ -307,6 +302,31 @@ export default function Home({
}
}, [])
useEffect(() => {
if (relayFeedState !== 'fallback') return
let cancelled = false
const refreshSnapshot = async () => {
try {
const status = await missionControlApi.getBridgeStatus()
if (!cancelled) {
setBridgeStatus(status)
setRelaySummary(summarizeMissionControlRelay(status))
}
} catch (error) {
if (!cancelled && process.env.NODE_ENV !== 'production') {
console.warn('Failed to refresh mission control snapshot:', error)
}
}
}
return createVisibilityAwarePoller({
intervalMs: HOME_DASHBOARD_REFRESH_MS,
task: refreshSnapshot,
})
}, [relayFeedState])
const relayToneClasses =
relaySummary?.tone === 'danger'
? 'border-red-200 bg-red-50 text-red-900 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-100'

View File

@@ -18,6 +18,7 @@ import {
type TransactionSummary,
} from '@/services/api/addresses'
import { WALLET_SNAPSHOT_REFRESH_MS } from '@/utils/featuredTokens'
import { createVisibilityAwarePoller } from '@/utils/visibilityRefresh'
import { formatRelativeAge, formatTokenAmount } from '@/utils/format'
import {
isWatchlistEntry,
@@ -146,8 +147,6 @@ export default function WalletPage(props: WalletPageProps) {
useEffect(() => {
let cancelled = false
let timer: ReturnType<typeof setTimeout> | null = null
if (!walletSession?.address) {
setAddressInfo(null)
setRecentAddressTransactions([])
@@ -170,20 +169,18 @@ export default function WalletPage(props: WalletPageProps) {
setTokenBalances([])
setTokenTransfers([])
}
} finally {
if (!cancelled) {
timer = setTimeout(() => {
void refreshSnapshot()
}, WALLET_SNAPSHOT_REFRESH_MS)
}
}
}
void refreshSnapshot()
const stop = createVisibilityAwarePoller({
intervalMs: WALLET_SNAPSHOT_REFRESH_MS,
task: refreshSnapshot,
})
return () => {
cancelled = true
if (timer) clearTimeout(timer)
stop()
}
}, [loadWalletSnapshot, walletSession?.address])

View File

@@ -7,6 +7,7 @@ import type { MissionControlBridgeStatusResponse } from '@/services/api/missionC
import type { ExplorerStats } from '@/services/api/stats'
import { fetchPublicJson } from '@/utils/publicExplorer'
import { fetchExplorerTruthContext } from '@/utils/serverExplorerContext'
import { loadTokenListResponseForSurface } from '@/services/api/tokenListSurfaces'
interface TokenPoolRecord {
symbol: string
@@ -46,7 +47,7 @@ export default function LiquidityPage(props: LiquidityPageProps) {
export const getServerSideProps: GetServerSideProps<LiquidityPageProps> = async () => {
const [tokenListResult, routeMatrixResult, plannerCapabilitiesResult, internalPlanResult, truthContext] =
await Promise.all([
fetchPublicJson<TokenListResponse>('/api/config/token-list').catch(() => null),
loadTokenListResponseForSurface('extended', 138).then((value) => value.response).catch(() => null),
fetchPublicJson<RouteMatrixResponse>('/token-aggregation/api/v1/routes/matrix?includeNonLive=true').catch(() => null),
fetchPublicJson<PlannerCapabilitiesResponse>('/token-aggregation/api/v2/providers/capabilities?chainId=138').catch(
() => null,

View File

@@ -6,6 +6,7 @@ import type { CapabilitiesResponse, NetworksConfigResponse, TokenListResponse }
import type { ExplorerStats } from '@/services/api/stats'
import { fetchPublicJson } from '@/utils/publicExplorer'
import { fetchExplorerTruthContext } from '@/utils/serverExplorerContext'
import { loadTokenListResponseForSurface } from '@/services/api/tokenListSurfaces'
interface OperationsPageProps {
initialBridgeStatus: MissionControlBridgeStatusResponse | null
@@ -24,7 +25,7 @@ export const getStaticProps: GetStaticProps<OperationsPageProps> = async () => {
const [routesResult, networksResult, tokenListResult, capabilitiesResult, truthContext] = await Promise.all([
fetchPublicJson<RouteMatrixResponse>('/token-aggregation/api/v1/routes/matrix?includeNonLive=true').catch(() => null),
fetchPublicJson<NetworksConfigResponse>('/api/config/networks').catch(() => null),
fetchPublicJson<TokenListResponse>('/api/config/token-list').catch(() => null),
loadTokenListResponseForSurface('extended', 138).then((value) => value.response).catch(() => null),
fetchPublicJson<CapabilitiesResponse>('/api/config/capabilities').catch(() => null),
fetchExplorerTruthContext(),
])

View File

@@ -1,6 +1,7 @@
import type { GetServerSideProps } from 'next'
import SystemOperationsPage from '@/components/explorer/SystemOperationsPage'
import { fetchPublicJson } from '@/utils/publicExplorer'
import { loadTokenListResponseForSurface } from '@/services/api/tokenListSurfaces'
import type { CapabilitiesResponse, NetworksConfigResponse, TokenListResponse } from '@/services/api/config'
import type { MissionControlBridgeStatusResponse } from '@/services/api/missionControl'
import type { RouteMatrixResponse } from '@/services/api/routes'
@@ -23,7 +24,7 @@ export const getServerSideProps: GetServerSideProps<SystemPageProps> = async ()
const [bridgeStatus, networksConfig, tokenList, capabilities, routeMatrix, stats] = await Promise.all([
fetchPublicJson<MissionControlBridgeStatusResponse>('/explorer-api/v1/track1/bridge/status').catch(() => null),
fetchPublicJson<NetworksConfigResponse>('/api/config/networks').catch(() => null),
fetchPublicJson<TokenListResponse>('/api/config/token-list').catch(() => null),
loadTokenListResponseForSurface('extended', 138).then((value) => value.response).catch(() => null),
fetchPublicJson<CapabilitiesResponse>('/api/config/capabilities').catch(() => null),
fetchPublicJson<RouteMatrixResponse>('/token-aggregation/api/v1/routes/matrix?includeNonLive=true').catch(() => null),
fetchPublicJson('/api/v2/stats').then((value) => normalizeExplorerStats(value as never)).catch(() => null),

View File

@@ -280,27 +280,50 @@ export const missionControlApi = {
return () => {}
}
const eventSource = new window.EventSource(getMissionControlStreamUrl())
let closed = false
let eventSource: EventSource | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
let retryMs = 5_000
const maxRetryMs = 60_000
const handleMessage = (event: MessageEvent<string>) => {
try {
const payload = JSON.parse(event.data) as MissionControlBridgeStatusResponse
retryMs = 5_000
onStatus(payload)
} catch (error) {
onError?.(error)
}
}
eventSource.addEventListener('mission-control', handleMessage)
eventSource.onmessage = handleMessage
const connect = () => {
if (closed) return
eventSource.onerror = () => {
onError?.(new Error('Mission-control live stream connection lost'))
eventSource?.close()
eventSource = new window.EventSource(getMissionControlStreamUrl())
eventSource.addEventListener('mission-control', handleMessage)
eventSource.onmessage = handleMessage
eventSource.onerror = () => {
eventSource?.close()
eventSource = null
onError?.(new Error('Mission-control live stream connection lost'))
if (closed) return
if (reconnectTimer) clearTimeout(reconnectTimer)
reconnectTimer = setTimeout(() => {
reconnectTimer = null
connect()
}, retryMs)
retryMs = Math.min(retryMs * 2, maxRetryMs)
}
}
connect()
return () => {
eventSource.removeEventListener('mission-control', handleMessage)
eventSource.close()
closed = true
if (reconnectTimer) clearTimeout(reconnectTimer)
eventSource?.removeEventListener('mission-control', handleMessage)
eventSource?.close()
}
},
}

View File

@@ -64,3 +64,15 @@ export async function fetchTokenListForSurface(
export function resolveTokenListClientPath(surface: TokenListSurface, chainId = 138): string {
return surface === 'extended' ? CONFIG_TOKEN_LIST_PATH : `/api/v1/report/token-list?chainId=${chainId}`
}
export async function loadTokenListResponseForSurface(
surface: TokenListSurface,
chainId = 138,
): Promise<{ response: { tokens: TokenListToken[] }; source: 'report' | 'config'; label: string }> {
const { tokens, source } = await fetchTokenListForSurface(surface, chainId)
return {
response: { tokens },
source,
label: TOKEN_LIST_SURFACE_LABELS[surface],
}
}

View File

@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest'
import { createVisibilityAwarePoller } from './visibilityRefresh'
describe('createVisibilityAwarePoller', () => {
it('runs the task immediately on schedule and stops when cancelled', async () => {
let count = 0
const stop = createVisibilityAwarePoller({
intervalMs: 50,
task: () => {
count += 1
},
})
await new Promise((resolve) => setTimeout(resolve, 120))
stop()
expect(count).toBeGreaterThanOrEqual(2)
})
})

View File

@@ -0,0 +1,43 @@
export function createVisibilityAwarePoller(options: {
intervalMs: number
task: () => void | Promise<void>
}): () => void {
let cancelled = false
let timeoutId: ReturnType<typeof setTimeout> | null = null
const schedule = () => {
if (cancelled) return
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(async () => {
if (cancelled) return
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
schedule()
return
}
try {
await options.task()
} finally {
schedule()
}
}, options.intervalMs)
}
const onVisibility = () => {
if (!cancelled && typeof document !== 'undefined' && document.visibilityState === 'visible') {
void options.task()
}
}
schedule()
if (typeof document !== 'undefined') {
document.addEventListener('visibilitychange', onVisibility)
}
return () => {
cancelled = true
if (timeoutId) clearTimeout(timeoutId)
if (typeof document !== 'undefined') {
document.removeEventListener('visibilitychange', onVisibility)
}
}
}