Merge pull request #11487 from Kilo-Org/feat/vscode-tool-call-animations

feat(vscode): animate tool call previews
This commit is contained in:
Iván Uruchurtu
2026-06-24 13:23:05 -06:00
committed by GitHub
6 changed files with 128 additions and 26 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Animate tool-call and reasoning details when expanding and collapsing.
+55 -3
View File
@@ -129,19 +129,62 @@ html[data-theme="kilo-vscode"] [data-component="tool-trigger"] {
}
}
@keyframes tool-card-down {
from {
grid-template-rows: 0fr;
opacity: 0;
}
to {
grid-template-rows: 1fr;
opacity: 1;
}
}
@keyframes tool-card-up {
from {
grid-template-rows: 1fr;
opacity: 1;
}
to {
grid-template-rows: 0fr;
opacity: 0;
}
}
/* Card styling for tool calls in the VS Code sidebar theme */
html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] {
border-radius: 0;
border: 0;
overflow: visible;
/* Header trigger — add bottom border when the collapsible is open */
[data-component="collapsible"].tool-collapsible {
gap: 0;
border-radius: 0;
&:has([data-slot="collapsible-trigger"][aria-expanded="true"]) {
gap: 6px;
> [data-slot="collapsible-content"] {
display: grid;
overflow: visible;
clip-path: inset(0 -6px);
> * {
min-height: 0;
}
}
> [data-slot="collapsible-content"][data-expanded] {
animation: tool-card-down 220ms cubic-bezier(0.23, 1, 0.32, 1);
}
> [data-slot="collapsible-content"][data-closed] {
animation: tool-card-up 160ms cubic-bezier(0.77, 0, 0.175, 1);
}
[data-slot="basic-tool-details"]::before {
display: block;
height: 6px;
content: "";
}
[data-slot="collapsible-trigger"] {
@@ -409,3 +452,12 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
}
}
}
@media (prefers-reduced-motion: reduce) {
html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] {
[data-component="collapsible"].tool-collapsible > [data-slot="collapsible-content"][data-expanded],
[data-component="collapsible"].tool-collapsible > [data-slot="collapsible-content"][data-closed] {
animation-duration: 0.01ms;
}
}
}
+10 -8
View File
@@ -20,14 +20,16 @@ export function initialOpen(props: OpenProps) {
export function BasicTool(props: BasicToolProps) {
const key = () => toolOpenKey(props)
const initial = () => initialOpen(props)
const change = (open: boolean) => {
writeToolOpen(key(), open)
props.onOpenChange?.(open)
}
if (!("children" in props)) {
return <Base {...props} defaultOpen={initial()} retainDetails={props.defer} onOpenChange={change} />
}
return (
<Base
{...props}
defaultOpen={initial()}
onOpenChange={(open) => {
writeToolOpen(key(), open)
props.onOpenChange?.(open)
}}
/>
<Base {...props} defaultOpen={initial()} retainDetails={props.defer} onOpenChange={change}>
<div data-slot="basic-tool-details">{props.children}</div>
</Base>
)
}
@@ -477,6 +477,30 @@ html[data-theme="kilo-vscode"] [data-component="todos"] {
padding: 8px;
}
@keyframes reasoning-card-down {
from {
grid-template-rows: 0fr;
opacity: 0;
}
to {
grid-template-rows: 1fr;
opacity: 1;
}
}
@keyframes reasoning-card-up {
from {
grid-template-rows: 1fr;
opacity: 1;
}
to {
grid-template-rows: 0fr;
opacity: 0;
}
}
/* Reasoning block: visually distinct from normal agent output.
Reasoning stays open unless the user explicitly collapses it. */
[data-component="reasoning-part"] {
@@ -550,22 +574,21 @@ html[data-theme="kilo-vscode"] [data-component="todos"] {
}
}
/* Collapse/expand animation via Kobalte's CSS variable.
Uses grid trick: the outer wrapper transitions grid-template-rows
between 0fr (collapsed) and 1fr (expanded) so the content smoothly
collapses without needing to know the pixel height up front. */
[data-slot="collapsible-content"] {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 250ms ease-out;
overflow: hidden;
> * {
min-height: 0;
overflow: hidden;
}
&[data-expanded] {
grid-template-rows: 1fr;
animation: reasoning-card-down 220ms cubic-bezier(0.23, 1, 0.32, 1);
}
&[data-closed] {
animation: reasoning-card-up 160ms cubic-bezier(0.77, 0, 0.175, 1);
}
}
@@ -598,10 +621,6 @@ html[data-theme="kilo-vscode"] [data-component="reasoning-part"] {
[data-component="collapsible"].tool-collapsible {
gap: 0;
border-radius: 0;
&:has([data-slot="collapsible-trigger"][aria-expanded="true"]) {
gap: 6px;
}
}
[data-slot="collapsible-trigger"] {
@@ -667,6 +686,12 @@ html[data-theme="kilo-vscode"] [data-component="reasoning-part"] {
width: calc(100% + 12px);
}
[data-slot="reasoning-details"]::before {
display: block;
height: 6px;
content: "";
}
[data-slot="reasoning-content"] {
padding: 8px 12px 10px;
border: 0;
@@ -712,3 +737,10 @@ html[data-theme="kilo-vscode"] [data-component="reasoning-part"] {
line-height: var(--line-height-large);
}
}
@media (prefers-reduced-motion: reduce) {
[data-component="reasoning-part"] [data-slot="collapsible-content"][data-expanded],
[data-component="reasoning-part"] [data-slot="collapsible-content"][data-closed] {
animation-duration: 0.01ms;
}
}
@@ -1591,8 +1591,10 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
<Collapsible.Arrow />
</Collapsible.Trigger>
<Collapsible.Content>
<div data-slot="reasoning-content" ref={ref} onScroll={onScroll} onWheel={onWheel}>
<Markdown text={view().body} cacheKey={id} streaming={!done()} />
<div data-slot="reasoning-details">
<div data-slot="reasoning-content" ref={ref} onScroll={onScroll} onWheel={onWheel}>
<Markdown text={view().body} cacheKey={id} streaming={!done()} />
</div>
</div>
</Collapsible.Content>
</Collapsible>
+11 -2
View File
@@ -33,6 +33,7 @@ export interface BasicToolProps {
onOpenChange?: (open: boolean) => void
forceOpen?: boolean
defer?: boolean
retainDetails?: boolean // kilocode_change
hasDetails?: boolean // kilocode_change
locked?: boolean
animated?: boolean
@@ -133,7 +134,7 @@ export function BasicTool(props: BasicToolProps) {
if (!props.defer) return
if (!value) {
cancel()
setState("ready", false)
if (!props.retainDetails) setState("ready", false) // kilocode_change
return
}
@@ -183,6 +184,14 @@ export function BasicTool(props: BasicToolProps) {
props.onOpenChange?.(value) // kilocode_change
}
// kilocode_change start
const end = (event: AnimationEvent) => {
if (event.target !== event.currentTarget) return
if (!props.retainDetails || open()) return
setState("ready", false)
}
// kilocode_change end
const trigger = () => (
<div
data-component="tool-trigger"
@@ -307,7 +316,7 @@ export function BasicTool(props: BasicToolProps) {
</Show>
{/* kilocode_change start */}
<Show when={!props.animated && (hasChildren() || hasDetails()) && !props.hideDetails}>
<Collapsible.Content>
<Collapsible.Content onAnimationEnd={end}>
<Show when={!props.defer || ready()}>{props.children}</Show>
</Collapsible.Content>
</Show>