Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/permission-prompt-scroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Keep the Allow and Deny buttons reachable when a permission prompt contains a large diff or a long command: the prompt now scrolls its own content and shrinks with the available chat height instead of pushing its buttons out of view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/kilo-vscode/tests/permission-diff.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ test("edit approval diff shows line numbers in compact viewer", async ({ page })
await page.setViewportSize({ width: 420, height: 720 })
await page.goto(`/iframe.html?id=${STORY_ID}&viewMode=story&globals=${GLOBALS}`, { waitUntil: "load" })

await page.locator('[data-slot="permission-diff"]').scrollIntoViewIfNeeded()
const number = page.locator('[data-slot="permission-diff-content"] [data-column-number]').first()
await expect(number).toBeVisible()
})
Original file line number Diff line number Diff line change
Expand Up @@ -287,50 +287,58 @@ export const PermissionDock: Component<{
</Show>
}
>
<Show
when={skillShellCommands().length > 0}
fallback={
<>
<Show when={cmdDescription()}>{(desc) => <div data-slot="permission-hint">{desc()}</div>}</Show>
<Show when={command()}>
{(cmd) => <PermissionCommand command={cmd()} plain={props.request.args.heredoc === true} />}
</Show>

{(() => {
const desc = description()
if (!desc)
return !command() && toolDescription() ? (
<div data-slot="permission-hint">{toolDescription()}</div>
) : null
if (desc.kind === "single")
return (
<div
data-slot="permission-hint"
data-wrap={external() ? "" : undefined}
title={external() ? desc.text : undefined}
>
{desc.text}
</div>
)
return (
<div data-slot="permission-patterns">
<span data-slot="permission-patterns-title">{desc.title}</span>
<For each={desc.paths}>{(path) => <code data-slot="permission-pattern">{path}</code>}</For>
</div>
)
})()}
</>
}
>
{/* Verbatim commands (args.commands), control-char/bidi-escaped so the displayed command matches execution. */}
<For each={skillShellCommands()}>{(cmd) => <PermissionCommand command={displaySkillCommand(cmd)} />}</For>
</Show>

<Show when={diffs().length > 0}>
<div data-slot="permission-diffs" data-count={diffs().length}>
<For each={diffs()}>{(diff) => <PermissionDiff filediff={diff} />}</For>
{/* Everything above the buttons scrolls: a long command or a large diff must never
push Allow/Deny out of the clipped chat view. */}
<div data-slot="permission-scroll">
{/* Pierre's virtualizer uses the scroll root's first child as its content
container, so keep all variable-height permission content in one wrapper. */}
<div data-slot="permission-scroll-content">
<Show
when={skillShellCommands().length > 0}
fallback={
<>
<Show when={cmdDescription()}>{(desc) => <div data-slot="permission-hint">{desc()}</div>}</Show>
<Show when={command()}>
{(cmd) => <PermissionCommand command={cmd()} plain={props.request.args.heredoc === true} />}
</Show>

{(() => {
const desc = description()
if (!desc)
return !command() && toolDescription() ? (
<div data-slot="permission-hint">{toolDescription()}</div>
) : null
if (desc.kind === "single")
return (
<div
data-slot="permission-hint"
data-wrap={external() ? "" : undefined}
title={external() ? desc.text : undefined}
>
{desc.text}
</div>
)
return (
<div data-slot="permission-patterns">
<span data-slot="permission-patterns-title">{desc.title}</span>
<For each={desc.paths}>{(path) => <code data-slot="permission-pattern">{path}</code>}</For>
</div>
)
})()}
</>
}
>
{/* Verbatim commands (args.commands), control-char/bidi-escaped so the displayed command matches execution. */}
<For each={skillShellCommands()}>{(cmd) => <PermissionCommand command={displaySkillCommand(cmd)} />}</For>
</Show>

<Show when={diffs().length > 0}>
<div data-slot="permission-diffs" data-count={diffs().length}>
<For each={diffs()}>{(diff) => <PermissionDiff filediff={diff} />}</For>
</div>
</Show>
</div>
</Show>
</div>

<div data-slot="permission-actions">
<Button
Expand Down
36 changes: 36 additions & 0 deletions packages/kilo-vscode/webview-ui/src/styles/chat-layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,42 @@
margin-inline: 0;
}

/* A permission card with a diff can be taller than the room left under the
transcript, and `.chat-view` clips whatever doesn't fit. With a rigid
`.chat-input` that silently cut off the Allow/Deny row with nothing to
scroll. Only while a permission is pending, lay the composer column out so
the card is a shrinkable flex item: its height is then bounded by the space
actually available and its own body scrolls instead. */
.chat-input:has(> [data-component="permission-shortcuts"]) {
display: flex;
flex-direction: column;
flex-shrink: 1;
min-height: 0;
}

