import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link } from "@/lib/router"; import { casesApi, type CaseDocumentRevision } from "@/api/cases"; import { queryKeys } from "@/lib/queryKeys"; import { buildLineDiff, type DiffRow } from "@/lib/line-diff"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { MarkdownBody } from "@/components/MarkdownBody"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { cn, relativeTime } from "@/lib/utils"; import { Diff } from "lucide-react"; /** Author + via-issue attribution line for a revision. */ function RevisionByline({ revision }: { revision: CaseDocumentRevision }) { const author = revision.actorAgentName ?? (revision.createdByUserId ? "User" : "System"); return ( {author} {revision.issue && ( <> · via e.stopPropagation()} title={revision.issue.title} > {revision.issue.identifier} )} ); } function getRevisionLabel(revision: CaseDocumentRevision) { const actor = revision.actorAgentName ?? (revision.createdByUserId ? "board" : "system"); return `rev ${revision.revisionNumber} - ${relativeTime(revision.createdAt)} - ${actor}`; } function CaseDocumentDiffModal({ documentKey, revisions, latestRevisionNumber, open, onOpenChange, }: { documentKey: string; revisions: CaseDocumentRevision[]; latestRevisionNumber: number; open: boolean; onOpenChange: (open: boolean) => void; }) { const [leftRevisionId, setLeftRevisionId] = useState(null); const [rightRevisionId, setRightRevisionId] = useState(null); const effectiveLeftId = leftRevisionId ?? revisions.find( (revision) => revision.revisionNumber === latestRevisionNumber - 1, )?.id ?? null; const effectiveRightId = rightRevisionId ?? revisions.find( (revision) => revision.revisionNumber === latestRevisionNumber, )?.id ?? null; const leftRevision = revisions.find((revision) => revision.id === effectiveLeftId) ?? null; const rightRevision = revisions.find((revision) => revision.id === effectiveRightId) ?? null; const diffRows = buildLineDiff(leftRevision?.body ?? "", rightRevision?.body ?? ""); const lineClassesByKind: Record = { context: "bg-transparent", removed: "bg-red-500/10 text-red-900 dark:text-red-100", added: "bg-green-500/10 text-green-900 dark:text-green-100", }; const markerByKind: Record = { context: " ", removed: "-", added: "+", }; return (
Diff - {documentKey}
Old
New
{!leftRevision || !rightRevision ? (
Select two revisions to compare.
) : leftRevision.id === rightRevision.id ? (
Both sides are the same revision.
) : (
Old New Content
{diffRows.map((row, index) => (
{row.oldLineNumber ?? ""} {row.newLineNumber ?? ""} {markerByKind[row.kind]}
                    {row.text.length > 0 ? row.text : " "}
                  
))}
)}
); } /** * Revision rail (P4 §2): read-only body document with a per-revision list. The * newest revision is selected by default; picking another swaps the rendered * body. No editing UI in v1. */ export function CaseRevisionRail({ caseIdentifier, documentKey = "body", }: { caseIdentifier: string; documentKey?: string; }) { const revisionsQuery = useQuery({ queryKey: queryKeys.cases.revisions(caseIdentifier, documentKey), queryFn: () => casesApi.listRevisions(caseIdentifier, documentKey), }); const revisions = revisionsQuery.data?.revisions ?? []; const [selectedId, setSelectedId] = useState(null); const [diffOpen, setDiffOpen] = useState(false); // Default to the latest revision once loaded; keep a valid selection if the // list changes underneath us. useEffect(() => { if (revisions.length === 0) return; if (!selectedId || !revisions.some((r) => r.id === selectedId)) { setSelectedId(revisions[0]!.id); } }, [revisions, selectedId]); if (revisionsQuery.isLoading) { return

Loading revisions…

; } if (revisionsQuery.isError) { return

Could not load revisions.

; } if (revisions.length === 0) { return

No revisions yet.

; } const selected = revisions.find((r) => r.id === selectedId) ?? revisions[0]!; const latestRevisionNumber = revisionsQuery.data?.document.latestRevisionNumber ?? selected.revisionNumber; return (
rev {selected.revisionNumber}
{selected.body ? ( {selected.body} ) : (

This revision has no body.

)}
{revisions.length > 1 ? ( ) : null}
); }