From fa326f21e33a8cb1a81b5e32a7735e96a66bef8a Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 7 May 2026 11:36:45 +0200 Subject: [PATCH] Add permission prompt shortcuts (#9991) * feat(vscode): add permission prompt shortcuts * chore: update kilo-vscode visual regression baselines --------- Co-authored-by: github-actions[bot] --- .changeset/permission-prompt-shortcuts.md | 5 + ...permission-dock-heredoc-chromium-linux.png | 4 +- .../src/components/chat/PermissionDock.tsx | 94 ++++++++++++------- 3 files changed, 69 insertions(+), 34 deletions(-) create mode 100644 .changeset/permission-prompt-shortcuts.md diff --git a/.changeset/permission-prompt-shortcuts.md b/.changeset/permission-prompt-shortcuts.md new file mode 100644 index 00000000000..88901f97af0 --- /dev/null +++ b/.changeset/permission-prompt-shortcuts.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Support accepting command permission prompts with Enter and denying them with Escape. diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png index d7d21c928f4..889daa6e724 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-heredoc-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:483c04840175e7678f0cfd1eba377ec199239404acd567fd7778367f4d2cb46c -size 21219 +oid sha256:a1ca383c2a5fa9d6f477430d7c63c7330e13e2667bf717906d3c29c215f163c5 +size 21564 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 616ef563ece..9e9da21929e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -9,7 +9,7 @@ * The command buttons (Deny / Run) control the current command. */ -import { Component, For, Show, createEffect, createMemo, createSignal } from "solid-js" +import { Component, For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { DockPrompt } from "@kilocode/kilo-ui/dock-prompt" import { Icon } from "@kilocode/kilo-ui/icon" @@ -111,48 +111,78 @@ export const PermissionDock: Component<{ const focusPrompt = () => requestAnimationFrame(() => window.dispatchEvent(new Event("focusPrompt"))) + const submit = (response: "once" | "reject") => { + if (props.responding) return + const { approved, denied } = collectRules() + props.onDecide(response, approved, denied) + focusPrompt() + } + + const element = (e: KeyboardEvent) => (e.target instanceof Element ? e.target : undefined) + + const control = (target: Element | undefined) => + !!target?.closest( + "button, input, select, textarea, a[href], [contenteditable='true'], [role='button'], [role='menu'], [role='menuitem'], [role='listbox'], [role='option'], [role='combobox'], [role='textbox']", + ) + + const plain = (e: KeyboardEvent) => + e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey && !e.isComposing + + const skip = (e: KeyboardEvent, target: Element | undefined) => { + const local = !!target?.closest("[data-component='permission-shortcuts']") + const prompt = !!target?.closest("textarea.prompt-input") + const modal = !!target?.closest("[data-component='dialog'], [data-component='dropdown-menu-content']") + if (local) return e.key === "Enter" + if (modal) return true + if (prompt) return false + return control(target) + } + + const handle = (e: KeyboardEvent, response: "once" | "reject") => { + e.preventDefault() + e.stopPropagation() + submit(response) + } + const onRoot = (e: KeyboardEvent) => { - const tag = (e.target as HTMLElement).tagName - - // Escape always denies — even from focused buttons — and stopPropagation - // prevents ChatView's global Escape handler from calling session.abort(). if (e.key === "Escape") { - e.preventDefault() - e.stopPropagation() - if (props.responding) return - const { approved, denied } = collectRules() - props.onDecide("reject", approved, denied) - focusPrompt() - return - } - - // Enter approves, but only when focus is on the dock wrapper itself. - // Skip buttons, inputs, and textareas so Enter activates the focused - // control (e.g. toggle/expand) instead of approving the permission. - if (tag === "BUTTON" || tag === "INPUT" || tag === "TEXTAREA") return - if (e.key === "Enter") { - e.preventDefault() - e.stopPropagation() - if (props.responding) return - const { approved, denied } = collectRules() - props.onDecide("once", approved, denied) - focusPrompt() + handle(e, "reject") return } } - // Keep keyboard shortcuts when the webview already has focus, but do not - // steal focus from the editor, terminal, or other VS Code surfaces. + const onKey = (e: KeyboardEvent) => { + if (!document.hasFocus()) return + if (e.defaultPrevented) return + if (root.getClientRects().length === 0) return + + const target = element(e) + + // Preserve normal Enter behavior for controls inside the permission card, + // while allowing the shortcut when the prompt input still has focus. + if (skip(e, target)) return + + if (e.key === "Escape") { + handle(e, "reject") + return + } + + if (plain(e)) { + handle(e, "once") + return + } + } + + // Listen only while this permission is rendered. This keeps shortcuts working + // when the prompt input owns focus without forcing focus onto the dock. createEffect(() => { void props.request.id - requestAnimationFrame(() => { - if (!document.hasFocus()) return - root?.focus() - }) + document.addEventListener("keydown", onKey, true) + onCleanup(() => document.removeEventListener("keydown", onKey, true)) }) return ( -
+