Files
pig/apps/web/src/components/SourcedValue.tsx
T
karti e12d27edd1
CI / verify (push) Successful in 3m32s
Polish every product workflow across desktop and mobile
Reframe each screen around the decisions compute brokers make: sellable capacity, full-cost margin, pipeline movement, contract deadlines, evidence review, staged imports, and controlled agent access. Group the shell by operating domain, strengthen mobile navigation and sheets, add responsive record treatments, and make loading, error, empty, readiness, and retry states explicit.

The visual audit exposed sortable table targets and an unnamed file input only after exercising the rendered app, so this commit also pins those accessibility decisions at their actual interaction boundaries. Manrope is self-hosted as a single Latin variable subset to keep the stronger hierarchy without shipping unused font payloads.
2026-08-13 05:34:23 -07:00

133 lines
5.0 KiB
TypeScript

import type { FactBand, FactStatus } from '@pig/core';
import { Clock3, ExternalLink, Link2, ScanSearch } from 'lucide-react';
import type { ReactNode } from 'react';
import { Badge } from '@/components/ui';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
export interface SourcedFact {
id: string;
field: string;
value: string;
score: string | number;
band: FactBand;
status: FactStatus;
evidence: Record<string, unknown> | null;
sourceUrl: string | null;
method: string | null;
observedAt: string;
}
interface SourcedValueProps {
value: ReactNode;
fact: SourcedFact;
className?: string;
}
const BAND_TONE = {
verified: 'positive',
probable: 'info',
possible: 'warning',
} as const;
function safeSourceUrl(value: string | null): string | null {
if (!value) return null;
try {
const url = new URL(value);
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : null;
} catch {
return null;
}
}
function evidenceSummary(evidence: Record<string, unknown> | null): string | null {
if (!evidence) return null;
for (const key of ['excerpt', 'quote', 'summary', 'snippet', 'reason']) {
const value = evidence[key];
if (typeof value === 'string' && value.trim()) return value.trim();
}
const firstText = Object.values(evidence).find(
(value): value is string => typeof value === 'string' && Boolean(value.trim()),
);
return firstText?.trim() ?? null;
}
function humanise(value: string): string {
return value.replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
}
export function SourcedValue({ value, fact, className }: SourcedValueProps) {
const sourceUrl = safeSourceUrl(fact.sourceUrl);
const summary = evidenceSummary(fact.evidence);
const score = Number(fact.score);
const confidence = Number.isFinite(score) ? `${Math.round(score * 100)}%` : 'Not scored';
const observedDate = new Date(fact.observedAt);
const observedLabel = Number.isNaN(observedDate.getTime()) ? 'Observation date unavailable' : observedDate.toLocaleDateString(undefined, { dateStyle: 'medium' });
const sourceHost = sourceUrl ? new URL(sourceUrl).hostname.replace(/^www\./, '') : null;
return (
<span className={cn('inline-flex min-w-0 items-center gap-1.5', className)}>
<span className="min-w-0 break-words font-medium text-fg">{value}</span>
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="tap -my-2 inline-flex size-11 shrink-0 items-center justify-center rounded-md text-accent-fg hover:bg-accent-subtle"
aria-label={`View ${fact.band} evidence for ${fact.field}`}
>
<Link2 className="size-3.5" aria-hidden />
</button>
</PopoverTrigger>
<PopoverContent
align="start"
className="w-[min(22rem,calc(100vw-2rem))] border-border bg-surface text-fg"
>
<div className="flex flex-col gap-3">
<div className="flex min-w-0 items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-xs font-medium uppercase tracking-wide text-muted">
Source evidence · {humanise(fact.field)}
</p>
<p className="mt-1 break-words text-sm font-medium">{fact.value}</p>
</div>
<Badge tone={BAND_TONE[fact.band]}>{confidence}</Badge>
</div>
<Separator />
<div className="flex gap-2 text-sm">
<ScanSearch className="mt-0.5 size-4 shrink-0 text-muted" aria-hidden />
<div className="min-w-0">
<p className="font-medium">Evidence</p>
<p className="mt-0.5 break-words text-muted">
{summary ?? 'No evidence excerpt was recorded.'}
</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-2 text-xs text-muted">
<Badge tone="neutral">{humanise(fact.band)}</Badge>
<Badge tone="neutral">{humanise(fact.status)}</Badge>
{fact.method ? <span>via {humanise(fact.method)}</span> : null}
</div>
<div className="flex items-center gap-2 text-xs text-muted"><Clock3 className="size-3.5 shrink-0" aria-hidden /><span>Observed <time dateTime={fact.observedAt}>{observedLabel}</time></span></div>
{sourceUrl ? (
<a
href={sourceUrl}
target="_blank"
rel="noreferrer"
className="inline-flex min-h-11 items-center gap-2 break-all text-sm font-medium text-accent-fg hover:underline"
>
Open source{sourceHost ? ` · ${sourceHost}` : ''}
<ExternalLink className="size-3.5 shrink-0" aria-hidden />
</a>
) : null}
</div>
</PopoverContent>
</Popover>
</span>
);
}