fix(app): collapse deletion-only edit parts (#40536)

Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-08-05 00:23:06 +00:00
committed by GitHub
parent f0afb6750e
commit 98dd65cd60
3 changed files with 94 additions and 9 deletions
@@ -61,6 +61,7 @@ import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
import { AnimatedCountList } from "./tool-count-summary"
import { ToolStatusTitle } from "./tool-status-title"
import { patchFiles } from "./apply-patch-file"
import { partDefaultOpen } from "./part-default-open"
import { animate } from "motion"
import { attached, inline, kind, typeLabel } from "./message-file"
import { readPartText } from "./message-part-text"
@@ -718,15 +719,7 @@ export function renderable(part: PartType, showReasoningSummaries = true) {
return !!PART_MAPPING[part.type]
}
function toolDefaultOpen(tool: string, shell = false, edit = false) {
if (tool === "bash" || tool === "shell") return shell
if (tool === "edit" || tool === "write" || tool === "patch" || tool === "apply_patch") return edit
}
export function partDefaultOpen(part: PartType, shell = false, edit = false) {
if (part.type !== "tool") return
return toolDefaultOpen(part.tool, shell, edit)
}
export { partDefaultOpen } from "./part-default-open"
export function AssistantParts(props: {
messages: AssistantMessage[]
@@ -0,0 +1,66 @@
import { describe, expect, test } from "bun:test"
import type { Part as PartType } from "@opencode-ai/sdk/v2"
import { partDefaultOpen } from "./part-default-open"
describe("partDefaultOpen", () => {
test("keeps edited files expanded when enabled", () => {
expect(partDefaultOpen(tool("edit", { filediff: { additions: 1, deletions: 1 } }), false, true)).toBe(true)
})
test("collapses deletion-only edits when enabled", () => {
expect(partDefaultOpen(tool("edit", { filediff: { additions: 0, deletions: 1_200 } }), false, true)).toBe(false)
})
test("collapses patches containing only deleted files when enabled", () => {
expect(
partDefaultOpen(
tool("apply_patch", {
files: [
{ filePath: "one.ts", type: "delete" },
{ filePath: "two.ts", type: "delete" },
],
}),
false,
true,
),
).toBe(false)
})
test("keeps mixed patches expanded when enabled", () => {
expect(
partDefaultOpen(
tool("apply_patch", {
files: [
{ filePath: "one.ts", type: "delete" },
{ filePath: "two.ts", type: "update" },
],
}),
false,
true,
),
).toBe(true)
})
test("preserves shell defaults", () => {
expect(partDefaultOpen(tool("shell", {}), true, false)).toBe(true)
})
})
function tool(name: string, metadata: Record<string, unknown>): PartType {
return {
id: `part_${name}`,
sessionID: "session",
messageID: "message",
type: "tool",
callID: `call_${name}`,
tool: name,
state: {
status: "completed",
input: {},
output: "",
title: name,
metadata,
time: { start: 0, end: 1 },
},
}
}
@@ -0,0 +1,26 @@
import type { Part as PartType, ToolPart } from "@opencode-ai/sdk/v2"
function deletionOnly(part: ToolPart) {
if (!("metadata" in part.state)) return false
const metadata = part.state.metadata
if (!metadata) return false
const files = metadata.files
if (Array.isArray(files) && files.length > 0) {
return files.every((file) => !!file && typeof file === "object" && "type" in file && file.type === "delete")
}
const filediff = metadata.filediff
if (!filediff || typeof filediff !== "object") return false
if (!("additions" in filediff) || !("deletions" in filediff)) return false
return filediff.additions === 0 && typeof filediff.deletions === "number" && filediff.deletions > 0
}
export function partDefaultOpen(part: PartType, shell = false, edit = false) {
if (part.type !== "tool") return
if (part.tool === "bash" || part.tool === "shell") return shell
if (part.tool === "edit" || part.tool === "write" || part.tool === "patch" || part.tool === "apply_patch") {
if (!edit) return false
return !deletionOnly(part)
}
}