diff --git a/.changeset/open-write-diff-viewer.md b/.changeset/open-write-diff-viewer.md
new file mode 100644
index 00000000000..c9ce4d18efc
--- /dev/null
+++ b/.changeset/open-write-diff-viewer.md
@@ -0,0 +1,5 @@
+---
+"kilo-code": patch
+---
+
+Show an "Open in Diff Viewer" icon on Write tool calls (matching the Edit tool), so the changes can be inspected in a full diff tab.
diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css
index 942e77c45d9..bb6f99c515a 100644
--- a/packages/kilo-ui/src/components/message-part.css
+++ b/packages/kilo-ui/src/components/message-part.css
@@ -40,12 +40,14 @@
}
}
-/* Hover-revealed "Open in Diff Viewer" icon button in the edit-tool header */
-[data-component="tool-trigger"] [data-slot="basic-tool-tool-info"]:has([data-component="edit-trigger"]) {
+/* Hover-revealed "Open in Diff Viewer" icon button in edit/write tool headers */
+[data-component="tool-trigger"] [data-slot="basic-tool-tool-info"]:has([data-component="edit-trigger"]),
+[data-component="tool-trigger"] [data-slot="basic-tool-tool-info"]:has([data-component="write-trigger"]) {
flex: 1 1 auto;
}
-[data-component="edit-trigger"] {
+[data-component="edit-trigger"],
+[data-component="write-trigger"] {
display: flex;
align-items: center;
gap: 8px;
@@ -58,7 +60,7 @@
overflow: hidden;
}
- [data-slot="edit-trigger-actions"] {
+ [data-slot="tool-trigger-actions"] {
flex-shrink: 0;
margin-left: auto;
display: inline-flex;
@@ -68,8 +70,8 @@
}
}
-[data-component="tool-trigger"]:hover [data-slot="edit-trigger-actions"],
-[data-slot="edit-trigger-actions"]:focus-within {
+[data-component="tool-trigger"]:hover [data-slot="tool-trigger-actions"],
+[data-slot="tool-trigger-actions"]:focus-within {
opacity: 1;
}
diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx
index 9ff461092d7..aeb10d3dd43 100644
--- a/packages/kilo-ui/src/components/message-part.tsx
+++ b/packages/kilo-ui/src/components/message-part.tsx
@@ -2185,7 +2185,7 @@ ToolRegistry.register({
-
+
!!data.openDiff && !!props.input.filePath && (!!view() || !!props.input.content)
+ const canOpenFile = () => !!data.openFile && !!props.input.filePath
+
+ const openDiff = () => {
+ if (!data.openDiff || !props.input.filePath) return
+ const v = view()
+ data.openDiff({
+ file: props.metadata?.filediff?.file || props.input.filePath,
+ before: v?.before ?? "",
+ after: v?.after ?? props.input.content ?? "",
+ additions: props.metadata?.filediff?.additions ?? 0,
+ deletions: props.metadata?.filediff?.deletions ?? 0,
+ })
+ }
const handleFileClick = (e: MouseEvent) => {
e.stopPropagation()
- if (data.openDiff && view()) {
- data.openDiff({
- file: props.metadata?.filediff?.file || props.input.filePath,
- before: view()!.before,
- after: view()!.after,
- additions: props.metadata?.filediff?.additions ?? 0,
- deletions: props.metadata?.filediff?.deletions ?? 0,
- })
+ if (canOpenDiff()) {
+ openDiff()
return
}
- if (!data.openFile || !props.input.filePath) return
- data.openFile(props.input.filePath)
+ if (canOpenFile()) {
+ data.openFile!(props.input.filePath!)
+ }
+ }
+
+ const handleOpenDiffClick = (e: MouseEvent) => {
+ e.stopPropagation()
+ openDiff()
}
return (
@@ -2286,16 +2300,26 @@ ToolRegistry.register({
path={props.input.filePath?.includes("/") ? getDirectory(props.input.filePath!) : undefined}
changes={props.metadata.filediff}
animate={reveal()}
- onClick={
- (view() && data.openDiff) || (data.openFile && props.input.filePath)
- ? handleFileClick
- : undefined
- }
+ onClick={canOpenDiff() || canOpenFile() ? handleFileClick : undefined}
/>
)}
+
+
+
+ e.preventDefault()}
+ onClick={handleOpenDiffClick}
+ aria-label={i18n.t("ui.messagePart.openInDiffViewer")}
+ />
+
+
+
}
>
diff --git a/packages/kilo-ui/src/stories/message-part.stories.tsx b/packages/kilo-ui/src/stories/message-part.stories.tsx
index 8c256487b12..5e1912f3aff 100644
--- a/packages/kilo-ui/src/stories/message-part.stories.tsx
+++ b/packages/kilo-ui/src/stories/message-part.stories.tsx
@@ -196,6 +196,35 @@ const editCompletedPart: ToolPart = {
},
}
+// Completed write tool that creates a new file — exercises canOpenDiff() via
+// `props.input.content` so the "Open in Diff Viewer" icon button renders even
+// when metadata.filediff has no diff payload.
+const writeCompletedPart: ToolPart = {
+ id: "part-tool-write-done",
+ sessionID: SESSION_ID,
+ messageID: ASST_MSG_ID,
+ type: "tool",
+ callID: "call-write-done",
+ tool: "write",
+ state: {
+ status: "completed",
+ input: {
+ filePath: "src/greet.ts",
+ content: "export function greet(name: string) {\n return `Hello, ${name}!`\n}\n",
+ },
+ output: "File written successfully",
+ title: "Write file",
+ metadata: {
+ filediff: {
+ file: "src/greet.ts",
+ additions: 3,
+ deletions: 0,
+ },
+ },
+ time: { start: now - 4000, end: now - 3500 },
+ },
+}
+
// --- Reasoning part ---
const reasoningPart: ReasoningPart = {
@@ -243,6 +272,7 @@ const mockDataBash = createMockData([bashCompleted])
const mockDataContextGroup = createMockData([completedToolPart, grepCompleted, globCompleted, textPart])
// Completed edit tool with filediff — exercises the "Open in Diff Viewer" button path
const mockDataEdit = createMockData([editCompletedPart])
+const mockDataWrite = createMockData([writeCompletedPart])
function AllProviders(props: { children: any; data?: MockData; onOpenDiff?: () => void }) {
return (
@@ -402,7 +432,18 @@ export const WithEditToolOpenDiffAction: Story = {
name: "WithEditTool (open-diff action visible)",
render: () => (
{}}>
-
+
+
+
+ ),
+}
+
+// --- Completed write tool with content → "Open in Diff Viewer" icon visible ---
+export const WithWriteToolOpenDiffAction: Story = {
+ name: "WithWriteTool (open-diff action visible)",
+ render: () => (
+ {}}>
+
),