feat(release): v0.2.0 — batch 837 export, ClaimCard, theme tokens

Backend:

- New POST /api/batches/{id}/export-837: regenerate X12 837 files
  for a list of claim_ids into a ZIP using HCPF file naming standards,
  with a unique interchange/group control number per export. Wire
  the clearhouse Loop 1000A (NM1*41 + PER) and per-payer receiver
  (NM1*40) blocks so the serializer no longer falls back to
  CYCLONE / RECEIVER placeholders.
- /api/parse-837 and /api/parse-835 now surface the server-side
  batch_id in both JSON and NDJSON response shapes so the frontend
  can hit batch-scoped endpoints without an extra listBatches
  round-trip.
- Filename helpers and the 837 serializer updated to match the new
  HCPF envelope; tests cover batch export, parse batch_id, and the
  serializer's control-number uniqueness guarantee.

Frontend:
- New shared components: ClaimCard, ClaimCard837, DominantKpiCard,
  EditorialNote, ExportBar, TickerTape, and a charts/ set
  (BarChart, HBarChart, SegmentedBar, AgingBars).
- New useBatchExport hook driving ExportBar's download flow against
  the new endpoint.
- ClaimDrawer, Lane, and Layout migrated from raw CSS-variable
  colors to Tailwind theme tokens (bg-card, text-foreground,
  border/60, etc.) for consistency with the rest of the instrument
  chrome; the active tab indicator gains a subtle accent glow.
- Upload, Inbox, Batches, BatchDiff, Reconciliation, and Acks pages
  reworked to compose the new shared components and consume the new
  batch-scoped API surface (notably ExportBar wired into Batches).

Tooling / Docs:
- Add audit-uiux.mjs and a docs/goodclaim.x12 sample fixture.
- Update ClaimDrawer testids and add coverage for the new
  components and the useBatchExport hook.

