bytelyst-devops-tools/dashboard/web/src/app/hermes/page.tsx

463 lines
26 KiB
TypeScript

'use client';
import { useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { ArrowRight, BadgeCheck, BellRing, Bot, CheckCircle2, Clock3, LayoutDashboard, OctagonAlert, Rocket, ShieldAlert, Sparkles, TriangleAlert } from 'lucide-react';
import { Badge, Button } from '@/components/ui/Primitives';
import { HermesShell, MetricCard, SectionCard } from '@/components/hermes-shell';
import { HermesInstanceBadge } from '@/components/hermes-instance-switcher';
import { HermesOpsPanel } from '@/components/hermes-ops-panel';
import { useHermesInstance } from '@/lib/hermes-instance-context';
import {
getHermesAgents,
getHermesOverview,
getHermesProducts,
getHermesTasks,
hermesProducts,
hermesTasks,
HERMES_INSTANCES,
type HermesProduct,
type HermesTask,
} from '@/lib/hermes';
import {
collectBackupEntries,
collectCronEntries,
collectWatchdogAlerts,
emptyTelemetryState,
loadAllHermesTelemetry,
telemetryForFilter,
type HermesTelemetryState,
} from '@/lib/hermes-telemetry-client';
const fmtDate = new Intl.DateTimeFormat('en', {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
const statusTone: Record<string, 'success' | 'warning' | 'error' | 'info' | 'neutral'> = {
running: 'info',
idle: 'neutral',
degraded: 'warning',
error: 'error',
queued: 'neutral',
blocked: 'warning',
failed: 'error',
completed: 'success',
};
function taskStatusLabel(task: HermesTask) {
return task.status.replace('-', ' ');
}
function getTaskTone(task: HermesTask) {
return statusTone[task.status] ?? 'neutral';
}
function ProductMiniCard({ product }: { product: HermesProduct }) {
const healthColor = product.healthScore >= 85 ? 'bg-[var(--bl-success)]' : product.healthScore >= 70 ? 'bg-[var(--bl-warning)]' : 'bg-[var(--bl-danger)]';
return (
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-card)] p-4">
<div className="flex items-start justify-between gap-2">
<div>
<p className="font-medium text-[var(--bl-text-primary)]">{product.name}</p>
<p className="text-xs text-[var(--bl-text-secondary)]">{product.category} · {product.priority}</p>
</div>
<div className="flex items-center gap-2">
<HermesInstanceBadge instanceId={product.instanceId} />
<Badge variant={product.needsAttention ? 'warning' : 'success'}>{product.needsAttention ? 'Attention' : 'Healthy'}</Badge>
</div>
</div>
<div className="mt-3">
<div className="mb-1 flex items-center justify-between text-xs text-[var(--bl-text-secondary)]">
<span>Health</span>
<span>{product.healthScore}/100</span>
</div>
<div className="h-2 rounded-full bg-[var(--bl-surface-muted)]">
<div className={`h-2 rounded-full ${healthColor}`} style={{ width: `${product.healthScore}%` }} />
</div>
</div>
<div className="mt-3 flex flex-wrap gap-2">
{product.tags.slice(0, 3).map((tag) => (
<Badge key={tag} variant="neutral">{tag}</Badge>
))}
</div>
</div>
);
}
export default function HermesMissionControlPage() {
const { selectedInstance } = useHermesInstance();
const [telemetry, setTelemetry] = useState<HermesTelemetryState>(emptyTelemetryState);
const [telemetryError, setTelemetryError] = useState<string | null>(null);
const overview = useMemo(() => getHermesOverview(selectedInstance), [selectedInstance]);
// Per-instance roll-up cards always show both Vijay and Bheem regardless of
// the active filter — they're the "comparison" view that sits next to the
// filtered overview metrics. This satisfies the roadmap's "per-instance
// cards AND a combined roll-up" requirement.
const perInstance = useMemo(
() => HERMES_INSTANCES.map((inst) => ({ ...inst, overview: getHermesOverview(inst.id) })),
[],
);
const activeTasks = useMemo(
() => getHermesTasks({ status: 'running', instance: selectedInstance })
.concat(getHermesTasks({ status: 'blocked', instance: selectedInstance }))
.concat(getHermesTasks({ status: 'queued', instance: selectedInstance }))
.slice(0, 8),
[selectedInstance],
);
const attentionTasks = useMemo(
() => getHermesTasks({ status: 'blocked', instance: selectedInstance })
.concat(getHermesTasks({ status: 'failed', instance: selectedInstance }))
.slice(0, 8),
[selectedInstance],
);
const filteredProducts = useMemo(
() => (selectedInstance === 'all' ? hermesProducts : hermesProducts.filter((p) => p.instanceId === selectedInstance)),
[selectedInstance],
);
const filteredTasks = useMemo(
() => (selectedInstance === 'all' ? hermesTasks : hermesTasks.filter((t) => t.instanceId === selectedInstance)),
[selectedInstance],
);
const recentProducts = filteredProducts
.filter((product) => product.lastHermesActivityAt)
.sort((a, b) => new Date(b.lastHermesActivityAt!).getTime() - new Date(a.lastHermesActivityAt!).getTime())
.slice(0, 8);
const completedToday = filteredTasks.filter((task) => task.completedAt && new Date(task.completedAt).getTime() > Date.now() - 86_400_000);
const completedThisWeek = filteredTasks.filter((task) => task.completedAt && new Date(task.completedAt).getTime() > Date.now() - 7 * 86_400_000);
const failedTasks = filteredTasks.filter((task) => task.status === 'failed');
const repeatedFailures = useMemo(
() => getHermesProducts('repeated-failures', selectedInstance).slice(0, 5),
[selectedInstance],
);
const actionableProducts = filteredProducts.filter((product) => product.needsAttention).slice(0, 6);
const agentStatuses = useMemo(() => getHermesAgents(selectedInstance), [selectedInstance]);
const liveSnapshots = useMemo(() => telemetryForFilter(telemetry, selectedInstance), [telemetry, selectedInstance]);
const liveAlerts = useMemo(() => collectWatchdogAlerts(telemetry, selectedInstance).slice(0, 8), [telemetry, selectedInstance]);
const liveBackups = useMemo(() => collectBackupEntries(telemetry, selectedInstance).slice(0, 6), [telemetry, selectedInstance]);
const liveCron = useMemo(() => collectCronEntries(telemetry, selectedInstance).slice(0, 6), [telemetry, selectedInstance]);
useEffect(() => {
let active = true;
const load = async () => {
try {
const next = await loadAllHermesTelemetry();
if (!active) return;
setTelemetry(next);
setTelemetryError(null);
} catch (err) {
if (!active) return;
setTelemetryError(err instanceof Error ? err.message : String(err));
}
};
void load();
const timer = window.setInterval(load, 60_000);
return () => {
active = false;
window.clearInterval(timer);
};
}, []);
const autoActions = [
'Continue the queued execution lane for high-priority product updates.',
'Publish a weekly digest from completed and failed work.',
'Refresh the product health snapshot and attach evidence links.',
];
const founderActions = [
overview.nextRecommendedAction,
'Approve the blocked P0 work item before the release window closes.',
'Rotate the stale notification token so background alerts can resume.',
];
return (
<HermesShell
title="Hermes Mission Control"
description="A production-style command center for tracking what Hermes is doing now, what it already shipped, what is blocked, and what needs founder attention."
actions={(
<>
<Button asChild variant="secondary"><Link href="/hermes/tasks"><LayoutDashboard className="mr-2 h-4 w-4" />Task Ledger</Link></Button>
<Button asChild variant="primary"><Link href="/hermes/products"><Rocket className="mr-2 h-4 w-4" />Product Portfolio</Link></Button>
</>
)}
>
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<MetricCard label="Hermes status" value={overview.status.toUpperCase()} tone={overview.status === 'error' ? 'danger' : overview.status === 'degraded' ? 'warning' : overview.status === 'running' ? 'success' : 'default'} icon={<Bot className="h-5 w-5" />} helpText={overview.lastAction} />
<MetricCard label="Active tasks" value={overview.activeTasks} tone="info" icon={<Sparkles className="h-5 w-5" />} helpText={`${overview.upcomingJobs} queued jobs waiting to run`} />
<MetricCard label="Completed today" value={overview.completedToday} tone="success" icon={<CheckCircle2 className="h-5 w-5" />} helpText={`${overview.completedThisWeek} completed this week`} />
<MetricCard label="Founder attention" value={overview.founderAttentionCount} tone="warning" icon={<ShieldAlert className="h-5 w-5" />} helpText={overview.nextRecommendedAction} />
<MetricCard label="Failed tasks" value={overview.failedTasks} tone="danger" icon={<TriangleAlert className="h-5 w-5" />} helpText="Failure clusters are being tracked in the task ledger" />
<MetricCard label="Blocked tasks" value={overview.blockedTasks} tone="warning" icon={<OctagonAlert className="h-5 w-5" />} helpText="These items need a human decision or credential fix" />
<MetricCard label="Avg task duration" value={`${Math.round(overview.averageDurationMs / 60000)}m`} tone="info" icon={<Clock3 className="h-5 w-5" />} helpText="Average across completed tasks" />
<MetricCard label="Success rate" value={`${overview.successRate}%`} tone="success" icon={<BadgeCheck className="h-5 w-5" />} helpText={`${overview.productsTouchedRecently} products touched in the last 14 days`} />
</section>
<SectionCard
title="Per-instance roll-up"
subtitle="Side-by-side view of Vijay (root) and Bheem (uma). Shown regardless of the active instance filter so you can compare load and attention at a glance."
actions={<Badge variant="info">Always cross-instance</Badge>}
>
<div className="grid gap-4 md:grid-cols-2">
{perInstance.map(({ id, label, description, overview: ov }) => (
<div key={id} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-base font-semibold text-[var(--bl-text-primary)]">{label}</p>
<p className="text-xs text-[var(--bl-text-secondary)]">{description}</p>
</div>
<Badge variant={ov.status === 'error' ? 'error' : ov.status === 'degraded' ? 'warning' : ov.status === 'running' ? 'info' : 'neutral'}>{ov.status}</Badge>
</div>
<dl className="mt-3 grid grid-cols-2 gap-3 text-sm text-[var(--bl-text-secondary)] md:grid-cols-4">
<div><dt className="text-xs uppercase tracking-[0.18em] text-[var(--bl-text-tertiary)]">Active</dt><dd className="text-lg font-semibold text-[var(--bl-text-primary)]">{ov.activeTasks}</dd></div>
<div><dt className="text-xs uppercase tracking-[0.18em] text-[var(--bl-text-tertiary)]">Blocked</dt><dd className="text-lg font-semibold text-[var(--bl-warning)]">{ov.blockedTasks}</dd></div>
<div><dt className="text-xs uppercase tracking-[0.18em] text-[var(--bl-text-tertiary)]">Failed</dt><dd className="text-lg font-semibold text-[var(--bl-danger)]">{ov.failedTasks}</dd></div>
<div><dt className="text-xs uppercase tracking-[0.18em] text-[var(--bl-text-tertiary)]">Success %</dt><dd className="text-lg font-semibold text-[var(--bl-success)]">{ov.successRate}%</dd></div>
</dl>
</div>
))}
</div>
</SectionCard>
<HermesOpsPanel />
<SectionCard
title="Unified live alerts"
subtitle="Cross-instance alert, cron, session, and backup signals from the real Hermes telemetry endpoint."
actions={<Badge variant={telemetryError ? 'error' : 'success'}>{telemetryError ? 'Telemetry unavailable' : 'Live telemetry'}</Badge>}
>
{telemetryError ? (
<p className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4 text-sm text-[var(--bl-warning)]">
Could not load telemetry: {telemetryError}
</p>
) : (
<div className="grid gap-4 xl:grid-cols-[1.2fr_0.8fr]">
<div className="space-y-3">
{liveAlerts.length > 0 ? liveAlerts.map((alert) => (
<div key={`${alert.instanceId}-${alert.timestamp}-${alert.message}`} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<Badge variant={alert.severity === 'critical' ? 'error' : alert.severity === 'warn' ? 'warning' : 'info'}>{alert.severity}</Badge>
<HermesInstanceBadge instanceId={alert.instanceId} />
<span className="text-xs text-[var(--bl-text-tertiary)]">{fmtDate.format(new Date(alert.timestamp))}</span>
</div>
<p className="mt-2 text-sm text-[var(--bl-text-primary)]">{alert.message}</p>
</div>
<BellRing className="h-4 w-4 text-[var(--bl-text-tertiary)]" />
</div>
</div>
)) : (
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4 text-sm text-[var(--bl-text-secondary)]">
No watchdog alerts were returned for the selected instance filter.
</div>
)}
</div>
<div className="grid gap-3">
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Sessions</p>
<div className="mt-3 grid gap-2">
{liveSnapshots.map((snapshot) => (
<div key={snapshot.instanceId} className="flex items-center justify-between gap-3 text-sm">
<HermesInstanceBadge instanceId={snapshot.instanceId} />
<span className="text-[var(--bl-text-secondary)]">{snapshot.sessions.totalSessions} sessions · {snapshot.sessions.totalMessages} messages</span>
</div>
))}
</div>
</div>
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Upcoming Hermes cron</p>
<div className="mt-3 space-y-2 text-sm text-[var(--bl-text-secondary)]">
{liveCron.length > 0 ? liveCron.map((entry) => (
<div key={`${entry.instanceId}-${entry.id}`} className="flex items-center justify-between gap-3">
<span className="truncate">{entry.name}</span>
<HermesInstanceBadge instanceId={entry.instanceId} />
</div>
)) : <p>No cron entries returned.</p>}
</div>
</div>
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Recent backup commits</p>
<div className="mt-3 space-y-2 text-sm text-[var(--bl-text-secondary)]">
{liveBackups.length > 0 ? liveBackups.map((entry) => (
<div key={`${entry.instanceId}-${entry.sha}`} className="flex items-center justify-between gap-3">
<span className="truncate">{entry.subject}</span>
<HermesInstanceBadge instanceId={entry.instanceId} />
</div>
)) : <p>No backup commits returned.</p>}
</div>
</div>
</div>
</div>
)}
</SectionCard>
<div className="grid gap-6 xl:grid-cols-[1.5fr_1fr]">
<SectionCard title="Active Missions" subtitle="What Hermes is currently running or waiting on." actions={<Button asChild variant="ghost" size="sm"><Link href="/hermes/tasks">View all tasks <ArrowRight className="ml-2 h-4 w-4" /></Link></Button>}>
<div className="space-y-3">
{activeTasks.map((task) => {
const product = hermesProducts.find((item) => item.id === task.productId);
return (
<article key={task.id} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<Link href={`/hermes/tasks/${task.id}`} className="font-medium text-[var(--bl-text-primary)] hover:underline">{task.title}</Link>
<Badge variant={getTaskTone(task)}>{taskStatusLabel(task)}</Badge>
<Badge variant="neutral">{task.priority}</Badge>
<HermesInstanceBadge instanceId={task.instanceId} />
</div>
<p className="mt-1 text-sm text-[var(--bl-text-secondary)]">{product?.name ?? 'Unknown product'} · {task.assignedAgent} · {task.type}</p>
</div>
<div className="text-right text-xs text-[var(--bl-text-secondary)]">
<p>Started {fmtDate.format(new Date(task.startedAt ?? task.createdAt))}</p>
<p>{task.currentStep ?? task.nextAction}</p>
</div>
</div>
<div className="mt-3">
<div className="mb-1 flex items-center justify-between text-xs text-[var(--bl-text-secondary)]">
<span>Progress</span>
<span>{task.progressPercent}%</span>
</div>
<div className="h-2 rounded-full bg-[var(--bl-surface-card)]">
<div className="h-2 rounded-full bg-[var(--bl-accent)]" style={{ width: `${task.progressPercent}%` }} />
</div>
</div>
</article>
);
})}
</div>
</SectionCard>
<SectionCard title="Founder Attention Queue" subtitle="Items Hermes cannot safely complete without your help." actions={<Badge variant="warning">Needs decision</Badge>}>
<div className="space-y-3">
{attentionTasks.slice(0, 5).map((task) => (
<div key={task.id} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<div className="flex items-center justify-between gap-2">
<div>
<Link href={`/hermes/tasks/${task.id}`} className="font-medium text-[var(--bl-text-primary)] hover:underline">{task.title}</Link>
<p className="text-sm text-[var(--bl-text-secondary)]">{task.blockerReason ?? task.error ?? task.nextAction}</p>
</div>
<Badge variant={task.status === 'failed' ? 'error' : 'warning'}>{task.status}</Badge>
</div>
</div>
))}
{actionableProducts.slice(0, 2).map((product) => (
<div key={product.id} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<div className="flex items-center justify-between gap-2">
<div>
<p className="font-medium text-[var(--bl-text-primary)]">{product.name}</p>
<p className="text-sm text-[var(--bl-text-secondary)]">{product.description}</p>
</div>
<Badge variant="warning">Product attention</Badge>
</div>
</div>
))}
</div>
</SectionCard>
</div>
<div className="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
<SectionCard title="What Hermes did for me" subtitle="Operational summary of recent work." actions={<Badge variant="success">Evidence-backed</Badge>}>
<div className="grid gap-4 md:grid-cols-3">
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Today</p>
<p className="mt-2 text-2xl font-semibold">{completedToday.length}</p>
<p className="text-sm text-[var(--bl-text-secondary)]">Tasks completed or closed today.</p>
</div>
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">This week</p>
<p className="mt-2 text-2xl font-semibold">{completedThisWeek.length}</p>
<p className="text-sm text-[var(--bl-text-secondary)]">Shipped, repaired, or documented this week.</p>
</div>
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Last 30 days</p>
<p className="mt-2 text-2xl font-semibold">{hermesTasks.length}</p>
<p className="text-sm text-[var(--bl-text-secondary)]">Tracked execution events across the portfolio.</p>
</div>
</div>
<div className="mt-5 grid gap-3 md:grid-cols-2">
{[
'Fixed bugs and failure loops',
'Created PRs and commit-ready changes',
'Deployed services and validated health',
'Updated docs and audit summaries',
].map((item) => (
<div key={item} className="flex items-center gap-3 rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-3 text-sm text-[var(--bl-text-secondary)]">
<CheckCircle2 className="h-4 w-4 text-[var(--bl-success)]" />
{item}
</div>
))}
</div>
</SectionCard>
<SectionCard title="Next Best Actions" subtitle="Split between automation and founder decisions." actions={<Badge variant="info">Prioritized</Badge>}>
<div className="space-y-4">
<div>
<p className="mb-2 text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Hermes can do automatically</p>
<div className="space-y-2">
{autoActions.map((item) => (
<div key={item} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-3 text-sm text-[var(--bl-text-secondary)]">{item}</div>
))}
</div>
</div>
<div>
<p className="mb-2 text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Needs Saravana's decision</p>
<div className="space-y-2">
{founderActions.map((item) => (
<div key={item} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-3 text-sm text-[var(--bl-text-secondary)]">{item}</div>
))}
</div>
</div>
</div>
</SectionCard>
</div>
<div className="grid gap-6 xl:grid-cols-[1.2fr_0.8fr]">
<SectionCard title="Product Health Snapshot" subtitle="50-product portfolio view with recent activity and attention flags." actions={<Button asChild variant="ghost" size="sm"><Link href="/hermes/products">Open portfolio <ArrowRight className="ml-2 h-4 w-4" /></Link></Button>}>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{recentProducts.map((product) => (
<ProductMiniCard key={product.id} product={product} />
))}
</div>
</SectionCard>
<SectionCard title="Ecosystem Health" subtitle="Core agents and integrations are scored with recent status." actions={<Badge variant="neutral">Telemetry placeholder</Badge>}>
<div className="space-y-3">
{agentStatuses.map((agent) => (
<div key={agent.id} className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<div className="flex items-start justify-between gap-3">
<div>
<p className="font-medium text-[var(--bl-text-primary)]">{agent.name}</p>
<p className="text-sm text-[var(--bl-text-secondary)]">{agent.type} · {agent.callsToday} calls today</p>
{agent.configIssue ? <p className="mt-1 text-sm text-[var(--bl-warning)]">{agent.configIssue}</p> : null}
</div>
<Badge variant={agent.status === 'healthy' ? 'success' : agent.status === 'degraded' ? 'warning' : 'error'}>{agent.status}</Badge>
</div>
</div>
))}
</div>
</SectionCard>
</div>
<SectionCard title="Weekly digest" subtitle="A founder-friendly summary of the current operational week.">
<div className="grid gap-4 md:grid-cols-3">
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Shipped this week</p>
<p className="mt-2 text-2xl font-semibold">{completedThisWeek.length}</p>
</div>
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Failed this week</p>
<p className="mt-2 text-2xl font-semibold">{failedTasks.filter((task) => task.completedAt ? new Date(task.completedAt).getTime() > Date.now() - 7 * 86_400_000 : true).length}</p>
</div>
<div className="rounded-2xl border border-[var(--bl-border)] bg-[var(--bl-surface-muted)] p-4">
<p className="text-xs uppercase tracking-[0.2em] text-[var(--bl-text-tertiary)]">Repeated failure products</p>
<p className="mt-2 text-2xl font-semibold">{repeatedFailures.length}</p>
</div>
</div>
</SectionCard>
</HermesShell>
);
}