diff --git a/packages/session-ui/src/components/apply-patch-file.test.ts b/packages/session-ui/src/components/apply-patch-file.test.ts index f7a8e77881a..47b1fec8612 100644 --- a/packages/session-ui/src/components/apply-patch-file.test.ts +++ b/packages/session-ui/src/components/apply-patch-file.test.ts @@ -3,7 +3,43 @@ import { patchFiles } from "./apply-patch-file" import { text } from "./session-diff" describe("apply patch file", () => { - test("parses patch metadata from the server", () => { + test("parses v2 patch metadata", () => { + const file = patchFiles([ + { + file: "a.ts", + status: "modified", + patch: + "Index: a.ts\n===================================================================\n--- a.ts\n+++ a.ts\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n", + additions: 1, + deletions: 1, + }, + ])[0] + + expect(file).toBeDefined() + expect(file?.filePath).toBe("a.ts") + expect(file?.relativePath).toBe("a.ts") + expect(file?.type).toBe("update") + expect(file?.view.fileDiff.name).toBe("a.ts") + expect(file?.view.fileDiff.isPartial).toBe(true) + expect(text(file.view, "deletions")).toBe("one\ntwo\n") + expect(text(file.view, "additions")).toBe("one\nthree\n") + }) + + test("maps all v2 patch statuses", () => { + expect( + patchFiles([ + { file: "added.ts", status: "added", patch: "+one", additions: 1, deletions: 0 }, + { file: "deleted.ts", status: "deleted", patch: "-one", additions: 0, deletions: 1 }, + { file: "modified.ts", status: "modified", patch: "-one\n+two", additions: 1, deletions: 1 }, + ]).map((file) => ({ file: file.filePath, type: file.type })), + ).toEqual([ + { file: "added.ts", type: "add" }, + { file: "deleted.ts", type: "delete" }, + { file: "modified.ts", type: "update" }, + ]) + }) + + test("parses legacy patch metadata", () => { const file = patchFiles([ { filePath: "/tmp/a.ts", @@ -19,8 +55,8 @@ describe("apply patch file", () => { expect(file).toBeDefined() expect(file?.view.fileDiff.name).toBe("a.ts") expect(file?.view.fileDiff.isPartial).toBe(false) - expect(text(file!.view, "deletions")).toBe("one\ntwo\n") - expect(text(file!.view, "additions")).toBe("one\nthree\n") + expect(text(file.view, "deletions")).toBe("one\ntwo\n") + expect(text(file.view, "additions")).toBe("one\nthree\n") }) test("keeps legacy before and after payloads working", () => { @@ -37,7 +73,7 @@ describe("apply patch file", () => { ])[0] expect(file).toBeDefined() - expect(text(file!.view, "deletions")).toBe("one\n") - expect(text(file!.view, "additions")).toBe("two\n") + expect(text(file.view, "deletions")).toBe("one\n") + expect(text(file.view, "additions")).toBe("two\n") }) }) diff --git a/packages/session-ui/src/components/apply-patch-file.ts b/packages/session-ui/src/components/apply-patch-file.ts index 8e0c5408260..8711c98ca94 100644 --- a/packages/session-ui/src/components/apply-patch-file.ts +++ b/packages/session-ui/src/components/apply-patch-file.ts @@ -3,9 +3,11 @@ import { normalize, type ViewDiff } from "./session-diff" type Kind = "add" | "update" | "delete" | "move" type Raw = { + file?: string filePath?: string relativePath?: string type?: Kind + status?: "added" | "deleted" | "modified" patch?: string diff?: string before?: string @@ -27,6 +29,10 @@ export type ApplyPatchFile = { function kind(value: unknown) { if (value === "add" || value === "update" || value === "delete" || value === "move") return value + if (value === "added") return "add" + if (value === "deleted") return "delete" + if (value === "modified") return "update" + return undefined } function status(type: Kind): "added" | "deleted" | "modified" { @@ -36,18 +42,19 @@ function status(type: Kind): "added" | "deleted" | "modified" { } export function patchFile(raw: unknown): ApplyPatchFile | undefined { - if (!raw || typeof raw !== "object") return + if (!raw || typeof raw !== "object") return undefined const value = raw as Raw - const type = kind(value.type) - const filePath = typeof value.filePath === "string" ? value.filePath : undefined + const type = kind(value.type) ?? kind(value.status) + const filePath = + typeof value.filePath === "string" ? value.filePath : typeof value.file === "string" ? value.file : undefined const relativePath = typeof value.relativePath === "string" ? value.relativePath : filePath const patch = typeof value.patch === "string" ? value.patch : typeof value.diff === "string" ? value.diff : undefined const before = typeof value.before === "string" ? value.before : undefined const after = typeof value.after === "string" ? value.after : undefined - if (!type || !filePath || !relativePath) return - if (!patch && before === undefined && after === undefined) return + if (!type || !filePath || !relativePath) return undefined + if (!patch && before === undefined && after === undefined) return undefined const additions = typeof value.additions === "number" ? value.additions : 0 const deletions = typeof value.deletions === "number" ? value.deletions : 0 diff --git a/packages/session-ui/src/components/part-default-open.test.ts b/packages/session-ui/src/components/part-default-open.test.ts index 2fc58154395..a122561dbec 100644 --- a/packages/session-ui/src/components/part-default-open.test.ts +++ b/packages/session-ui/src/components/part-default-open.test.ts @@ -26,6 +26,21 @@ describe("partDefaultOpen", () => { ).toBe(false) }) + test("collapses v2 patches containing only deleted files when enabled", () => { + expect( + partDefaultOpen( + tool("patch", { + files: [ + { file: "one.ts", status: "deleted" }, + { file: "two.ts", status: "deleted" }, + ], + }), + false, + true, + ), + ).toBe(false) + }) + test("keeps mixed patches expanded when enabled", () => { expect( partDefaultOpen( @@ -41,6 +56,21 @@ describe("partDefaultOpen", () => { ).toBe(true) }) + test("keeps mixed v2 patches expanded when enabled", () => { + expect( + partDefaultOpen( + tool("patch", { + files: [ + { file: "one.ts", status: "deleted" }, + { file: "two.ts", status: "modified" }, + ], + }), + false, + true, + ), + ).toBe(true) + }) + test("preserves shell defaults", () => { expect(partDefaultOpen(tool("shell", {}), true, false)).toBe(true) }) diff --git a/packages/session-ui/src/components/part-default-open.ts b/packages/session-ui/src/components/part-default-open.ts index 57a66728457..13097c483df 100644 --- a/packages/session-ui/src/components/part-default-open.ts +++ b/packages/session-ui/src/components/part-default-open.ts @@ -7,7 +7,12 @@ function deletionOnly(part: ToolPart) { 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") + return files.every( + (file) => + !!file && + typeof file === "object" && + (("type" in file && file.type === "delete") || ("status" in file && file.status === "deleted")), + ) } const filediff = metadata.filediff @@ -16,11 +21,12 @@ function deletionOnly(part: ToolPart) { 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 +export function partDefaultOpen(part: PartType, shell = false, edit = false): boolean | undefined { + if (part.type !== "tool") return undefined 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) } + return undefined }