Change Summary
The card Cursor shows after a coding turn: file count, a Review action, a few rows with +/- stats, and “Show N more”. Pair it with WorkedFor for the elapsed-time badge.
Installation
npx shadcn@latest add miskibin/chat-components/change-summaryAlso installs collapsible, context-menu, file-icon.
Usage
import {
ChangeSummary,
WorkedFor,
} from "@/components/ui/change-summary"
export function AfterTurn() {
return (
<>
<WorkedFor seconds={72} />
<ChangeSummary
files={[
{ path: "lib/reducer.ts", additions: 4, deletions: 2 },
{ path: "lib/reducer.test.ts", additions: 11 },
]}
onAction={() => openReview()}
/>
</>
)
}Examples
Derive files from tools
fileChangesFromTools reads Edit / Write / ApplyPatch rows (path + output stats or a unified diff) so you do not have to assemble the list by hand. Message already does this when changes is omitted.
import { fileChangesFromTools } from "@/components/ui/change-summary"
<ChangeSummary files={fileChangesFromTools(tools)} />Right-click a row
fileActions puts a context menu on every row. The card only shows the menu; what “open” or “reveal” means on this machine is the host's call, which is why each action is handed the path and nothing else.
Preview count and styling
previewCount decides how many rows show before the “Show N more” toggle, onAction turns the header label into a button, and onFileClick turns every row into one. Rows, stats, and the header each have a slot, so the card can go monospace or take a tint without a fork; the glyph is a FileIcon, whose brand colours are fixed on purpose, while each row also tags itself with data-kind — code, style, data, text — so opt into colour from the outside when you want it.
Notes
API reference
ChangeSummary
files*ChangeSummaryFile[]- Each item is
{ path, additions?, deletions? }. The filename is derived from the path. titleReact.ReactNode- Defaults to “N Files Changed”.
actionLabelReact.ReactNode = "Review"- Right-aligned header label.
onAction() => void- Makes the header label a button. Omit it to keep Review as plain text.
onFileClick(file: ChangeSummaryFile) => void- Turns every row into a button — open the file in a
FilePreviewpanel. Rows stay plain text without it. fileActionsFileActionItem[]- Right-click menu on every row — open in an editor, reveal in the file manager, copy the path. Each action gets the row's path back. Keep the array stable: the rows are memoized.
previewCountnumber = 4- How many rows to show before “Show N more”.
FileActionItem
id / label / iconstring / string / React.ReactNode- One menu entry.
onSelect*(path: string) => void- Called with the path of the file the menu was opened on.
destructive / separatorBeforeboolean- Red text for the entry; a rule above it.
FileContextMenu
path*string- Wraps any trigger in the same menu the rows use —
FilePreviewandMessageMarkdownshare it. With noactionsthe child renders untouched.
WorkedFor
seconds*number- Elapsed time for the finished turn.
Data slots
Every part below is addressable from a parent, so you can restyle internals without forking the component.
change-summarychange-summary-headerchange-summary-actionchange-summary-listchange-summary-restchange-summary-filechange-summary-file-iconchange-summary-statschange-summary-morefile-context-menuworked-for