refactor(ui): unify operational alert banners

This commit is contained in:
Saravana Achu Mac 2026-05-09 02:15:33 -07:00
parent 3951767ab1
commit d4846b73cc
2 changed files with 39 additions and 60 deletions

View File

@ -7,7 +7,7 @@ import {
import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle';
import { fetchTradeHistory } from '../lib/tradeHistoryApi'; import { fetchTradeHistory } from '../lib/tradeHistoryApi';
import { fetchPositionsBootstrap } from '../lib/positionsApi'; import { fetchPositionsBootstrap } from '../lib/positionsApi';
import { Badge, Button, Input, Select } from '../components/ui/Primitives'; import { AlertBanner, Badge, Button, Input, Select } from '../components/ui/Primitives';
interface TradeRecord { interface TradeRecord {
@ -438,15 +438,15 @@ export const HistoryTab = ({ botState }: HistoryTabProps) => {
</div> </div>
{!hasCanonicalLifecycle && ( {!hasCanonicalLifecycle && (
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 text-amber-300 text-xs px-3 py-2"> <AlertBanner tone="warning" title="Canonical lifecycle unavailable">
Canonical lifecycle is unavailable{canonicalLoading ? ' (loading)' : ''}. History is using fallback ledger sources. History is using fallback ledger sources{canonicalLoading ? ' while loading' : ''}.
{canonicalError ? ` ${canonicalError}` : ''} {canonicalError ? ` ${canonicalError}` : ''}
</div> </AlertBanner>
)} )}
{canonicalSnapshot?.diagnostics?.truncated && ( {canonicalSnapshot?.diagnostics?.truncated && (
<div className="rounded-xl border border-red-500/30 bg-red-500/10 text-red-300 text-xs px-3 py-2"> <AlertBanner tone="error" title="Lifecycle snapshot truncated">
Canonical lifecycle snapshot is truncated ({canonicalSnapshot.diagnostics.orderRows} rows). Review a narrower scope for exact audit totals. {canonicalSnapshot.diagnostics.orderRows} rows were returned. Review a narrower scope for exact audit totals.
</div> </AlertBanner>
)} )}
{/* Performance Metrics Bar */} {/* Performance Metrics Bar */}

View File

@ -8,7 +8,7 @@ import { createRequestId } from '../../../shared/request-id.js';
import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react'; import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react';
import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle';
import { fetchPositionsBootstrap } from '../lib/positionsApi'; import { fetchPositionsBootstrap } from '../lib/positionsApi';
import { Badge, Button, Input, Select } from '../components/ui/Primitives'; import { AlertBanner, Badge, Button, Input, Select } from '../components/ui/Primitives';
interface PositionsTabProps { interface PositionsTabProps {
botState: BotState; botState: BotState;
@ -1343,66 +1343,45 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
</header> </header>
{!hasCanonicalLifecycle && ( {!hasCanonicalLifecycle && (
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 text-amber-300 text-xs px-3 py-2"> <AlertBanner tone="warning" title="Canonical lifecycle unavailable">
Canonical lifecycle is unavailable{canonicalLoading ? ' (loading)' : ''}. Truth labels are in fallback mode (DB/runtime-derived). Truth labels are in fallback mode (DB/runtime-derived){canonicalLoading ? ' while loading' : ''}.
{canonicalError ? ` ${canonicalError}` : ''} {canonicalError ? ` ${canonicalError}` : ''}
</div> </AlertBanner>
)} )}
{canonicalSnapshot?.diagnostics?.truncated && ( {canonicalSnapshot?.diagnostics?.truncated && (
<div className="rounded-xl border border-red-500/30 bg-red-500/10 text-red-300 text-xs px-3 py-2"> <AlertBanner tone="error" title="Lifecycle snapshot truncated">
Canonical lifecycle snapshot is truncated ({canonicalSnapshot.diagnostics.orderRows} rows). Narrow profile scope before operational decisions. {canonicalSnapshot.diagnostics.orderRows} rows were returned. Narrow profile scope before operational decisions.
</div> </AlertBanner>
)} )}
{/* Stale Orders Warning Banner */} {/* Stale Orders Warning Banner */}
{staleWarningOrders.length > 0 && ( {staleWarningOrders.length > 0 && (
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-4 flex items-start gap-3"> <AlertBanner tone="warning" title={`${staleWarningOrders.length} Stale Order${staleWarningOrders.length > 1 ? 's' : ''} Detected`}>
<span className="text-yellow-400 text-xl"></span> Some orders have remained in <code className="rounded bg-black/10 px-1">pending_new</code> for more than 5 minutes
<div className="flex-1"> without stronger fill or position evidence. The background sync service is re-checking their exchange status.
<h4 className="text-yellow-400 font-bold text-sm mb-1"> </AlertBanner>
{staleWarningOrders.length} Stale Order{staleWarningOrders.length > 1 ? 's' : ''} Detected )}
</h4>
<p className="text-gray-400 text-xs"> {positionMismatches.length > 0 && (
Some orders have remained in <code className="bg-black/30 px-1 rounded">pending_new</code> for more than 5 minutes <AlertBanner tone="error" title={`Lifecycle Mismatch Diagnostics (${positionMismatches.length})`}>
without stronger fill or position evidence. The background sync service is re-checking their exchange status. <p className="mb-3">
</p> One or more open positions do not have a clean entry-order lineage by profile and trade ID.
</div> </p>
</div> <div className="space-y-2">
{positionMismatches.map((issue) => (
<div key={issue.id} className="flex flex-wrap items-center gap-2 rounded-lg border border-[var(--border)] bg-[var(--card)] px-2 py-1 text-[10px]">
<Badge variant={issue.severity === 'critical' ? 'danger' : 'warning'} size="sm">
{issue.severity}
</Badge>
<span className="font-semibold text-[var(--foreground)]">{issue.profileName}</span>
<span className="font-mono text-[var(--foreground)]">{issue.symbol}</span>
<span className="font-mono text-[var(--muted-foreground)]">{issue.tradeId}</span>
<span className="text-[var(--muted-foreground)]">{issue.reason}</span>
</div>
))}
</div>
</AlertBanner>
)} )}
{positionMismatches.length > 0 && (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg p-4">
<div className="flex items-start gap-3">
<AlertTriangle className="text-red-400 mt-0.5" size={18} />
<div className="flex-1 space-y-3">
<div>
<h4 className="text-red-400 font-bold text-sm">
Lifecycle Mismatch Diagnostics ({positionMismatches.length})
</h4>
<p className="text-gray-400 text-xs">
One or more open positions do not have a clean entry-order lineage by profile and trade ID.
</p>
</div>
<div className="space-y-2">
{positionMismatches.map((issue) => (
<div key={issue.id} className="flex flex-wrap items-center gap-2 text-[10px] border border-white/10 rounded px-2 py-1">
<span className={`px-1.5 py-0.5 rounded font-bold uppercase tracking-wider ${issue.severity === 'critical'
? 'bg-red-500/20 text-red-300 border border-red-500/20'
: 'bg-yellow-500/20 text-yellow-300 border border-yellow-500/20'
}`}>
{issue.severity}
</span>
<span className="text-gray-300 font-semibold">{issue.profileName}</span>
<span className="text-white font-mono">{issue.symbol}</span>
<span className="text-zinc-500 font-mono">{issue.tradeId}</span>
<span className="text-zinc-400">{issue.reason}</span>
</div>
))}
</div>
</div>
</div>
</div>
)}
<section className="positions-section"> <section className="positions-section">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">