Rolls up into the v0.2.0 release tag.
This commit is contained in:
Tyler
2026-06-22 11:01:58 -06:00
parent 35298907bc
commit 9bca4b608a
54 changed files with 6224 additions and 3871 deletions
+14 -12
View File
@@ -141,12 +141,14 @@ export function ClaimDrawer({
// Right-anchored side panel. We override the Dialog primitive's
// default centered positioning (`left-1/2 top-1/2 -translate-x-1/2
// -translate-y-1/2`) by re-asserting `right-0 top-0 translate-x-0
// translate-y-0`. `h-full` + `rounded-none` + the left border
// translate-y-0`. `h-full` + `rounded-none` + the left hairline
// give it the visual identity of a drawer rather than a modal.
// `bg-card` matches the rest of the dark instrument chrome; the
// outer shadow is a soft directional falloff to the left.
// `aria-describedby={undefined}` suppresses the Radix warning
// about a missing description — the drawer content is its own
// description and there's nothing useful to point at.
className="fixed right-0 top-0 h-full w-full max-w-2xl translate-x-0 translate-y-0 rounded-none border-l border-[color:var(--m-border-heavy)]/60 bg-[color:var(--m-surface)] p-0 shadow-[-24px_0_60px_-12px_rgba(0,0,0,0.6)]"
className="fixed right-0 top-0 h-full w-full max-w-2xl translate-x-0 translate-y-0 rounded-none border-l border-border/60 bg-card p-0 shadow-[-24px_0_60px_-12px_rgba(0,0,0,0.6)]"
data-testid="claim-drawer"
aria-describedby={undefined}
>
@@ -167,7 +169,7 @@ export function ClaimDrawer({
>
<ClaimDrawerHeader claim={data} onClose={onClose} />
<div
className="flex gap-0 px-6 border-b border-[color:var(--surface-line)]/40"
className="flex gap-0 px-6 border-b border-border/40"
data-testid="claim-drawer-tabs"
>
<button
@@ -176,15 +178,15 @@ export function ClaimDrawer({
className={cn(
"relative px-3 py-2.5 text-[10.5px] font-semibold uppercase tracking-[0.18em] transition-colors",
activeTab === "details"
? "text-[color:var(--surface-ink)]"
: "text-[color:var(--surface-ink-3)] hover:text-[color:var(--surface-ink-2)]"
? "text-foreground"
: "text-muted-foreground hover:text-foreground"
)}
data-testid="tab-button-details"
data-active={activeTab === "details" ? "true" : "false"}
>
Details
{activeTab === "details" ? (
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-[color:var(--accent)]" />
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-accent shadow-[0_0_8px_hsl(var(--accent)/0.6)]" />
) : null}
</button>
<button
@@ -193,29 +195,29 @@ export function ClaimDrawer({
className={cn(
"relative px-3 py-2.5 text-[10.5px] font-semibold uppercase tracking-[0.18em] transition-colors",
activeTab === "line-reconciliation"
? "text-[color:var(--surface-ink)]"
: "text-[color:var(--surface-ink-3)] hover:text-[color:var(--surface-ink-2)]"
? "text-foreground"
: "text-muted-foreground hover:text-foreground"
)}
data-testid="tab-button-line-reconciliation"
data-active={activeTab === "line-reconciliation" ? "true" : "false"}
>
Line Reconciliation
{activeTab === "line-reconciliation" ? (
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-[color:var(--accent)]" />
<span className="absolute inset-x-0 -bottom-px h-[2px] bg-accent shadow-[0_0_8px_hsl(var(--accent)/0.6)]" />
) : null}
</button>
</div>
{activeTab === "line-reconciliation" ? (
lr.loading ? (
<p
className="px-6 py-4 text-sm text-[color:var(--m-ink-tertiary)]"
className="px-6 py-4 text-sm text-muted-foreground"
data-testid="line-reconciliation-loading"
>
Loading line reconciliation
</p>
) : lr.error ? (
<p
className="px-6 py-4 text-sm text-[color:var(--m-ink-tertiary)]"
className="px-6 py-4 text-sm text-muted-foreground"
data-testid="line-reconciliation-error"
>
Failed to load line reconciliation.
@@ -224,7 +226,7 @@ export function ClaimDrawer({
<LineReconciliationTab data={lr.data} />
) : null
) : (
<div className="flex flex-col divide-y divide-[color:var(--surface-line)]/12">
<div className="flex flex-col divide-y divide-border/40">
<ValidationPanel validation={data.validation} />
<ServiceLinesTable
serviceLines={data.serviceLines}
@@ -32,21 +32,21 @@ export function ClaimDrawerError({ kind, onRetry, onClose }: ClaimDrawerErrorPro
return (
<div
className="flex flex-col items-start gap-4 p-6 bg-[color:var(--m-surface)] text-[color:var(--m-ink-primary)]"
className="flex flex-col items-start gap-4 p-6 bg-card text-foreground"
role="alert"
data-testid={`claim-drawer-error-${kind}`}
>
<div className="flex items-center gap-2">
<Icon
className="h-5 w-5 text-[color:var(--m-error)]"
className="h-5 w-5 text-destructive"
strokeWidth={1.75}
aria-hidden
/>
<span className="eyebrow text-[color:var(--m-error)]">
<span className="eyebrow text-destructive">
{eyebrow}
</span>
</div>
<p className="text-sm text-[color:var(--m-ink-secondary)] max-w-sm">{message}</p>
<p className="text-sm text-muted-foreground max-w-sm">{message}</p>
<div className="flex items-center gap-2">
{kind === "network" && onRetry ? (
<Button variant="outline" size="sm" onClick={onRetry} data-testid="error-retry">
@@ -10,7 +10,7 @@ import { Skeleton } from "@/components/ui/skeleton";
export function ClaimDrawerSkeleton() {
return (
<div
className="flex flex-col gap-6 p-6 bg-[color:var(--m-surface)] text-[color:var(--m-ink-primary)]"
className="flex flex-col gap-6 p-6 bg-card text-foreground"
data-testid="claim-drawer-skeleton"
aria-busy="true"
>
+5 -5
View File
@@ -35,7 +35,7 @@ export function DiagnosesList({ diagnoses }: DiagnosesListProps) {
<section className="flex flex-col gap-3 px-6 py-4">
<h3
data-testid="section-label"
className="eyebrow text-[color:var(--m-ink-tertiary)]"
className="eyebrow text-muted-foreground"
>
Diagnoses ({diagnoses.length})
</h3>
@@ -43,7 +43,7 @@ export function DiagnosesList({ diagnoses }: DiagnosesListProps) {
{diagnoses.length === 0 ? (
<p
data-testid="diagnoses-empty"
className="text-sm text-[color:var(--m-ink-tertiary)]"
className="text-sm text-muted-foreground"
>
No diagnoses
</p>
@@ -58,17 +58,17 @@ export function DiagnosesList({ diagnoses }: DiagnosesListProps) {
className="flex items-baseline gap-3 text-sm"
>
<span
className="mono text-[color:var(--m-ink-primary)] font-medium"
className="mono text-foreground font-medium"
>
{d.qualifier ? `${d.qualifier} ${d.code}` : d.code}
</span>
{desc ? (
<>
<span
className="h-px w-3 bg-[color:var(--m-border-heavy)]/30"
className="h-px w-3 bg-border/60"
aria-hidden
/>
<span className="text-[color:var(--m-ink-secondary)] italic">
<span className="text-muted-foreground italic">
{desc}
</span>
</>
@@ -31,26 +31,26 @@ export function LineReconciliationTab({
>
<div className="flex gap-5 font-mono tabular-nums text-sm">
<span data-testid="billed-total" className="flex flex-col gap-0.5">
<span className="text-[color:var(--m-ink-tertiary)] eyebrow">
<span className="text-muted-foreground eyebrow">
Billed
</span>
<span className="text-[color:var(--m-ink-primary)] font-semibold">
<span className="text-foreground font-semibold">
{fmt.usdPrecise(Number(summary.billedTotal))}
</span>
</span>
<span data-testid="paid-total" className="flex flex-col gap-0.5">
<span className="text-[color:var(--m-ink-tertiary)] eyebrow">
<span className="text-muted-foreground eyebrow">
Paid
</span>
<span className="text-[color:var(--m-ink-primary)] font-semibold">
<span className="text-foreground font-semibold">
{fmt.usdPrecise(Number(summary.paidTotal))}
</span>
</span>
<span data-testid="adjustments-total" className="flex flex-col gap-0.5">
<span className="text-[color:var(--m-ink-tertiary)] eyebrow">
<span className="text-muted-foreground eyebrow">
Adjustments
</span>
<span className="text-[color:var(--m-ink-primary)] font-semibold">
<span className="text-foreground font-semibold">
{fmt.usdPrecise(Number(summary.adjustmentTotal))}
</span>
</span>
@@ -70,7 +70,7 @@ export function LineReconciliationTab({
<div className="grid grid-cols-2 gap-4">
<div data-testid="left-column">
<h3 className="eyebrow text-[color:var(--m-ink-tertiary)] mb-2">
<h3 className="eyebrow text-muted-foreground mb-2">
Billed (837 SV1)
</h3>
{leftRows.map((row) => (
@@ -78,7 +78,7 @@ export function LineReconciliationTab({
))}
</div>
<div data-testid="right-column">
<h3 className="eyebrow text-[color:var(--m-ink-tertiary)] mb-2">
<h3 className="eyebrow text-muted-foreground mb-2">
Adjudicated (835 SVC)
</h3>
{rightRows.map((row) => (
@@ -114,7 +114,7 @@ function LineCard({
return (
<div
className="flex flex-col gap-1 py-2 px-3 mb-2 rounded-r-md transition-colors hover:bg-[color:var(--m-ink-tertiary)]/5"
className="flex flex-col gap-1 py-2 px-3 mb-2 rounded-r-md transition-colors hover:bg-muted/40"
style={{
borderLeft: `3px solid ${accentColor}`,
background: "rgba(255,255,255,0.02)",
@@ -123,9 +123,9 @@ function LineCard({
>
<div className="flex items-baseline justify-between">
<div className="font-mono text-sm">
<span className="font-semibold text-[color:var(--m-ink-primary)]">{procCode}</span>
<span className="font-semibold text-foreground">{procCode}</span>
{lineNumber !== null ? (
<span className="text-[color:var(--m-ink-tertiary)] ml-2">
<span className="text-muted-foreground ml-2">
#{lineNumber}
</span>
) : null}
@@ -146,16 +146,16 @@ function LineCard({
</span>
) : null}
</div>
<div className="font-mono tabular-nums text-xs text-[color:var(--m-ink-primary)] font-semibold">
<div className="font-mono tabular-nums text-xs text-foreground font-semibold">
{side === "left" && cl ? <span>{fmt.usdPrecise(Number(cl.charge))}</span> : null}
{side === "right" && svc ? <span>{fmt.usdPrecise(Number(svc.payment))}</span> : null}
</div>
</div>
{row.adjustments.length > 0 ? (
<ul className="text-xs font-mono text-[color:var(--m-ink-tertiary)] mt-1 space-y-0.5">
<ul className="text-xs font-mono text-muted-foreground mt-1 space-y-0.5">
{row.adjustments.map((a, i) => (
<li key={i}>
<span className="text-[color:var(--m-ink-secondary)]">{a.groupCode}-{a.reasonCode}</span>
<span className="text-foreground/80">{a.groupCode}-{a.reasonCode}</span>
<span className="ml-2 tabular-nums">{fmt.usdPrecise(Number(a.amount))}</span>
</li>
))}
@@ -56,20 +56,20 @@ export function MatchedRemitCard({ matchedRemittance }: MatchedRemitCardProps) {
>
<h3
data-testid="section-label"
className="eyebrow text-[color:var(--m-ink-tertiary)]"
className="eyebrow text-muted-foreground"
>
Matched Remittance
</h3>
<div
data-testid="matched-remit-card-inner"
className="flex flex-col gap-4 rounded-lg border border-[color:var(--m-border-heavy)]/30 bg-[color:var(--m-surface)]/60 p-4 sm:flex-row sm:items-start sm:justify-between"
className="flex flex-col gap-4 rounded-lg border border-border/60 bg-muted/30 p-4 sm:flex-row sm:items-start sm:justify-between"
>
{/* Left: remit id + status badge + received date */}
<div className="flex min-w-0 flex-col gap-2">
<span
data-testid="matched-remit-id"
className="mono text-sm text-[color:var(--m-ink-primary)]"
className="mono text-sm text-foreground"
>
{id}
</span>
@@ -83,7 +83,7 @@ export function MatchedRemitCard({ matchedRemittance }: MatchedRemitCardProps) {
</Badge>
<span
data-testid="matched-remit-received"
className="text-xs text-[color:var(--m-ink-tertiary)]"
className="text-xs text-muted-foreground"
>
Received {fmt.date(receivedAt)}
</span>
@@ -94,7 +94,7 @@ export function MatchedRemitCard({ matchedRemittance }: MatchedRemitCardProps) {
<div className="flex flex-col items-start gap-2 sm:items-end">
<span
data-testid="matched-remit-total-paid"
className="display text-3xl text-[color:var(--m-ink-primary)] tabular-nums"
className="display text-3xl text-foreground tabular-nums"
>
{fmt.usdPrecise(totalPaid)}
</span>
@@ -103,7 +103,7 @@ export function MatchedRemitCard({ matchedRemittance }: MatchedRemitCardProps) {
size="sm"
onClick={handleViewRemittance}
data-testid="view-remittance-link"
className="gap-1 text-[color:var(--m-ink-secondary)] hover:text-[color:var(--m-ink-primary)]"
className="gap-1 text-muted-foreground hover:text-foreground"
>
View remittance
<ArrowRight
+7 -7
View File
@@ -28,7 +28,7 @@ function AddressBlock({ address }: { address: AddressLike }) {
const a = address as ClaimDetailAddress;
return (
<div
className="mt-1 border-t border-dashed border-[color:var(--m-border-heavy)]/30 pt-2 text-xs leading-relaxed text-[color:var(--m-ink-secondary)]"
className="mt-1 border-t border-dashed border-border/60 pt-2 text-xs leading-relaxed text-muted-foreground"
data-testid="party-address"
>
<div>{a.line1}</div>
@@ -63,9 +63,9 @@ function PartyCard({
return (
<div
data-testid={testId}
className="flex flex-col gap-2 rounded-lg border border-[color:var(--m-border-heavy)]/30 bg-[color:var(--m-surface)]/60 px-4 py-3 transition-colors hover:border-[color:var(--m-border-heavy)]/50"
className="flex flex-col gap-2 rounded-lg border border-border/60 bg-muted/30 px-4 py-3 transition-colors hover:border-border hover:bg-muted/40"
>
<span className="eyebrow text-[color:var(--m-ink-tertiary)]">
<span className="eyebrow text-muted-foreground">
{label}
</span>
{onNameClick ? (
@@ -74,17 +74,17 @@ function PartyCard({
onClick={onNameClick}
data-testid={`${testId}-name-drill`}
aria-label={`Drill into ${label.toLowerCase()} ${name}`}
className="display text-lg text-[color:var(--m-ink-primary)] text-left cursor-pointer drillable rounded-sm px-0 -mx-0"
className="display text-lg text-foreground text-left cursor-pointer drillable rounded-sm px-0 -mx-0"
>
{name}
</button>
) : (
<div className="display text-lg text-[color:var(--m-ink-primary)]">
<div className="display text-lg text-foreground">
{name}
</div>
)}
<div
className="mono text-[12.5px] text-[color:var(--m-ink-secondary)] tabular-nums"
className="mono text-[12.5px] text-muted-foreground tabular-nums"
>
{identity}
</div>
@@ -120,7 +120,7 @@ export function PartiesGrid({ parties }: PartiesGridProps) {
>
<h3
data-testid="section-label"
className="eyebrow text-[color:var(--m-ink-tertiary)]"
className="eyebrow text-muted-foreground"
>
Parties
</h3>
@@ -33,17 +33,17 @@ export function RawSegmentsPanel({ rawSegments }: RawSegmentsPanelProps) {
>
<h3
data-testid="section-label"
className="eyebrow text-[color:var(--m-ink-tertiary)]"
className="eyebrow text-muted-foreground"
>
Raw Segments ({rawSegments.length})
</h3>
<details className="group rounded-lg border border-[color:var(--m-border-heavy)]/30 bg-[color:var(--m-surface)]/40 overflow-hidden transition-colors hover:border-[color:var(--m-border-heavy)]/50">
<details className="group rounded-lg border border-border/60 bg-muted/30 overflow-hidden transition-colors hover:border-border">
<summary
className="cursor-pointer select-none px-4 py-2.5 text-sm text-[color:var(--m-ink-secondary)] hover:text-[color:var(--m-ink-primary)] transition-colors [&::-webkit-details-marker]:hidden flex items-center gap-2"
className="cursor-pointer select-none px-4 py-2.5 text-sm text-muted-foreground hover:text-foreground transition-colors [&::-webkit-details-marker]:hidden flex items-center gap-2"
data-testid="raw-segments-summary"
>
<span className="inline-block h-1.5 w-1.5 rounded-full bg-[color:var(--m-ink-tertiary)] group-open:bg-[color:var(--m-accent)] transition-colors" />
<span className="inline-block h-1.5 w-1.5 rounded-full bg-muted-foreground group-open:bg-accent transition-colors" />
{rawSegments.length === 0
? `Show raw segments`
: `Show ${rawSegments.length} raw segment${rawSegments.length === 1 ? "" : "s"}`}
@@ -52,14 +52,14 @@ export function RawSegmentsPanel({ rawSegments }: RawSegmentsPanelProps) {
{rawSegments.length === 0 ? (
<p
data-testid="raw-segments-empty"
className="px-4 pb-3 text-sm text-[color:var(--m-ink-tertiary)]"
className="px-4 pb-3 text-sm text-muted-foreground"
>
No segments
</p>
) : (
<pre
data-testid="raw-segments-pre"
className="overflow-x-auto whitespace-pre-wrap border-t border-[color:var(--m-border-heavy)]/20 px-4 pb-3 pt-3 text-[12px] leading-relaxed text-[color:var(--m-ink-primary)] font-mono"
className="overflow-x-auto whitespace-pre-wrap border-t border-border/40 px-4 pb-3 pt-3 text-[12px] leading-relaxed text-foreground font-mono"
style={{ fontFamily: "var(--m-font-mono)" }}
>
{rawSegments.map((segment, i) => (
@@ -49,7 +49,7 @@ export function ServiceLinesTable({
<section className="flex flex-col gap-3 px-6 py-4">
<h3
data-testid="section-label"
className="eyebrow text-[color:var(--m-ink-tertiary)]"
className="eyebrow text-muted-foreground"
>
Service Lines ({serviceLines.length})
</h3>
@@ -57,7 +57,7 @@ export function ServiceLinesTable({
{serviceLines.length === 0 ? (
<p
data-testid="service-lines-empty"
className="text-sm text-[color:var(--m-ink-tertiary)]"
className="text-sm text-muted-foreground"
>
No service lines
</p>
@@ -84,34 +84,34 @@ export function ServiceLinesTable({
data-line-number={line.lineNumber}
className="row-hover"
>
<TableCell className="font-mono text-[color:var(--m-ink-secondary)]">
<TableCell className="font-mono text-muted-foreground">
{line.lineNumber}
</TableCell>
<TableCell className="font-mono text-[color:var(--m-ink-primary)]">
<TableCell className="font-mono text-foreground">
{formatProcedure(line)}
</TableCell>
<TableCell
className="text-right font-mono text-base tabular-nums text-[color:var(--m-ink-primary)] font-semibold"
className="text-right font-mono text-base tabular-nums text-foreground font-semibold"
style={{ fontFamily: "var(--m-font-mono)" }}
>
{fmt.usdPrecise(line.charge)}
</TableCell>
<TableCell className="font-mono text-[color:var(--m-ink-secondary)]">
<TableCell className="font-mono text-muted-foreground">
{formatUnits(line)}
</TableCell>
<TableCell className="font-mono text-[color:var(--m-ink-secondary)]">
<TableCell className="font-mono text-muted-foreground">
{line.serviceDate ? fmt.date(line.serviceDate) : ""}
</TableCell>
<TableCell
data-testid="paid-cell"
className="text-right font-mono tabular-nums text-[color:var(--m-ink-primary)]"
className="text-right font-mono tabular-nums text-foreground"
style={{ fontFamily: "var(--m-font-mono)" }}
>
{formatMoneyOrDash(lr?.paid ?? null)}
</TableCell>
<TableCell
data-testid="adjustments-cell"
className="text-right font-mono tabular-nums text-[color:var(--m-ink-secondary)]"
className="text-right font-mono tabular-nums text-muted-foreground"
style={{ fontFamily: "var(--m-font-mono)" }}
>
{formatMoneyOrDash(lr?.adjustmentsSum ?? null)}
@@ -64,7 +64,7 @@ export function StateHistoryTimeline({ history }: StateHistoryTimelineProps) {
>
<h3
data-testid="section-label"
className="eyebrow text-[color:var(--m-ink-tertiary)]"
className="eyebrow text-muted-foreground"
>
State History ({history.length})
</h3>
@@ -72,14 +72,14 @@ export function StateHistoryTimeline({ history }: StateHistoryTimelineProps) {
{history.length === 0 ? (
<p
data-testid="state-history-empty"
className="text-sm text-[color:var(--m-ink-tertiary)]"
className="text-sm text-muted-foreground"
>
No history events
</p>
) : (
<ol
data-testid="state-history-timeline"
className="relative ml-2 flex flex-col gap-3.5 border-l border-dashed border-[color:var(--m-border-heavy)]/40 pl-6"
className="relative ml-2 flex flex-col gap-3.5 border-l border-dashed border-border/40 pl-6"
>
{history.map((event, i) => (
<li
@@ -91,18 +91,18 @@ export function StateHistoryTimeline({ history }: StateHistoryTimelineProps) {
<span
data-testid="state-history-dot"
aria-hidden
className={`absolute -left-[29px] top-1.5 h-2.5 w-2.5 rounded-full ring-4 ring-[color:var(--m-surface)] ${dotColorFor(event.kind)}`}
className={`absolute -left-[29px] top-1.5 h-2.5 w-2.5 rounded-full ring-4 ring-card ${dotColorFor(event.kind)}`}
/>
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
<span
data-testid="state-history-kind"
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-[color:var(--m-ink-primary)]"
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-foreground"
>
{kindLabel(event.kind)}
</span>
<span
data-testid="state-history-ts"
className="mono text-xs text-[color:var(--m-ink-secondary)] tabular-nums"
className="mono text-xs text-muted-foreground tabular-nums"
>
{fmt.date(event.ts)} · {fmt.time(event.ts)}
</span>
@@ -110,7 +110,7 @@ export function StateHistoryTimeline({ history }: StateHistoryTimelineProps) {
{event.remittanceId ? (
<span
data-testid="history-remit-id"
className="mono text-xs text-[color:var(--m-ink-tertiary)]"
className="mono text-xs text-muted-foreground"
>
Remit {event.remittanceId}
</span>
+13 -13
View File
@@ -63,12 +63,12 @@ function IssueGroup({
onClick={() => openPeek({ kind: "rule", rule })}
data-testid={`${testId}-rule-drill`}
aria-label={`Drill into rule ${rule}`}
className="mono text-[12px] font-semibold tracking-tight text-[color:var(--m-ink-primary)] cursor-pointer drillable rounded-sm px-0 -mx-0"
className="mono text-[12px] font-semibold tracking-tight text-foreground cursor-pointer drillable rounded-sm px-0 -mx-0"
>
{rule}
</button>
<span
className="inline-flex items-center rounded-full bg-[color:var(--m-ink-tertiary)]/15 px-1.5 py-0.5 text-[10px] font-medium text-[color:var(--m-ink-secondary)] tabular-nums"
className="inline-flex items-center rounded-full bg-muted-foreground/15 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground tabular-nums"
data-testid={`${testId}-count`}
>
{issues.length}
@@ -78,7 +78,7 @@ function IssueGroup({
{issues.map((issue, idx) => (
<li
key={`${rule}-${idx}`}
className="flex items-start gap-2 text-[13px] leading-snug text-[color:var(--m-ink-secondary)]"
className="flex items-start gap-2 text-[13px] leading-snug text-muted-foreground"
data-testid={`${testId}-message`}
>
<Icon
@@ -121,14 +121,14 @@ export function ValidationPanel({ validation }: ValidationPanelProps) {
className="flex items-center gap-2 px-6 py-3"
>
<CheckCircle2
className="h-4 w-4 text-[color:var(--m-success)]"
className="h-4 w-4 text-[hsl(var(--success))]"
strokeWidth={1.75}
aria-hidden
/>
<span className="text-[13px] font-medium text-[color:var(--m-ink-primary)]">
<span className="text-[13px] font-medium text-foreground">
All checks passed
</span>
<span className="ml-auto inline-flex items-center rounded-full bg-[color:var(--m-success)]/12 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-[color:var(--m-success)]">
<span className="ml-auto inline-flex items-center rounded-full bg-[hsl(var(--success)/0.12)] px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-[hsl(var(--success))]">
Valid
</span>
</section>
@@ -140,10 +140,10 @@ export function ValidationPanel({ validation }: ValidationPanelProps) {
return (
<section
className="flex flex-col gap-4 px-6 py-4 bg-[color:var(--m-surface)]"
className="flex flex-col gap-4 px-6 py-4"
data-testid="validation-panel"
>
<span className="eyebrow text-[color:var(--m-ink-tertiary)]">
<span className="eyebrow text-muted-foreground">
Validation
</span>
@@ -152,15 +152,15 @@ export function ValidationPanel({ validation }: ValidationPanelProps) {
data-testid="validation-errors"
data-rule-group="errors"
role="alert"
className="flex flex-col gap-3 border-l-2 border-[color:var(--m-error)] bg-[hsl(var(--destructive)/0.06)] px-3.5 py-3"
className="flex flex-col gap-3 border-l-2 border-[color:var(--m-error)] bg-[hsl(var(--destructive)/0.08)] px-3.5 py-3"
>
<div className="flex items-center gap-2">
<AlertCircle
className="h-3.5 w-3.5 text-[color:var(--m-error)]"
className="h-3.5 w-3.5 text-destructive"
strokeWidth={1.75}
aria-hidden
/>
<span className="text-[10.5px] font-semibold uppercase tracking-[0.14em] text-[color:var(--m-error)]">
<span className="text-[10.5px] font-semibold uppercase tracking-[0.14em] text-destructive">
Errors ({validation.errors.length})
</span>
</div>
@@ -185,11 +185,11 @@ export function ValidationPanel({ validation }: ValidationPanelProps) {
>
<div className="flex items-center gap-2">
<AlertTriangle
className="h-3.5 w-3.5 text-[color:var(--m-warning)]"
className="h-3.5 w-3.5 text-[hsl(var(--warning))]"
strokeWidth={1.75}
aria-hidden
/>
<span className="text-[10.5px] font-semibold uppercase tracking-[0.14em] text-[color:var(--m-warning)]">
<span className="text-[10.5px] font-semibold uppercase tracking-[0.14em] text-[hsl(var(--warning))]">
Warnings ({validation.warnings.length})
</span>
</div>