Add permission prompt shortcuts (#9991)

* feat(vscode): add permission prompt shortcuts

* chore: update kilo-vscode visual regression baselines

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Marius
2026-05-07 11:36:45 +02:00
committed by GitHub
co-authored by github-actions[bot]
parent 3095efcc4c
commit fa326f21e3
3 changed files with 69 additions and 34 deletions
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Support accepting command permission prompts with Enter and denying them with Escape.
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:483c04840175e7678f0cfd1eba377ec199239404acd567fd7778367f4d2cb46c
size 21219
oid sha256:a1ca383c2a5fa9d6f477430d7c63c7330e13e2667bf717906d3c29c215f163c5
size 21564
@@ -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 (
<div ref={root} tabIndex={-1} onKeyDown={onRoot} style={{ outline: "none" }}>
<div ref={root} data-component="permission-shortcuts" onKeyDown={onRoot}>
<DockPrompt
kind="permission"
header={