/** Shared visual language for the RCA workbench (dark theme, chip-heavy). */ import { type ReactNode } from 'react'; import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; import Paper from '@mui/material/Paper'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import type { Confidence, Finding, FindingStatus, Severity } from '../../types/rca'; export const ACCENT = '#4fc3f7'; export const BG_PANEL = '#12171d'; export const BORDER = '#232a31'; export const STATUS_COLORS: Record = { PASS: '#66bb6a', MISMATCH: '#ef5350', UNKNOWN: '#ffb74d', }; export const SEVERITY_COLORS: Record = { INFO: '#78909c', WARN: '#ffb74d', CRITICAL: '#ef5350', }; export const CONFIDENCE_COLORS: Record = { HIGH: '#66bb6a', MEDIUM: '#4fc3f7', LOW: '#90a4ae', UNKNOWN: '#ffb74d', }; export function SectionCard({ title, subtitle, children, action }: { title: string; subtitle?: string; action?: ReactNode; children: ReactNode; }) { return ( {title} {action} {subtitle ? ( {subtitle} ) : null} {children} ); } export function StatusPill({ status }: { status: string }) { const norm = status.toUpperCase() as FindingStatus; const map: Record = { PASS: { c: STATUS_COLORS.PASS, label: 'PASS' }, MISMATCH: { c: STATUS_COLORS.MISMATCH, label: 'MISMATCH' }, UNKNOWN: { c: STATUS_COLORS.UNKNOWN, label: 'UNKNOWN' }, COMPARABLE: { c: STATUS_COLORS.PASS, label: 'comparable' }, MATCH: { c: STATUS_COLORS.PASS, label: 'match' }, EXPECTED_DIFFERENCE: { c: '#78909c', label: 'expected diff' }, }; const m = map[norm] ?? map[status.toUpperCase().replace(/ /g, '_')] ?? { c: SEVERITY_COLORS.WARN, label: status, }; return ( ); } export function FindingChip({ finding, onClick }: { finding: Finding; onClick?: () => void }) { const c = STATUS_COLORS[finding.status]; const sev = SEVERITY_COLORS[finding.severity]; return ( [{finding.code}] {finding.title} {finding.detail} } arrow > {finding.status} {finding.severity !== 'INFO' ? ( {` · ${finding.severity}`} ) : null} {` · ${finding.title}`} {finding.source === 'human' ? ' · 👤' : ''} } sx={{ height: 22, fontSize: 11, bgcolor: `${c}14`, border: `1px solid ${c}44`, mr: 0.5, mb: 0.5, '.MuiChip-label': { px: 1 }, }} /> ); } export function FindingChips({ findings, onClick }: { findings: Finding[]; onClick?: (f: Finding) => void; }) { if (!findings.length) return null; return ( {findings.map((f) => ( onClick(f) : undefined} /> ))} ); } export function Explainer({ text }: { text: string }) { return ( {text} ); } /** Horizontal signed delta bar centered at zero (pp values). */ export function DeltaBar({ value, max, width = 140 }: { value: number; max: number; width?: number }) { const half = width / 2; const frac = Math.min(1, Math.abs(value) / Math.max(1e-9, max)); const w = Math.max(1.5, frac * half); const color = value >= 0 ? '#66bb6a' : '#ef5350'; return ( = 0 ? half : half - w} y={2.5} width={w} height={9} rx={2} fill={color} opacity={0.85} /> {value >= 0 ? '+' : ''} {value.toFixed(1)}pp ); } /** Simple 0..1 proportion bar for mix comparisons. */ export function ShareBar({ value, color = ACCENT, width = 90 }: { value: number; color?: string; width?: number; }) { return ( {(value * 100).toFixed(1)}% ); } export function PsiBadge({ psi }: { psi: number }) { const mag = psi < 0.02 ? 'negligible' : psi < 0.1 ? 'small' : psi < 0.25 ? 'moderate' : 'large'; const color = mag === 'large' ? '#ef5350' : mag === 'moderate' ? '#ffb74d' : mag === 'small' ? '#4fc3f7' : '#78909c'; return ( ); } export function KV({ k, v }: { k: string; v: ReactNode }) { return ( {k} {v} ); } export const tableSx = { width: '100%', borderCollapse: 'collapse' as const, fontSize: 12, '& th': { textAlign: 'left' as const, color: '#8a949e', fontWeight: 700, borderBottom: `1px solid ${BORDER}`, px: 1, py: 0.6, fontSize: 11, whiteSpace: 'nowrap' as const, }, '& td': { borderBottom: `1px solid ${BORDER}55`, px: 1, py: 0.55, verticalAlign: 'middle' as const, }, };