"use client"; import { useEffect, useState, useTransition } from "react"; import { HistoryIcon, RotateCcwIcon } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetDescription, SheetTitle } from "@/components/ui/sheet"; import { roleHistoryAction, restoreRoleRevisionAction } from "@/server/actions"; /** * A role's earlier versions, readable. * * Every background edit was already kept (list_revisions / restore_revision), * but only an assistant could see them — so "what did Claude just change in my * Juggernaut role" had no answer on screen. Each version says who caused it * and which lines came and went after it, which is exactly what restoring it * would undo, so nobody restores blind. */ type Row = Awaited>["rows"][number]; function who(row: Row) { if (row.writtenBy === "mcp") return row.connectionName ? `Before ${row.connectionName}` : "Before an assistant"; return row.tool === "restore_revision" ? "Before a restore" : "Before your edit"; } export function RoleHistory({ roleId, open, onOpenChange, }: { roleId: string; open: boolean; onOpenChange: (open: boolean) => void; }) { const [rows, setRows] = useState(null); const [expanded, setExpanded] = useState(null); const [pending, startTransition] = useTransition(); useEffect(() => { if (!open) return; setRows(null); void roleHistoryAction(roleId) .then((history) => setRows(history.rows)) .catch(() => setRows([])); }, [open, roleId]); const restore = (row: Row) => { startTransition(async () => { try { const result = await restoreRoleRevisionAction(roleId, row.id); toast.success(result.summary); // The editor holds its own copy of every field while you type, so a // restore is only honest once the page has read the role again. window.location.reload(); } catch (error) { toast.error(error instanceof Error ? error.message : "Could not restore that version."); } }); }; return ( History A copy is kept before each change to this role, one per sitting, up to twenty. Restoring one takes back the lines listed under it, and is itself undoable.
{rows === null &&

Loading…

} {rows?.length === 0 && (

No earlier versions yet. One is kept the next time this role changes.

)} {rows?.map((row) => { const change = row.changedSince; const quiet = change.addedCount === 0 && change.removedCount === 0; const showing = expanded === row.id; return (
{who(row)}
{new Date(row.createdAt).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short", })}
{showing && (
    {change.added.map((line, index) => (
  • + {line}
  • ))} {change.removed.map((line, index) => (
  • − {line}
  • ))} {change.addedCount + change.removedCount > change.added.length + change.removed.length && (
  • And more.
  • )}
)}
); })}
); }