2026-04-12 06:33:54 -07:00
|
|
|
import type { MissionControlBridgeStatusResponse } from '@/services/api/missionControl'
|
|
|
|
|
import type { ExplorerStats } from '@/services/api/stats'
|
|
|
|
|
import type { ChainActivityContext } from '@/utils/activityContext'
|
|
|
|
|
import {
|
|
|
|
|
resolveFreshnessSourceLabel,
|
|
|
|
|
summarizeFreshnessConfidence,
|
|
|
|
|
} from '@/utils/explorerFreshness'
|
|
|
|
|
import { formatRelativeAge } from '@/utils/format'
|
2026-04-12 18:22:08 -07:00
|
|
|
import { useUiMode } from './UiModeContext'
|
2026-04-12 06:33:54 -07:00
|
|
|
|
|
|
|
|
function buildSummary(context: ChainActivityContext) {
|
|
|
|
|
if (context.transaction_visibility_unavailable) {
|
|
|
|
|
return 'Chain-head visibility is current, while transaction freshness is currently unavailable.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.state === 'active') {
|
|
|
|
|
return 'Chain head and latest indexed transactions are closely aligned.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.head_is_idle) {
|
|
|
|
|
return 'Chain head is current, while latest visible transactions trail the tip.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.state === 'low' || context.state === 'inactive') {
|
|
|
|
|
return 'Chain head is current, and recent visible transaction activity is sparse.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 'Freshness context is based on the latest visible public explorer evidence.'
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 18:22:08 -07:00
|
|
|
function buildDetail(context: ChainActivityContext, diagnosticExplanation?: string | null) {
|
|
|
|
|
if (diagnosticExplanation) {
|
|
|
|
|
return diagnosticExplanation
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 06:33:54 -07:00
|
|
|
if (context.transaction_visibility_unavailable) {
|
|
|
|
|
return 'Use chain-head visibility and the last non-empty block as the current trust anchors.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const latestTxAge = formatRelativeAge(context.latest_transaction_timestamp)
|
|
|
|
|
const latestNonEmptyBlock =
|
|
|
|
|
context.last_non_empty_block_number != null ? `#${context.last_non_empty_block_number.toLocaleString()}` : 'unknown'
|
|
|
|
|
|
|
|
|
|
if (context.head_is_idle) {
|
|
|
|
|
return `Latest visible transaction: ${latestTxAge}. Last non-empty block: ${latestNonEmptyBlock}.`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.state === 'active') {
|
|
|
|
|
return `Latest visible transaction: ${latestTxAge}. Recent indexed activity remains close to the tip.`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return `Latest visible transaction: ${latestTxAge}. Recent head blocks may be quiet even while the chain remains current.`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function FreshnessTrustNote({
|
|
|
|
|
context,
|
|
|
|
|
stats,
|
|
|
|
|
bridgeStatus,
|
|
|
|
|
scopeLabel,
|
|
|
|
|
className = '',
|
|
|
|
|
}: {
|
|
|
|
|
context: ChainActivityContext
|
|
|
|
|
stats?: ExplorerStats | null
|
|
|
|
|
bridgeStatus?: MissionControlBridgeStatusResponse | null
|
|
|
|
|
scopeLabel?: string
|
|
|
|
|
className?: string
|
|
|
|
|
}) {
|
2026-04-12 18:22:08 -07:00
|
|
|
const { mode } = useUiMode()
|
2026-04-12 06:33:54 -07:00
|
|
|
const sourceLabel = resolveFreshnessSourceLabel(stats, bridgeStatus)
|
|
|
|
|
const confidenceBadges = summarizeFreshnessConfidence(stats, bridgeStatus)
|
2026-04-12 18:22:08 -07:00
|
|
|
const diagnosticExplanation = stats?.diagnostics?.explanation || bridgeStatus?.data?.diagnostics?.explanation || null
|
2026-04-12 06:33:54 -07:00
|
|
|
const normalizedClassName = className ? ` ${className}` : ''
|
|
|
|
|
|
2026-04-12 18:22:08 -07:00
|
|
|
if (mode === 'expert') {
|
|
|
|
|
return (
|
|
|
|
|
<div className={`rounded-2xl border border-gray-200 bg-white/80 px-4 py-3 text-sm dark:border-gray-800 dark:bg-gray-950/40${normalizedClassName}`}>
|
|
|
|
|
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
|
|
|
|
|
<div className="font-medium text-gray-900 dark:text-white">{buildSummary(context)}</div>
|
|
|
|
|
<div className="text-xs text-gray-600 dark:text-gray-400">{sourceLabel}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-2 flex flex-wrap gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
|
{confidenceBadges.map((badge) => (
|
|
|
|
|
<span
|
|
|
|
|
key={badge}
|
|
|
|
|
className="rounded-full border border-gray-200 bg-gray-50 px-2.5 py-1 dark:border-gray-700 dark:bg-gray-900/70"
|
|
|
|
|
>
|
|
|
|
|
{badge}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 06:33:54 -07:00
|
|
|
return (
|
|
|
|
|
<div className={`rounded-2xl border border-gray-200 bg-white/80 px-4 py-3 text-sm dark:border-gray-800 dark:bg-gray-950/40${normalizedClassName}`}>
|
|
|
|
|
<div className="font-medium text-gray-900 dark:text-white">{buildSummary(context)}</div>
|
|
|
|
|
<div className="mt-1 text-gray-600 dark:text-gray-400">
|
2026-04-12 18:22:08 -07:00
|
|
|
{buildDetail(context, diagnosticExplanation)} {scopeLabel ? `${scopeLabel}. ` : ''}{sourceLabel}
|
2026-04-12 06:33:54 -07:00
|
|
|
</div>
|
|
|
|
|
<div className="mt-2 flex flex-wrap gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
|
|
|
{confidenceBadges.map((badge) => (
|
|
|
|
|
<span
|
|
|
|
|
key={badge}
|
|
|
|
|
className="rounded-full border border-gray-200 bg-gray-50 px-2.5 py-1 dark:border-gray-700 dark:bg-gray-900/70"
|
|
|
|
|
>
|
|
|
|
|
{badge}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|