diff --git a/src/components/BatchDiffView.tsx b/src/components/BatchDiffView.tsx
index a7b3311..df0f3c6 100644
--- a/src/components/BatchDiffView.tsx
+++ b/src/components/BatchDiffView.tsx
@@ -5,6 +5,7 @@ import {
Plus,
type LucideIcon,
} from "lucide-react";
+import { useNavigate } from "react-router-dom";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import {
@@ -15,6 +16,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
+import { DrillableCell } from "@/components/drill/DrillableCell";
import { fmt } from "@/lib/format";
import { cn } from "@/lib/utils";
import type {
@@ -263,13 +265,28 @@ function RowIndicator({
// ---------------------------------------------------------------------------
function ClaimIdCell({ id }: { id: string }) {
+ // SP21 Phase 5 Task 5.6: each claim id is drillable to
+ // /claims?claim=ID so the operator can jump straight from a
+ // "Removed from A" or "Changed" row into the ClaimDrawer.
+ // DrillableCell handles e.stopPropagation internally — important
+ // if these rows ever get a row-level onClick. For removed claims
+ // that no longer exist in the DB, the ClaimDrawer's 404 state
+ // takes over (verified in Phase 2 testing).
+ const navigate = useNavigate();
return (
-
+ navigate(`/claims?claim=${encodeURIComponent(id)}`)
+ }
+ ariaLabel={`View claim ${id} in detail`}
>
- {id}
-
+
+ {id}
+
+
);
}
diff --git a/src/pages/BatchDiff.test.tsx b/src/pages/BatchDiff.test.tsx
index c58c612..8829605 100644
--- a/src/pages/BatchDiff.test.tsx
+++ b/src/pages/BatchDiff.test.tsx
@@ -481,8 +481,184 @@ describe("BatchDiff page", () => {
).toContain("pick a batch");
unmount();
});
+
+ it("SP21 Task 5.6: clicking an added-claim id navigates to /claims?claim=ID", async () => {
+ // Each ClaimIdCell wraps its id text with DrillableCell, whose
+ // onClick navigates to /claims?claim=ID. The MemoryRouter gives
+ // us a router context so the navigation completes; we observe
+ // it via the rendered pathname on a LocationTracker spy.
+ (api.listBatches as unknown as ReturnType).mockResolvedValue([
+ BATCH_A, BATCH_B,
+ ]);
+ (api.getBatchDiff as unknown as ReturnType).mockResolvedValue(
+ makeDiffPayload(),
+ );
+
+ const captured: { pathname: string; search: string } = {
+ pathname: "/batch-diff",
+ search: "",
+ };
+ const Tracker = () => {
+ const loc = useLocationSafe();
+ React.useEffect(() => {
+ captured.pathname = loc.pathname;
+ captured.search = loc.search;
+ }, [loc.pathname, loc.search]);
+ return null;
+ };
+
+ const { unmount } = renderIntoContainer(
+ <>
+
+
+ >,
+ [`/batch-diff?a=${BATCH_A.id}&b=${BATCH_B.id}`],
+ );
+
+ await waitFor(
+ () => !!document.querySelector('[data-testid="diff-added-row-CLM-3"]'),
+ "added row rendered",
+ );
+
+ const cell = document.querySelector(
+ '[data-testid="diff-added-row-CLM-3"] [data-testid="diff-claim-id"]',
+ );
+ expect(cell).not.toBeNull();
+ // DrillableCell wraps the id text in its own