diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatCollapsibleContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatCollapsibleContentPart.ts index f805e40ec94ba..d8fc51bf70366 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatCollapsibleContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatCollapsibleContentPart.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $ } from '../../../../../../base/browser/dom.js'; +import { $, DisposableResizeObserver } from '../../../../../../base/browser/dom.js'; import { ButtonWithIcon } from '../../../../../../base/browser/ui/button/button.js'; import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js'; import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; @@ -128,6 +128,23 @@ export abstract class ChatCollapsibleContentPart extends Disposable implements I this._contentInitialized = true; this._contentElement = this.initContent(); this._domNode?.appendChild(this._contentElement); + + const resizeObserver = this._register(new DisposableResizeObserver(() => { + if (this._isExpanded.read(undefined) && this._contentElement) { + this._contentElement.style.maxHeight = `${this._contentElement.scrollHeight}px`; + } + })); + this._register(resizeObserver.observe(this._contentElement)); + } + + if (this._contentElement) { + if (expanded) { + this._contentElement.style.maxHeight = `${this._contentElement.scrollHeight}px`; + } else { + this._contentElement.style.maxHeight = `${this._contentElement.scrollHeight}px`; + void this._contentElement.offsetHeight; + this._contentElement.style.maxHeight = '0px'; + } } })); diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTodoListWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTodoListWidget.ts index 619d00a2f5878..542b2848ecec6 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTodoListWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatTodoListWidget.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { disposableTimeout } from '../../../../../../base/common/async.js'; import * as dom from '../../../../../../base/browser/dom.js'; import { trackFocus } from '../../../../../../base/browser/dom.js'; import { Button } from '../../../../../../base/browser/ui/button/button.js'; import { IconLabel } from '../../../../../../base/browser/ui/iconLabel/iconLabel.js'; import { IListRenderer, IListVirtualDelegate } from '../../../../../../base/browser/ui/list/list.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; -import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../../../base/common/lifecycle.js'; import { isEqual } from '../../../../../../base/common/resources.js'; import { URI } from '../../../../../../base/common/uri.js'; import { localize } from '../../../../../../nls.js'; @@ -110,6 +111,15 @@ class TodoListRenderer implements IListRenderer { } } +const collapsedTodoTitleFlipDuration = 450; +const collapsedTodoIconCompleteDuration = 350; +const collapsedTodoIconHoldDuration = 100; + +interface ICollapsedTodoTitleContent { + readonly key: string; + readonly content: HTMLElement; +} + export class ChatTodoListWidget extends Disposable { public readonly domNode: HTMLElement; @@ -123,6 +133,8 @@ export class ChatTodoListWidget extends Disposable { private clearButton!: Button; private _currentSessionResource: URI | undefined; private _todoList: WorkbenchList | undefined; + private _collapsedTitleKey: string | undefined; + private readonly _collapsedTitleAnimation = this._register(new MutableDisposable()); private readonly _inChatTodoListContextKey: IContextKey; @@ -157,6 +169,23 @@ export class ChatTodoListWidget extends Disposable { this.domNode.style.display = 'none'; } + private getExpandedMaxHeight(): string { + const currentListHeight = this._todoList?.getHTMLElement().style.height; + const parsedHeight = currentListHeight ? Number.parseFloat(currentListHeight) : 0; + return `${parsedHeight + 4}px`; + } + + private setExpanded(expanded: boolean): void { + this._isExpanded = expanded; + this.expandoButton.element.setAttribute('aria-expanded', String(expanded)); + this.todoListContainer.classList.toggle('collapsed', !expanded); + this.todoListContainer.setAttribute('aria-hidden', String(!expanded)); + this.todoListContainer.style.maxHeight = expanded ? this.getExpandedMaxHeight() : '0px'; + + this.expandIcon.classList.toggle('codicon-chevron-down', expanded); + this.expandIcon.classList.toggle('codicon-chevron-right', !expanded); + } + private createChatTodoWidget(): HTMLElement { const container = dom.$('.chat-todo-list-widget'); container.style.display = 'none'; @@ -190,13 +219,14 @@ export class ChatTodoListWidget extends Disposable { this.expandoButton.element.appendChild(this.clearButtonContainer); this.todoListContainer = dom.$('.todo-list-container'); - this.todoListContainer.style.display = this._isExpanded ? 'block' : 'none'; this.todoListContainer.id = 'todo-list-container'; this.todoListContainer.setAttribute('role', 'list'); this.todoListContainer.setAttribute('aria-labelledby', 'todo-list-title'); + this.todoListContainer.style.maxHeight = '0px'; container.appendChild(expandoContainer); container.appendChild(this.todoListContainer); + this.setExpanded(this._isExpanded); this._register(this.expandoButton.onDidClick(() => { this.toggleExpanded(); @@ -286,8 +316,6 @@ export class ChatTodoListWidget extends Disposable { } private renderTodoList(todoList: IChatTodo[]): void { - this.updateTitleElement(this.titleElement, todoList); - const allIncomplete = todoList.every(todo => todo.status === 'not-started'); if (allIncomplete) { this._userManuallyExpanded = false; @@ -320,6 +348,7 @@ export class ChatTodoListWidget extends Disposable { const height = itemsShown * 22; this._todoList.layout(height); this._todoList.getHTMLElement().style.height = `${height}px`; + this.todoListContainer.style.maxHeight = this._isExpanded ? this.getExpandedMaxHeight() : '0px'; this._todoList.splice(0, this._todoList.length, todoList); const hasInProgressTask = todoList.some(todo => todo.status === 'in-progress'); @@ -330,26 +359,20 @@ export class ChatTodoListWidget extends Disposable { // Only auto-collapse if there are in-progress or completed tasks AND user hasn't manually expanded if ((hasInProgressTask || hasCompletedTask) && this._isExpanded && !this._userManuallyExpanded) { - this._isExpanded = false; - this.expandoButton.element.setAttribute('aria-expanded', 'false'); - this.todoListContainer.style.display = 'none'; - - this.expandIcon.classList.remove('codicon-chevron-down'); - this.expandIcon.classList.add('codicon-chevron-right'); - - this.updateTitleElement(this.titleElement, todoList); + this.setExpanded(false); + // Clear the key so the first collapsed title renders without animation. + // The expanded title content in the DOM would cause a broken flip animation. + this._collapsedTitleKey = undefined; + this._collapsedTitleAnimation.clear(); } + + this.updateTitleElement(this.titleElement, todoList); } private toggleExpanded(): void { - this._isExpanded = !this._isExpanded; + this.setExpanded(!this._isExpanded); this._userManuallyExpanded = true; - this.expandIcon.classList.toggle('codicon-chevron-down', this._isExpanded); - this.expandIcon.classList.toggle('codicon-chevron-right', !this._isExpanded); - - this.todoListContainer.style.display = this._isExpanded ? 'block' : 'none'; - if (this._currentSessionResource) { const todoList = this.chatTodoListService.getTodos(this._currentSessionResource); this.updateTitleElement(this.titleElement, todoList); @@ -386,8 +409,6 @@ export class ChatTodoListWidget extends Disposable { } private updateTitleElement(titleElement: HTMLElement, todoList: IChatTodo[]): void { - titleElement.textContent = ''; - const completedCount = todoList.filter(todo => todo.status === 'completed').length; const totalCount = todoList.length; const inProgressTodos = todoList.filter(todo => todo.status === 'in-progress'); @@ -403,44 +424,138 @@ export class ChatTodoListWidget extends Disposable { this.expandoButton.element.setAttribute('aria-expanded', this._isExpanded ? 'true' : 'false'); if (this._isExpanded) { - const titleText = dom.$('span'); - titleText.textContent = totalCount > 0 ? + this._collapsedTitleAnimation.clear(); + this._collapsedTitleKey = undefined; + + const { content: titleText } = this.createTitleContent(totalCount > 0 ? localize('chat.todoList.titleWithCount', 'Todos ({0}/{1})', currentTaskNumber, totalCount) : - localize('chat.todoList.title', 'Todos'); - titleElement.appendChild(titleText); + localize('chat.todoList.title', 'Todos')); + this.renderTitleContent(titleText); } else { + let collapsedTitleContent: ICollapsedTodoTitleContent | undefined; + // Show first in-progress todo, or if none, the first not-started todo const todoToShow = firstInProgressTodo || firstNotStartedTodo; if (todoToShow) { - const icon = dom.$('.codicon'); - if (todoToShow === firstInProgressTodo) { - icon.classList.add('codicon-record'); - icon.style.color = 'var(--vscode-charts-blue)'; - } else { - icon.classList.add('codicon-circle-outline'); - icon.style.color = 'var(--vscode-foreground)'; - } - icon.style.marginRight = '4px'; - icon.style.verticalAlign = 'middle'; - titleElement.appendChild(icon); - - const todoText = dom.$('span'); - todoText.textContent = localize('chat.todoList.currentTask', '{0} ({1}/{2})', todoToShow.title, currentTaskNumber, totalCount); - todoText.style.verticalAlign = 'middle'; - todoText.style.overflow = 'hidden'; - todoText.style.textOverflow = 'ellipsis'; - todoText.style.whiteSpace = 'nowrap'; - todoText.style.minWidth = '0'; - titleElement.appendChild(todoText); + const { content } = this.createTitleContent( + localize('chat.todoList.currentTask', '{0} ({1}/{2})', todoToShow.title, currentTaskNumber, totalCount), + todoToShow === firstInProgressTodo + ? { codicon: 'codicon-record', color: 'var(--vscode-charts-blue)' } + : { codicon: 'codicon-circle-outline', color: 'var(--vscode-foreground)' } + ); + collapsedTitleContent = { + key: `${todoToShow.status}:${todoToShow.title}:${currentTaskNumber}:${totalCount}`, + content + }; } // Show "Done" when all tasks are completed else if (completedCount > 0 && completedCount === totalCount) { - const doneText = dom.$('span'); - doneText.textContent = localize('chat.todoList.titleWithCount', 'Todos ({0}/{1})', totalCount, totalCount); - doneText.style.verticalAlign = 'middle'; - titleElement.appendChild(doneText); + const { content } = this.createTitleContent(localize('chat.todoList.titleWithCount', 'Todos ({0}/{1})', totalCount, totalCount)); + collapsedTitleContent = { + key: `done:${totalCount}`, + content + }; + } + + if (!collapsedTitleContent) { + this._collapsedTitleAnimation.clear(); + this._collapsedTitleKey = undefined; + dom.clearNode(titleElement); + return; } + + const shouldAnimate = this.shouldAnimateCollapsedTitle(collapsedTitleContent.key); + if (shouldAnimate) { + this.animateCollapsedTitleContent(collapsedTitleContent); + } else { + this._collapsedTitleAnimation.clear(); + this._collapsedTitleKey = collapsedTitleContent.key; + this.renderTitleContent(collapsedTitleContent.content); + } + } + } + + private shouldAnimateCollapsedTitle(nextKey: string): boolean { + if (!this._collapsedTitleKey || this._collapsedTitleKey === nextKey || this.domNode.style.display === 'none') { + return false; + } + + return !dom.getWindow(this.domNode).matchMedia('(prefers-reduced-motion: reduce)').matches; + } + + private animateCollapsedTitleContent(nextContent: ICollapsedTodoTitleContent): void { + const previousContent = this.settleCollapsedTitleContent(); + if (!previousContent) { + this._collapsedTitleKey = nextContent.key; + this.renderTitleContent(nextContent.content); + return; + } + + this._collapsedTitleAnimation.clear(); + + const startFlip = () => { + previousContent.classList.add('animating-out'); + nextContent.content.classList.add('animating-in'); + titleAppend(this.titleElement, nextContent.content); + this._collapsedTitleKey = nextContent.key; + + this._collapsedTitleAnimation.value = disposableTimeout(() => { + previousContent.remove(); + nextContent.content.classList.remove('animating-in'); + }, collapsedTodoTitleFlipDuration); + }; + + // If the outgoing content was an in-progress task, briefly show the + // icon turning into a completed check before starting the flip + const wasInProgress = this._collapsedTitleKey?.startsWith('in-progress:'); + const iconElement = previousContent.firstElementChild as HTMLElement | null; + if (wasInProgress && iconElement) { + iconElement.className = 'todo-list-title-content-icon codicon codicon-pass completing'; + iconElement.style.color = 'var(--vscode-charts-green)'; + + // Show the checkmark animation, hold briefly so it's visible, then flip + this._collapsedTitleAnimation.value = disposableTimeout(() => { + startFlip(); + }, collapsedTodoIconCompleteDuration + collapsedTodoIconHoldDuration); + } else { + startFlip(); + } + } + + private settleCollapsedTitleContent(): HTMLElement | undefined { + // Only settle content that was rendered by the collapsed path + if (this._isExpanded || !this._collapsedTitleKey) { + return undefined; + } + + const latestContent = this.titleElement.lastElementChild as HTMLElement | null; + if (!latestContent || !latestContent.classList.contains('todo-list-title-content')) { + return undefined; } + + dom.clearNode(this.titleElement); + latestContent.classList.remove('animating-in', 'animating-out'); + titleAppend(this.titleElement, latestContent); + return latestContent; + } + + private renderTitleContent(content: HTMLElement): void { + dom.clearNode(this.titleElement); + titleAppend(this.titleElement, content); + } + + private createTitleContent(text: string, icon?: { codicon: string; color: string }): { content: HTMLElement } { + const content = dom.$('.todo-list-title-content'); + + if (icon) { + const iconElement = dom.append(content, dom.$(`.todo-list-title-content-icon.codicon.${icon.codicon}`)); + iconElement.style.color = icon.color; + iconElement.setAttribute('aria-hidden', 'true'); + } + + const titleText = dom.append(content, dom.$('span.todo-list-title-content-text')); + titleText.textContent = text; + return { content }; } private getStatusText(status: string): string { @@ -455,3 +570,7 @@ export class ChatTodoListWidget extends Disposable { } } } + +function titleAppend(container: HTMLElement, content: HTMLElement): void { + container.appendChild(content); +} diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index 2096051eba952..f5002f010b246 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -3018,6 +3018,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._chatEditsActionsDisposables.add(autorun(reader => { const collapsed = this._workingSetCollapsed.read(reader); button.icon = collapsed ? Codicon.chevronRight : Codicon.chevronDown; + button.element.setAttribute('aria-expanded', String(!collapsed)); + workingSetContainer.style.maxHeight = collapsed ? '0px' : (this._chatEditList?.object.getHTMLElement().style.height || '0px'); workingSetContainer.classList.toggle('collapsed', collapsed); })); @@ -3085,8 +3087,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const itemsShown = Math.min(allEntries.length, maxItemsShown); const height = itemsShown * 22; const list = this._chatEditList!.object; + const collapsed = this._workingSetCollapsed.read(reader); list.layout(height); list.getHTMLElement().style.height = `${height}px`; + workingSetContainer.style.maxHeight = collapsed ? '0px' : `${height}px`; list.splice(0, list.length, allEntries); })); } diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css index 1278d6c1e7982..d60e8f7c66cf2 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -9,7 +9,8 @@ max-width: 950px; height: 100%; margin: auto; - position: relative; /* Enable absolute positioning for child elements */ + position: relative; + /* Enable absolute positioning for child elements */ /* 11px when base font is 13px */ --vscode-chat-font-size-body-xs: 0.846em; @@ -130,6 +131,18 @@ } } +@keyframes chat-input-top-widget-appear { + 0% { + opacity: 0; + transform: translateY(10px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + .interactive-item-container .chat-animated-ellipsis::after { content: ''; white-space: nowrap; @@ -875,6 +888,27 @@ have to be updated for changes to the rules above, or to support more deeply nes flex-direction: column; gap: 2px; overflow: hidden; + animation: 260ms chat-input-top-widget-appear cubic-bezier(0.2, 0, 0, 1); +} + +.interactive-session .chat-editing-session .chat-editing-session-list, +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container { + max-height: 0; + opacity: 1; + transform: translateY(0); + transform-origin: top; + overflow: hidden; + pointer-events: auto; + transition: max-height 180ms cubic-bezier(0.2, 0, 0, 1), opacity 160ms ease, transform 180ms cubic-bezier(0.2, 0, 0, 1), margin-top 180ms cubic-bezier(0.2, 0, 0, 1); + will-change: max-height, opacity, transform; +} + +.interactive-session .chat-editing-session .chat-editing-session-list.collapsed, +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container.collapsed { + max-height: 0; + opacity: 0; + transform: translateY(-6px); + pointer-events: none; } .interactive-session .interactive-input-part > .chat-todo-list-widget-container:has(.chat-todo-list-widget.has-todos) + .chat-editing-session .chat-editing-session-container { @@ -1105,6 +1139,11 @@ have to be updated for changes to the rules above, or to support more deeply nes position: relative; } +.interactive-session .interactive-input-part > .chat-input-widgets-container .chat-status-widget, +.interactive-session .chat-getting-started-tip-container > .chat-tip-widget { + animation: 260ms chat-input-top-widget-appear cubic-bezier(0.2, 0, 0, 1); +} + /* Chat Todo List Widget Container - mirrors chat-editing-session styling */ .interactive-session .interactive-input-part > .chat-todo-list-widget-container { margin-bottom: -4px; @@ -1123,6 +1162,7 @@ have to be updated for changes to the rules above, or to support more deeply nes flex-direction: column; gap: 2px; overflow: hidden; + animation: 260ms chat-input-top-widget-appear cubic-bezier(0.2, 0, 0, 1); } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-expand { @@ -1214,12 +1254,95 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; overflow: hidden; text-overflow: ellipsis; + position: relative; + min-width: 0; + width: 100%; + min-height: 22px; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; + width: 100%; + line-height: 22px; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content-icon { + flex-shrink: 0; + font-size: 16px; + transition: color 200ms cubic-bezier(0.2, 0, 0, 1); +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content-icon.completing { + animation: chat-todo-icon-complete 350ms cubic-bezier(0.2, 0, 0, 1) both; +} + +@keyframes chat-todo-icon-complete { + 0% { + transform: scale(1); + } + + 40% { + transform: scale(1.25); + } + + 100% { + transform: scale(1); + } +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content.animating-out, +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content.animating-in { + position: absolute; + inset: 0; + pointer-events: none; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content.animating-out { + animation: 450ms chat-todo-collapsed-title-flip-out cubic-bezier(0.2, 0, 0, 1) both; +} + +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-title .todo-list-title-content.animating-in { + animation: 450ms chat-todo-collapsed-title-flip-in cubic-bezier(0.2, 0, 0, 1) both; +} + +@keyframes chat-todo-collapsed-title-flip-out { + 0% { + opacity: 1; + transform: translateY(0); + } + + 100% { + opacity: 0; + transform: translateY(-100%); + } +} + +@keyframes chat-todo-collapsed-title-flip-in { + + 0%, + 40% { + opacity: 0; + transform: translateY(100%); + } + + 100% { + opacity: 1; + transform: translateY(0); + } } .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container { margin-top: 2px; - max-height: calc(6.5 * 21px); - /* 6.5 items to show half-line affordance */ overflow-y: auto; overscroll-behavior: contain; scroll-behavior: smooth; @@ -1229,6 +1352,11 @@ have to be updated for changes to the rules above, or to support more deeply nes /* Half item height to show partial previous item */ } +.interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list-container.collapsed { + margin-top: 0; + overflow-y: hidden; +} + .interactive-session .interactive-input-part > .chat-todo-list-widget-container .chat-todo-list-widget .todo-list { display: flex; @@ -2231,8 +2359,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } } -.interactive-session .checkpoint-file-changes-summary.chat-file-changes-collapsed .chat-summary-list, -.interactive-session .chat-used-context.chat-used-context-collapsed .chat-used-context-list { +.interactive-session .checkpoint-file-changes-summary.chat-file-changes-collapsed .chat-summary-list { display: none; } @@ -2243,6 +2370,31 @@ have to be updated for changes to the rules above, or to support more deeply nes line-height: 1.5em; } +.interactive-session .chat-used-context.chat-used-context-collapsed { + gap: 0; +} + +.interactive-session .chat-used-context .chat-used-context-list { + transform: translateY(0); + transform-origin: top; + overflow: hidden; + pointer-events: auto; + transition: max-height 180ms cubic-bezier(0.2, 0, 0, 1), height 180ms cubic-bezier(0.2, 0, 0, 1), opacity 160ms ease, transform 180ms cubic-bezier(0.2, 0, 0, 1), margin 180ms cubic-bezier(0.2, 0, 0, 1), padding 180ms cubic-bezier(0.2, 0, 0, 1), border 180ms cubic-bezier(0.2, 0, 0, 1); + will-change: max-height, opacity, transform; +} + +.interactive-session .interactive-item-container .chat-used-context.chat-used-context-collapsed .chat-used-context-list, +.interactive-session .interactive-response .value .chat-used-context.chat-used-context-collapsed .chat-used-context-list { + max-height: 0; + height: 0; + opacity: 0; + transform: translateY(-6px); + pointer-events: none; + margin: 0; + padding: 0; + border: none; +} + .interactive-response-progress-tree, .chat-notification-widget, .chat-summary-list, @@ -2282,7 +2434,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .chat-editing-session-list.collapsed { - display: none; + margin-top: 0; } .interactive-session .chat-summary-list .monaco-list .monaco-list-row { @@ -2462,14 +2614,12 @@ have to be updated for changes to the rules above, or to support more deeply nes /* shimmer animation for shimmer progress */ &.shimmer-progress .rendered-markdown.progress-step > p { - background: linear-gradient( - 90deg, - var(--vscode-descriptionForeground) 0%, - var(--vscode-descriptionForeground) 30%, - var(--vscode-chat-thinkingShimmer) 50%, - var(--vscode-descriptionForeground) 70%, - var(--vscode-descriptionForeground) 100% - ); + background: linear-gradient(90deg, + var(--vscode-descriptionForeground) 0%, + var(--vscode-descriptionForeground) 30%, + var(--vscode-chat-thinkingShimmer) 50%, + var(--vscode-descriptionForeground) 70%, + var(--vscode-descriptionForeground) 100%); background-size: 400% 100%; background-clip: text; -webkit-background-clip: text; @@ -2705,7 +2855,8 @@ have to be updated for changes to the rules above, or to support more deeply nes outline: none; border: none; - .codicon.codicon-file-media, .codicon.codicon-warning { + .codicon.codicon-file-media, + .codicon.codicon-warning { font-size: 12px; margin-right: 2px; } @@ -3210,7 +3361,8 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; justify-content: center; height: 100%; - margin: -1px -8px -1px 0; /* Extend to button edge, accounting for button padding */ + margin: -1px -8px -1px 0; + /* Extend to button edge, accounting for button padding */ padding: 0 4px 0 2px; gap: 4px; cursor: pointer; @@ -3295,7 +3447,8 @@ have to be updated for changes to the rules above, or to support more deeply nes opacity: 0; transition: opacity 0.15s; color: var(--vscode-descriptionForeground); - margin-top: -4px; /* visually center with overlaid top controls */ + margin-top: -4px; + /* visually center with overlaid top controls */ } .interactive-item-container.pending-request:hover .chat-pending-drag-handle {