From ae726cdc45317a6bc6d2a77066c191dd674d0aa2 Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 29 Apr 2026 15:49:06 +0200 Subject: [PATCH] fix(vscode): show apply_patch diffs before approval (#9691) * fix(vscode): show apply patch permission diffs * fix(vscode): avoid caching permission diff patches * chore: update kilo-vscode visual regression baselines --------- Co-authored-by: github-actions[bot] --- .changeset/permission-apply-patch-diffs.md | 5 ++ ...ission-dock-apply-patch-chromium-linux.png | 3 + .../permission-dock-edit-chromium-linux.png | 4 +- .../tests/unit/permission-diff-utils.test.ts | 50 +++++++++++++++ .../src/components/chat/PermissionDiff.tsx | 52 ++++++++++++---- .../src/components/chat/PermissionDock.tsx | 14 ++--- .../components/chat/permission-diff-utils.ts | 62 +++++++++++++++++++ .../src/stories/composite.stories.tsx | 60 +++++++++++++++++- .../webview-ui/src/styles/permission-dock.css | 16 +++++ .../src/types/messages/permissions.ts | 11 ++++ 10 files changed, 256 insertions(+), 21 deletions(-) create mode 100644 .changeset/permission-apply-patch-diffs.md create mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-apply-patch-chromium-linux.png create mode 100644 packages/kilo-vscode/tests/unit/permission-diff-utils.test.ts create mode 100644 packages/kilo-vscode/webview-ui/src/components/chat/permission-diff-utils.ts diff --git a/.changeset/permission-apply-patch-diffs.md b/.changeset/permission-apply-patch-diffs.md new file mode 100644 index 0000000000..2d115ae7c2 --- /dev/null +++ b/.changeset/permission-apply-patch-diffs.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show apply_patch diffs in the permission prompt before approval. diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-apply-patch-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-apply-patch-chromium-linux.png new file mode 100644 index 0000000000..a17e69d621 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-apply-patch-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa0924c6679cbff6e816dd86371a2d483afb7f0840824503ef4ba325c25d6c5 +size 26282 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-edit-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-edit-chromium-linux.png index 2862d25afc..cdca3764fb 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-edit-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-edit-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36d371115e898752d566502cae5815c9a9719be64d9b5bd9f6612c0ed8a1d114 -size 15546 +oid sha256:f28a551185ec9d7607d02295803b155e0f7bac378fcefdf0996c6ca51c0afad6 +size 21892 diff --git a/packages/kilo-vscode/tests/unit/permission-diff-utils.test.ts b/packages/kilo-vscode/tests/unit/permission-diff-utils.test.ts new file mode 100644 index 0000000000..dfb1abdaee --- /dev/null +++ b/packages/kilo-vscode/tests/unit/permission-diff-utils.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "bun:test" +import { permissionDiffs } from "../../webview-ui/src/components/chat/permission-diff-utils" +import type { PermissionRequest } from "../../webview-ui/src/types/messages" + +function perm(args: PermissionRequest["args"]): PermissionRequest { + return { + id: "perm", + sessionID: "ses", + toolName: "edit", + patterns: ["*"], + always: ["*"], + args, + } +} + +describe("permissionDiffs", () => { + test("uses filediff metadata for edit and write permissions", () => { + const diffs = permissionDiffs( + perm({ filediff: { file: "src/app.ts", patch: "patch", additions: 1, deletions: 0 } }), + ) + + expect(diffs).toEqual([{ file: "src/app.ts", patch: "patch", additions: 1, deletions: 0 }]) + }) + + test("uses apply_patch files metadata", () => { + const diffs = permissionDiffs( + perm({ + files: [ + { relativePath: "src/a.ts", type: "update", patch: "a", additions: 1, deletions: 1 }, + { relativePath: "src/b.ts", type: "add", patch: "b", additions: 2, deletions: 0 }, + ], + }), + ) + + expect(diffs).toEqual([ + { file: "src/a.ts", patch: "a", additions: 1, deletions: 1 }, + { file: "src/b.ts", patch: "b", additions: 2, deletions: 0 }, + ]) + }) + + test("falls back to raw diff metadata", () => { + const diffs = permissionDiffs(perm({ filepath: "src/a.ts", diff: "Index: src/a.ts" })) + + expect(diffs).toEqual([{ file: "src/a.ts", patch: "Index: src/a.ts", additions: 0, deletions: 0 }]) + }) + + test("returns no diffs for command-only permissions", () => { + expect(permissionDiffs(perm({ command: "git status" }))).toEqual([]) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDiff.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDiff.tsx index 4cb7448fa1..fb836489fe 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDiff.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDiff.tsx @@ -1,9 +1,9 @@ -import { type Component, createMemo } from "solid-js" +import { Show, type Component, createMemo } from "solid-js" import { Diff } from "@kilocode/kilo-ui/diff" import { DiffChanges } from "@kilocode/kilo-ui/diff-changes" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Tooltip } from "@kilocode/kilo-ui/tooltip" -import { normalize, text } from "@kilocode/kilo-ui/session-diff" +import { parsePatch } from "diff" import type { PermissionFileDiff } from "../../types/messages" import { useVSCode } from "../../context/vscode" @@ -11,6 +11,29 @@ interface PermissionDiffProps { filediff: PermissionFileDiff } +function patchText(patch: string) { + const parsed = parsePatch(patch)[0] + if (!parsed) return { before: "", after: "" } + + const before: string[] = [] + const after: string[] = [] + for (const hunk of parsed.hunks) { + for (const line of hunk.lines) { + if (line.startsWith("-")) { + before.push(line.slice(1)) + continue + } + if (line.startsWith("+")) { + after.push(line.slice(1)) + continue + } + before.push(line.slice(1)) + after.push(line.slice(1)) + } + } + return { before: before.join("\n"), after: after.join("\n") } +} + export const PermissionDiff: Component = (props) => { const vscode = useVSCode() const filename = createMemo(() => { @@ -27,13 +50,15 @@ export const PermissionDiff: Component = (props) => { const resolved = createMemo(() => { const fd = props.filediff if (fd.before !== undefined || fd.after !== undefined) return { before: fd.before ?? "", after: fd.after ?? "" } - if (fd.patch) { - const view = normalize(fd) - return { before: text(view, "deletions"), after: text(view, "additions") } - } + if (fd.patch) return patchText(fd.patch) return { before: "", after: "" } }) + const empty = createMemo(() => { + const diff = resolved() + return diff.before === "" && diff.after === "" + }) + const openInTab = () => { const { before, after } = resolved() vscode.postMessage({ @@ -77,11 +102,16 @@ export const PermissionDiff: Component = (props) => {
- + Diff preview unavailable for this file.
} + > + + ) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx index bf20c69fde..616ef563ec 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -20,6 +20,7 @@ import { useConfig } from "../../context/config" import { describePatterns, resolveLabel, savedRuleStates, type RuleDecision } from "./permission-dock-utils" import { PermissionCommand } from "./PermissionCommand" import { PermissionDiff } from "./PermissionDiff" +import { permissionDiffs } from "./permission-diff-utils" import type { PermissionRequest } from "../../types/messages" let rulesExpandedPreference = false @@ -47,12 +48,7 @@ export const PermissionDock: Component<{ command() ? null : describePatterns(props.request.toolName, props.request.patterns, language.t), ) - const filediff = () => { - if (props.request.toolName !== "edit" && props.request.toolName !== "write") return null - const fd = props.request.args?.filediff - if (!fd || typeof fd !== "object") return null - return fd as NonNullable - } + const diffs = createMemo(() => permissionDiffs(props.request)) // Pre-populate toggle states from existing config rules so previously // approved/denied patterns show their saved state immediately. @@ -247,7 +243,11 @@ export const PermissionDock: Component<{ ) })()} - {(fd) => } + 0}> +
+ {(diff) => } +
+