fix(activity): tolerate unknown activity kinds in ActivityFeed

The live-tail stream (now reachable through the new Vite /api proxy)
started delivering activity rows with kind="manual_match", a kind the
ActivityFeed kindConfig map didn't know about. The map lookup returned
undefined, and reading .icon on it threw — taking the whole Activity
Log page down.

Two fixes:

1. Add "manual_match" to the ActivityKind union in src/types/index.ts
   and to the kindConfig map in src/components/ActivityFeed.tsx (with
   a Wrench icon + neutral tone), so the kind is properly typed and
   rendered.

2. Add a FALLBACK_KIND guard so any future backend-emitted kind that
   arrives before the frontend is updated no longer crashes the page.
   kindConfig stays exhaustive for known kinds, so TypeScript still
   catches missing entries at compile time.

Also adds src/components/ActivityFeed.test.tsx with three regression
tests (empty state, multi-item render, unknown-kind safety).
This commit is contained in:
Tyler
2026-06-20 17:46:00 -06:00
parent f4a7cd99b3
commit 6ee87174d3
4 changed files with 95 additions and 2 deletions
+19 -1
View File
@@ -1,9 +1,11 @@
import {
Banknote,
CheckCircle2,
Circle,
FileText,
Pill,
UserPlus,
Wrench,
XCircle,
type LucideIcon,
} from "lucide-react";
@@ -21,6 +23,22 @@ const kindConfig: Record<
claim_denied: { icon: XCircle, tone: "text-destructive" },
remit_received: { icon: Pill, tone: "text-[hsl(var(--warning))]" },
provider_added: { icon: UserPlus, tone: "text-foreground" },
// `manual_match` is emitted by the backend when an operator manually
// pairs a remit to a claim from the Reconciliation page — see
// `cyclone.store` `record_manual_match`. Wrench is the closest fit;
// we keep it neutral-tone so it doesn't read as a status.
manual_match: { icon: Wrench, tone: "text-muted-foreground" },
};
// Defensive fallback: if the backend ever emits a kind this UI doesn't
// know about (e.g. a new kind landed server-side before the frontend
// picked it up), render a neutral icon instead of crashing the whole
// Activity Log page. The kindConfig map above stays exhaustive for
// known kinds so TypeScript still catches missing entries at compile
// time; this only protects the runtime path.
const FALLBACK_KIND: { icon: LucideIcon; tone: string } = {
icon: Circle,
tone: "text-muted-foreground",
};
export function ActivityFeed({
@@ -40,7 +58,7 @@ export function ActivityFeed({
return (
<ul className="divide-y divide-border/40">
{items.map((a) => {
const cfg = kindConfig[a.kind];
const cfg = kindConfig[a.kind] ?? FALLBACK_KIND;
const Icon = cfg.icon;
return (
<li