.chat-input:has(> [data-component="permission-shortcuts"]) > * {
flex-shrink: 0;
}

/* Margins don't collapse in a flex column, so drop the halves that used to
overlap and keep the spacing around the card identical. */
.chat-input:has(> [data-component="permission-shortcuts"]) > .startup-error-banner {
margin-bottom: 0;
}

.chat-input:has(> [data-component="permission-shortcuts"]) > .prompt-input-container {
margin-top: 0;
}

.chat-input > [data-component="permission-shortcuts"] {
display: flex;
flex-direction: column;
flex-shrink: 1;
/* Shrinks down to the card's own floor plus its 12px vertical margins, so the
card is never laid out over the prompt input below it. */
min-height: calc(var(--permission-card-min-height, 11em) + 24px);
}

.vscode-session-turn-diffs-trigger {
display: inline-flex;
align-items: center;
Expand Down
60 changes: 57 additions & 3 deletions packages/kilo-vscode/webview-ui/src/styles/permission-dock.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
Permission Dock (VS Code overrides)
============================================ */

[data-component="permission-shortcuts"] {
/* Title row + Allow/Deny row + tray. The card never shrinks below this, and
the composer column reserves the same space (see chat-layout.css) so a
shrinking card can never overlap the prompt input below. */
--permission-card-min-height: 11em;
}

[data-component="dock-prompt"][data-kind="permission"] {
animation: permission-slide-in 0.3s ease-out;
margin: 12px;
/* The card shrinks with the chat view, but never below its minimum: the
scroll area collapses first, so the buttons stay reachable even in a very
short panel. */
min-height: var(--permission-card-min-height, 11em);
border: 1px solid
color-mix(in srgb, var(--vscode-editorWarning-foreground, #cca700) 40%, var(--vscode-panel-border, transparent));
border-radius: 0.25rem;
Expand Down Expand Up @@ -43,6 +54,15 @@
color: var(--text-weak, var(--vscode-descriptionForeground));
}

/* Both keep their full height inside [data-slot="permission-scroll"]: they have
their own clipped or hidden-scrollbar boxes, so shrinking them would cut text
off instead of letting the scroll area scroll. */
[data-slot="permission-command"],
[data-slot="permission-patterns"] {
flex: 0 0 auto;
overflow: visible;
}

[data-slot="permission-command"] {
position: relative;
min-width: 0;
Expand Down Expand Up @@ -135,16 +155,45 @@
word-break: break-all;
}

/* The only scroll region of the card. It shrinks before anything else, so the
description, command and diffs stay reachable by scrolling while the header
and the Allow/Deny row keep their space no matter how tall the diff is or
how little room the chat view has left. */
[data-slot="permission-scroll"] {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
max-height: 420px;
overflow-y: auto;
overscroll-behavior: contain;
}

/* Pierre's virtualizer treats the scroll root's first child as its content
container. Keep the variable-height permission content together so nested
descriptions do not become the virtualizer's content root. */
[data-slot="permission-scroll-content"] {
display: flex;
flex: 0 0 auto;
flex-direction: column;
gap: 4px;
min-height: min-content;
}

[data-slot="permission-diffs"] {
display: flex;
flex-direction: column;
gap: 8px;
margin: 8px 0;
max-height: 420px;
overflow: auto;
flex-shrink: 0;
min-height: 0;
}

/* Keeps its own height when the card shrinks. The file card clips its content
(overflow: hidden), so shrinking it would hide the diff instead of scrolling
[data-slot="permission-scroll"]. */
[data-slot="permission-diff"] {
flex-shrink: 0;
margin: 8px 0;
border: 1px solid var(--border-weak-base);
border-radius: 4px;
Expand Down Expand Up @@ -212,7 +261,10 @@
flex-shrink: 0;
}

[data-slot="permission-diff-content"] {
/* With a single file the card scrolls as part of [data-slot="permission-scroll"],
so there is only one scrollbar. With several files each one keeps its own cap,
otherwise the last file sits thousands of pixels down the list. */
[data-slot="permission-diffs"]:not([data-count="1"]) [data-slot="permission-diff-content"] {
max-height: 300px;
overflow: auto;
}
Expand Down Expand Up @@ -399,6 +451,8 @@
gap: 8px;
justify-content: flex-start;
padding: 4px 0 8px;
/* Never shrink or scroll away: this row is the only way to answer the ask. */
flex-shrink: 0;
}

[data-slot="permission-actions"] > [data-component="button"][data-variant="primary"] {
Expand Down
Loading