From 4b2d69559ffb04274977a7d5781e642d141c1178 Mon Sep 17 00:00:00 2001 From: justschen Date: Fri, 17 Apr 2026 17:02:48 -0700 Subject: [PATCH 1/6] developer joy animation around input box --- .../lib/stylelint/vscode-known-variables.json | 1 + .../contrib/chat/browser/chat.contribution.ts | 52 +++-------- .../contrib/chat/browser/widget/chatWidget.ts | 18 ++++ .../chat/browser/widget/media/chat.css | 89 ++++++++++++++++++- .../contrib/chat/common/constants.ts | 5 +- 5 files changed, 117 insertions(+), 48 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 59dfc331f3501..16ab5f7914eae 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -1036,6 +1036,7 @@ "--monaco-editor-warning-decoration", "--animation-angle", "--animation-opacity", + "--chat-input-anim-angle", "--chat-setup-dialog-glow-angle", "--vscode-chat-font-family", "--vscode-chat-font-size-body-l", diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 568a6c0855baa..3003613470f1f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -11,7 +11,6 @@ import { PolicyCategory } from '../../../../base/common/policy.js'; import { AgentHostEnabledSettingId, AgentHostIpcLoggingSettingId } from '../../../../platform/agentHost/common/agentService.js'; import { AgentNetworkFilterService, IAgentNetworkFilterService } from '../../../../platform/networkFilter/common/networkFilterService.js'; import { AgentNetworkDomainSettingId } from '../../../../platform/networkFilter/common/settings.js'; -import { AgentSandboxSettingId } from '../../../../platform/sandbox/common/settings.js'; import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js'; import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; @@ -350,40 +349,6 @@ configurationRegistry.registerConfiguration({ description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), default: null }, - [ChatConfiguration.IncrementalRendering]: { - type: 'boolean', - description: nls.localize('chat.experimental.incrementalRendering.enabled', "Enables incremental rendering with optional block-level animation when streaming chat responses."), - default: false, - tags: ['experimental'], - }, - [ChatConfiguration.IncrementalRenderingStyle]: { - type: 'string', - enum: ['none', 'fade', 'rise', 'blur', 'scale', 'slide', 'reveal'], - enumDescriptions: [ - nls.localize('chat.experimental.incrementalRendering.animationStyle.none', "No animation. Content appears instantly."), - nls.localize('chat.experimental.incrementalRendering.animationStyle.fade', "Simple opacity fade from 0 to 1."), - nls.localize('chat.experimental.incrementalRendering.animationStyle.rise', "Content fades in while rising upward."), - nls.localize('chat.experimental.incrementalRendering.animationStyle.blur', "Content fades in from a blurred state."), - nls.localize('chat.experimental.incrementalRendering.animationStyle.scale', "Content scales up from slightly smaller."), - nls.localize('chat.experimental.incrementalRendering.animationStyle.slide', "Content slides in from the left."), - nls.localize('chat.experimental.incrementalRendering.animationStyle.reveal', "Content reveals top-to-bottom with a soft gradient edge."), - ], - description: nls.localize('chat.experimental.incrementalRendering.animationStyle', "Controls the animation style for incremental rendering."), - default: 'fade', - tags: ['experimental'], - }, - [ChatConfiguration.IncrementalRenderingBuffering]: { - type: 'string', - enum: ['off', 'word', 'paragraph'], - enumDescriptions: [ - nls.localize('chat.experimental.incrementalRendering.buffering.off', "Renders content immediately as tokens arrive."), - nls.localize('chat.experimental.incrementalRendering.buffering.word', "Reveals content word by word."), - nls.localize('chat.experimental.incrementalRendering.buffering.paragraph', "Buffers content until a paragraph break before rendering."), - ], - description: nls.localize('chat.experimental.incrementalRendering.buffering', "Controls how content is buffered before rendering during incremental rendering. Lower buffering levels render faster but may show incomplete sentences or partially formed markdown."), - default: 'word', - tags: ['experimental'], - }, 'chat.detectParticipant.enabled': { type: 'boolean', description: nls.localize('chat.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), @@ -640,6 +605,11 @@ configurationRegistry.registerConfiguration({ default: product.quality !== 'stable', description: nls.localize('chat.persistentProgress.enabled', "Show elapsed time and token usage in chat response progress."), }, + [ChatConfiguration.DeveloperJoy]: { + type: 'boolean', + default: false, + description: nls.localize('chat.developerJoy.enabled', "Show an animated gradient border and soft glow around the chat input while the agent is working or thinking."), + }, [ChatConfiguration.NotifyWindowOnResponseReceived]: { type: 'string', enum: ['off', 'windowNotFocused', 'always'], @@ -835,7 +805,7 @@ configurationRegistry.registerConfiguration({ } }, [AgentNetworkDomainSettingId.NetworkFilter]: { - markdownDescription: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. Domain filtering is also applied to those tools when {2} is enabled.", `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``), + markdownDescription: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. When disabled, no network filtering is applied.", '`#chat.agent.allowedNetworkDomains#`', '`#chat.agent.deniedNetworkDomains#`'), type: 'boolean', default: false, restricted: true, @@ -846,13 +816,13 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.networkFilter', - value: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. Domain filtering is also applied to those tools when {2} is enabled.", `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``), + value: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. When disabled, no network filtering is applied.", '`#chat.agent.allowedNetworkDomains#`', '`#chat.agent.deniedNetworkDomains#`'), } } } }, [AgentNetworkDomainSettingId.AllowedNetworkDomains]: { - markdownDescription: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, '`*.example.com`', `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``), + markdownDescription: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`*.example.com`', '`#chat.agent.deniedNetworkDomains#`'), type: 'array', items: { type: 'string' }, default: [], @@ -864,13 +834,13 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.allowedNetworkDomains', - value: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, '`*.example.com`', `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``), + value: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`*.example.com`', '`#chat.agent.deniedNetworkDomains#`'), } } } }, [AgentNetworkDomainSettingId.DeniedNetworkDomains]: { - markdownDescription: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Takes precedence over {2}. Supports wildcards like {3}.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, '`*.example.com`'), + markdownDescription: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Takes precedence over {2}. Supports wildcards like {3}.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`#chat.agent.allowedNetworkDomains#`', '`*.example.com`'), type: 'array', items: { type: 'string' }, default: [], @@ -882,7 +852,7 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.deniedNetworkDomains', - value: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Takes precedence over {2}. Supports wildcards like {3}.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, '`*.example.com`'), + value: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Takes precedence over {2}. Supports wildcards like {3}.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`#chat.agent.allowedNetworkDomains#`', '`*.example.com`'), } } } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 719d50a08e8d0..3b3fcea0a7422 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -465,6 +465,9 @@ export class ChatWidget extends Disposable implements IChatWidget { this.updateChatViewVisibility(); } } + if (e.affectsConfiguration(ChatConfiguration.DeveloperJoy)) { + this.updateWorkingProgressBorder(); + } })); this._register(bindContextKey(decidedChatEditingResourceContextKey, contextKeyService, (reader) => { @@ -656,6 +659,20 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inlineInputPartDisposable.value!; } + private updateWorkingProgressBorder(): void { + const inputPart = this.inputPartDisposable.value; + if (!inputPart) { + return; + } + const inputContainer = inputPart.inputContainerElement; + if (!inputContainer) { + return; + } + const enabled = this.configurationService.getValue(ChatConfiguration.DeveloperJoy) === true; + const inProgress = !!this.viewModel?.model.requestInProgress.get(); + inputContainer.classList.toggle('working', enabled && inProgress); + } + get inputEditor(): ICodeEditor { return this.input.inputEditor; } @@ -2032,6 +2049,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.requestInProgress.set(this.viewModel.model.requestInProgress.get()); this.hasActiveRequest.set(this.viewModel.model.hasActiveRequest.get()); + this.updateWorkingProgressBorder(); // Update the editor's placeholder text when it changes in the view model if (events?.some(e => e?.kind === 'changePlaceholder')) { 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 4b777d2711c02..530f79de1ec07 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -871,6 +871,76 @@ have to be updated for changes to the rules above, or to support more deeply nes overflow: hidden; } +/* Animated gradient border shown around the chat input while the agent is + working or thinking. Toggled by the `chat.developerJoy.enabled` + setting and the chat widget's request-in-progress state. The ring is + rendered inside the container (using a mask trick) so it works whether or + not the parent has `overflow: hidden`. */ +@property --chat-input-anim-angle { + syntax: ''; + inherits: false; + initial-value: 135deg; +} + +@keyframes chat-input-working-border-spin { + from { + --chat-input-anim-angle: 135deg; + } + to { + --chat-input-anim-angle: 495deg; + } +} + +@keyframes chat-input-working-border-glow { + 0%, 100% { + box-shadow: + 0 0 4px 0 color-mix(in srgb, #b44aff 18%, transparent), + 0 0 10px 0 color-mix(in srgb, #51a2ff 8%, transparent); + } + 50% { + box-shadow: + 0 0 6px 0 color-mix(in srgb, #4af0c0 22%, transparent), + 0 0 14px 1px color-mix(in srgb, #b44aff 12%, transparent); + } +} + +.monaco-workbench .interactive-session .chat-input-container.working { + animation: chat-input-working-border-glow 3s ease-in-out infinite; +} + +.monaco-workbench .interactive-session .chat-input-container.working::before { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + padding: 1px; + background: conic-gradient(from var(--chat-input-anim-angle), + #b44aff, + #4af0c0, + #51a2ff, + #4af0c0, + #b44aff + ); + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + mask-composite: exclude; + pointer-events: none; + z-index: 1; + animation: chat-input-working-border-spin 1.2s linear infinite; +} + +@media (prefers-reduced-motion: reduce) { + .monaco-workbench .interactive-session .chat-input-container.working, + .monaco-workbench .interactive-session .chat-input-container.working::before { + animation: none; + } +} + /* Context usage widget container - positioned in the secondary toolbar below input */ .interactive-session .chat-input-toolbars .chat-context-usage-container, .interactive-session .chat-secondary-toolbar .chat-context-usage-container { @@ -1415,6 +1485,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .chat-secondary-toolbar > .chat-secondary-input-toolbar { overflow: hidden; min-width: 0px; + flex: 1 1 auto; color: var(--vscode-icon-foreground); .monaco-action-bar .action-item .codicon { @@ -1571,6 +1642,14 @@ have to be updated for changes to the rules above, or to support more deeply nes color: var(--vscode-icon-foreground); } +.interactive-session .chat-secondary-input-toolbar .chat-sessionPicker-item .action-label { + height: 16px; + padding: 3px 0px 3px 6px; + display: flex; + align-items: center; + color: var(--vscode-icon-foreground); +} + /* Keep hover background while picker dropdown is open */ .interactive-session .chat-input-toolbar .action-label[aria-expanded="true"], .interactive-session .chat-secondary-toolbar .action-label[aria-expanded="true"] { @@ -1580,7 +1659,8 @@ have to be updated for changes to the rules above, or to support more deeply nes /* When chevrons are hidden and only showing an icon (no label), size to 22x22 with centered icon */ .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)), .interactive-session .chat-input-toolbar .chat-input-picker-item.hide-chevrons .action-label:not(:has(.chat-input-picker-label)), -.interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)) { +.interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)), +.interactive-session .chat-secondary-input-toolbar .chat-sessionPicker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)) { width: 22px; min-width: 22px; height: 22px; @@ -1608,7 +1688,8 @@ have to be updated for changes to the rules above, or to support more deeply nes } .monaco-workbench .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label .codicon-chevron-down, -.monaco-workbench .interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label .codicon-chevron-down { +.monaco-workbench .interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label .codicon-chevron-down, +.monaco-workbench .interactive-session .chat-secondary-input-toolbar .chat-sessionPicker-item .action-label .codicon-chevron-down { font-size: 10px; margin-left: 4px; opacity: 0.75; @@ -1620,8 +1701,10 @@ have to be updated for changes to the rules above, or to support more deeply nes gap: 4px; } -.interactive-session .chat-input-toolbars .chat-sessionPicker-container { +.interactive-session .chat-input-toolbars .chat-sessionPicker-container, +.interactive-session .chat-secondary-toolbar .chat-sessionPicker-container { display: flex; + gap: 4px; max-width: 100%; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 49a79cf797d9e..d63a4913168ec 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -48,6 +48,7 @@ export enum ChatConfiguration { ChatViewProgressBadgeEnabled = 'chat.viewProgressBadge.enabled', ChatContextUsageEnabled = 'chat.contextUsage.enabled', ChatPersistentProgressEnabled = 'chat.persistentProgress.enabled', + DeveloperJoy = 'chat.developerJoy.enabled', SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled', GeneralPurposeAgentEnabled = 'chat.generalPurposeAgent.enabled', SubagentsAllowInvocationsFromSubagents = 'chat.subagents.allowInvocationsFromSubagents', @@ -70,10 +71,6 @@ export enum ChatConfiguration { ToolConfirmationCarousel = 'chat.tools.confirmationCarousel.enabled', DefaultNewSessionMode = 'chat.newSession.defaultMode', AgentHostClientTools = 'chat.agentHost.clientTools', - - IncrementalRendering = 'chat.experimental.incrementalRendering.enabled', - IncrementalRenderingStyle = 'chat.experimental.incrementalRendering.animationStyle', - IncrementalRenderingBuffering = 'chat.experimental.incrementalRendering.buffering', } /** From 3c14ffafdc728ec6b4ea21a36bbc5321f8e65d5e Mon Sep 17 00:00:00 2001 From: justschen Date: Fri, 17 Apr 2026 17:17:48 -0700 Subject: [PATCH 2/6] add back removed stuff --- .../contrib/chat/browser/chat.contribution.ts | 47 ++++++++++++++++--- .../contrib/chat/browser/widget/chatWidget.ts | 2 + .../chat/browser/widget/media/chat.css | 19 ++------ .../contrib/chat/common/constants.ts | 4 ++ 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 3003613470f1f..ba0119a846b30 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -11,6 +11,7 @@ import { PolicyCategory } from '../../../../base/common/policy.js'; import { AgentHostEnabledSettingId, AgentHostIpcLoggingSettingId } from '../../../../platform/agentHost/common/agentService.js'; import { AgentNetworkFilterService, IAgentNetworkFilterService } from '../../../../platform/networkFilter/common/networkFilterService.js'; import { AgentNetworkDomainSettingId } from '../../../../platform/networkFilter/common/settings.js'; +import { AgentSandboxSettingId } from '../../../../platform/sandbox/common/settings.js'; import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js'; import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; @@ -349,6 +350,40 @@ configurationRegistry.registerConfiguration({ description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), default: null }, + [ChatConfiguration.IncrementalRendering]: { + type: 'boolean', + description: nls.localize('chat.experimental.incrementalRendering.enabled', "Enables incremental rendering with optional block-level animation when streaming chat responses."), + default: false, + tags: ['experimental'], + }, + [ChatConfiguration.IncrementalRenderingStyle]: { + type: 'string', + enum: ['none', 'fade', 'rise', 'blur', 'scale', 'slide', 'reveal'], + enumDescriptions: [ + nls.localize('chat.experimental.incrementalRendering.animationStyle.none', "No animation. Content appears instantly."), + nls.localize('chat.experimental.incrementalRendering.animationStyle.fade', "Simple opacity fade from 0 to 1."), + nls.localize('chat.experimental.incrementalRendering.animationStyle.rise', "Content fades in while rising upward."), + nls.localize('chat.experimental.incrementalRendering.animationStyle.blur', "Content fades in from a blurred state."), + nls.localize('chat.experimental.incrementalRendering.animationStyle.scale', "Content scales up from slightly smaller."), + nls.localize('chat.experimental.incrementalRendering.animationStyle.slide', "Content slides in from the left."), + nls.localize('chat.experimental.incrementalRendering.animationStyle.reveal', "Content reveals top-to-bottom with a soft gradient edge."), + ], + description: nls.localize('chat.experimental.incrementalRendering.animationStyle', "Controls the animation style for incremental rendering."), + default: 'fade', + tags: ['experimental'], + }, + [ChatConfiguration.IncrementalRenderingBuffering]: { + type: 'string', + enum: ['off', 'word', 'paragraph'], + enumDescriptions: [ + nls.localize('chat.experimental.incrementalRendering.buffering.off', "Renders content immediately as tokens arrive."), + nls.localize('chat.experimental.incrementalRendering.buffering.word', "Reveals content word by word."), + nls.localize('chat.experimental.incrementalRendering.buffering.paragraph', "Buffers content until a paragraph break before rendering."), + ], + description: nls.localize('chat.experimental.incrementalRendering.buffering', "Controls how content is buffered before rendering during incremental rendering. Lower buffering levels render faster but may show incomplete sentences or partially formed markdown."), + default: 'word', + tags: ['experimental'], + }, 'chat.detectParticipant.enabled': { type: 'boolean', description: nls.localize('chat.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), @@ -805,7 +840,7 @@ configurationRegistry.registerConfiguration({ } }, [AgentNetworkDomainSettingId.NetworkFilter]: { - markdownDescription: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. When disabled, no network filtering is applied.", '`#chat.agent.allowedNetworkDomains#`', '`#chat.agent.deniedNetworkDomains#`'), + markdownDescription: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. Domain filtering is also applied to those tools when {2} is enabled.", `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``), type: 'boolean', default: false, restricted: true, @@ -816,13 +851,13 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.networkFilter', - value: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. When disabled, no network filtering is applied.", '`#chat.agent.allowedNetworkDomains#`', '`#chat.agent.deniedNetworkDomains#`'), + value: nls.localize('chat.agent.networkFilter', "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to {0} and {1}. Domain filtering is also applied to those tools when {2} is enabled.", `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``), } } } }, [AgentNetworkDomainSettingId.AllowedNetworkDomains]: { - markdownDescription: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`*.example.com`', '`#chat.agent.deniedNetworkDomains#`'), + markdownDescription: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, '`*.example.com`', `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``), type: 'array', items: { type: 'string' }, default: [], @@ -834,13 +869,13 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.allowedNetworkDomains', - value: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`*.example.com`', '`#chat.agent.deniedNetworkDomains#`'), + value: nls.localize('chat.agent.allowedNetworkDomains', "Allowed domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Supports wildcards like {2}. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see {3}) take precedence.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, '`*.example.com`', `\`#${AgentNetworkDomainSettingId.DeniedNetworkDomains}#\``), } } } }, [AgentNetworkDomainSettingId.DeniedNetworkDomains]: { - markdownDescription: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Takes precedence over {2}. Supports wildcards like {3}.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`#chat.agent.allowedNetworkDomains#`', '`*.example.com`'), + markdownDescription: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Takes precedence over {2}. Supports wildcards like {3}.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, '`*.example.com`'), type: 'array', items: { type: 'string' }, default: [], @@ -852,7 +887,7 @@ configurationRegistry.registerConfiguration({ localization: { description: { key: 'chat.agent.deniedNetworkDomains', - value: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Only takes effect when {0} is enabled. When {1} is enabled, these also apply to the terminal sandbox. Takes precedence over {2}. Supports wildcards like {3}.", '`#chat.agent.networkFilter#`', '`#chat.agent.sandbox.enabled#`', '`#chat.agent.allowedNetworkDomains#`', '`*.example.com`'), + value: nls.localize('chat.agent.deniedNetworkDomains', "Denied domains for network access by agent tools (fetch tool, integrated browser). Applies when {0} or {1} is enabled. When {1} is enabled, this also configures terminal sandbox networking. Takes precedence over {2}. Supports wildcards like {3}.", `\`#${AgentNetworkDomainSettingId.NetworkFilter}#\``, `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``, `\`#${AgentNetworkDomainSettingId.AllowedNetworkDomains}#\``, '`*.example.com`'), } } } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 3b3fcea0a7422..25bd45feba143 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -1994,6 +1994,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.finishedEditing(); } this.viewModel = undefined; + this.updateWorkingProgressBorder(); this.onDidChangeItems(); this._hasPendingRequestsContextKey.set(false); return; @@ -2068,6 +2069,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } // Disposes the viewmodel and listeners this.viewModel = undefined; + this.updateWorkingProgressBorder(); this.onDidChangeItems(); })); this._sessionIsEmptyContextKey.set(model.getRequests().length === 0); 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 530f79de1ec07..f2018526ea6af 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -1485,7 +1485,6 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .chat-secondary-toolbar > .chat-secondary-input-toolbar { overflow: hidden; min-width: 0px; - flex: 1 1 auto; color: var(--vscode-icon-foreground); .monaco-action-bar .action-item .codicon { @@ -1642,14 +1641,6 @@ have to be updated for changes to the rules above, or to support more deeply nes color: var(--vscode-icon-foreground); } -.interactive-session .chat-secondary-input-toolbar .chat-sessionPicker-item .action-label { - height: 16px; - padding: 3px 0px 3px 6px; - display: flex; - align-items: center; - color: var(--vscode-icon-foreground); -} - /* Keep hover background while picker dropdown is open */ .interactive-session .chat-input-toolbar .action-label[aria-expanded="true"], .interactive-session .chat-secondary-toolbar .action-label[aria-expanded="true"] { @@ -1659,8 +1650,7 @@ have to be updated for changes to the rules above, or to support more deeply nes /* When chevrons are hidden and only showing an icon (no label), size to 22x22 with centered icon */ .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)), .interactive-session .chat-input-toolbar .chat-input-picker-item.hide-chevrons .action-label:not(:has(.chat-input-picker-label)), -.interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)), -.interactive-session .chat-secondary-input-toolbar .chat-sessionPicker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)) { +.interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label.hide-chevrons:not(:has(.chat-input-picker-label)) { width: 22px; min-width: 22px; height: 22px; @@ -1688,8 +1678,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } .monaco-workbench .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label .codicon-chevron-down, -.monaco-workbench .interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label .codicon-chevron-down, -.monaco-workbench .interactive-session .chat-secondary-input-toolbar .chat-sessionPicker-item .action-label .codicon-chevron-down { +.monaco-workbench .interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label .codicon-chevron-down { font-size: 10px; margin-left: 4px; opacity: 0.75; @@ -1701,10 +1690,8 @@ have to be updated for changes to the rules above, or to support more deeply nes gap: 4px; } -.interactive-session .chat-input-toolbars .chat-sessionPicker-container, -.interactive-session .chat-secondary-toolbar .chat-sessionPicker-container { +.interactive-session .chat-input-toolbars .chat-sessionPicker-container { display: flex; - gap: 4px; max-width: 100%; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index d63a4913168ec..ad63610cafca0 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -71,6 +71,10 @@ export enum ChatConfiguration { ToolConfirmationCarousel = 'chat.tools.confirmationCarousel.enabled', DefaultNewSessionMode = 'chat.newSession.defaultMode', AgentHostClientTools = 'chat.agentHost.clientTools', + + IncrementalRendering = 'chat.experimental.incrementalRendering.enabled', + IncrementalRenderingStyle = 'chat.experimental.incrementalRendering.animationStyle', + IncrementalRenderingBuffering = 'chat.experimental.incrementalRendering.buffering', } /** From b4157a41ae7ebc3d333f5e4fb18a7578d6577126 Mon Sep 17 00:00:00 2001 From: justschen Date: Sun, 19 Apr 2026 16:25:41 -0700 Subject: [PATCH 3/6] don't show border while stuff is working --- src/vs/sessions/browser/media/style.css | 34 +++++++++++-------- .../chat/browser/widget/media/chat.css | 18 ++++++---- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/vs/sessions/browser/media/style.css b/src/vs/sessions/browser/media/style.css index b2b18772bd6c1..c48c606db9cbe 100644 --- a/src/vs/sessions/browser/media/style.css +++ b/src/vs/sessions/browser/media/style.css @@ -31,36 +31,30 @@ @supports (background: color-mix(in srgb, black 0%, white)) { .agent-sessions-workbench.experimental-shell-gradient-background::before { background: - linear-gradient( - to bottom right, + linear-gradient(to bottom right, transparent 0%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 5%, transparent) 58%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 7%, transparent) 82%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 9%, transparent) 100% - ), + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 9%, transparent) 100%), var(--vscode-agents-background); } .monaco-workbench.vs.agent-sessions-workbench.experimental-shell-gradient-background::before { background: - linear-gradient( - to bottom right, + linear-gradient(to bottom right, transparent 0%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 1%, transparent) 45%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 6%, transparent) 75%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 12%, transparent) 100% - ), + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 12%, transparent) 100%), var(--vscode-agents-background); } .monaco-workbench.vs-dark.agent-sessions-workbench.experimental-shell-gradient-background::before { - background: linear-gradient( - to bottom right, - var(--vscode-agents-background) 0%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 7%, var(--vscode-agents-background)) 56%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 10%, var(--vscode-agents-background)) 82%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 13%, var(--vscode-agents-background)) 100% - ); + background: linear-gradient(to bottom right, + var(--vscode-agents-background) 0%, + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 7%, var(--vscode-agents-background)) 56%, + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 10%, var(--vscode-agents-background)) 82%, + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 13%, var(--vscode-agents-background)) 100%); } } @@ -393,6 +387,13 @@ border-color: var(--vscode-agentsChatInput-focusBorder, var(--vscode-focusBorder)) !important; } +/* While the developer-joy animated border is active, suppress the static + border so it doesn't visually conflict with the spinning gradient ring. */ +.interactive-session .chat-input-container.working, +.interactive-session .chat-input-container.working.focused { + border-color: transparent !important; +} + /* Make the Monaco editor inside the chat input transparent so it inherits the chatInput.background */ .agent-sessions-workbench .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor, .agent-sessions-workbench .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor .monaco-editor-background { @@ -461,6 +462,7 @@ .agent-sessions-workbench .interactive-session .interactive-response .interactive-result-code-block { border-color: var(--vscode-editorWidget-border, var(--vscode-widget-border)); } + /* ---- Modal Editor Block ---- */ .agent-sessions-workbench .monaco-modal-editor-block { @@ -545,6 +547,7 @@ } @starting-style { + /* Shared starting values */ .agent-sessions-workbench .part.auxiliarybar, .agent-sessions-workbench .part.panel, @@ -582,6 +585,7 @@ } @media (prefers-reduced-motion: reduce) { + .agent-sessions-workbench .part.auxiliarybar, .agent-sessions-workbench .part.panel, .agent-sessions-workbench .part.chatbar { 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 f2018526ea6af..4ddc63c45e148 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -886,17 +886,21 @@ have to be updated for changes to the rules above, or to support more deeply nes from { --chat-input-anim-angle: 135deg; } + to { --chat-input-anim-angle: 495deg; } } @keyframes chat-input-working-border-glow { - 0%, 100% { + + 0%, + 100% { box-shadow: 0 0 4px 0 color-mix(in srgb, #b44aff 18%, transparent), 0 0 10px 0 color-mix(in srgb, #51a2ff 8%, transparent); } + 50% { box-shadow: 0 0 6px 0 color-mix(in srgb, #4af0c0 22%, transparent), @@ -915,12 +919,11 @@ have to be updated for changes to the rules above, or to support more deeply nes border-radius: inherit; padding: 1px; background: conic-gradient(from var(--chat-input-anim-angle), - #b44aff, - #4af0c0, - #51a2ff, - #4af0c0, - #b44aff - ); + #b44aff, + #4af0c0, + #51a2ff, + #4af0c0, + #b44aff); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); @@ -935,6 +938,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } @media (prefers-reduced-motion: reduce) { + .monaco-workbench .interactive-session .chat-input-container.working, .monaco-workbench .interactive-session .chat-input-container.working::before { animation: none; From ce71bc75de1f3763604475c1b568c5f037d3ad0b Mon Sep 17 00:00:00 2001 From: justschen Date: Mon, 20 Apr 2026 12:43:37 -0700 Subject: [PATCH 4/6] better css --- .../chat/browser/widget/media/chat.css | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) 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 4ddc63c45e148..e5fc865bc04aa 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -909,38 +909,22 @@ have to be updated for changes to the rules above, or to support more deeply nes } .monaco-workbench .interactive-session .chat-input-container.working { - animation: chat-input-working-border-glow 3s ease-in-out infinite; -} - -.monaco-workbench .interactive-session .chat-input-container.working::before { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - padding: 1px; - background: conic-gradient(from var(--chat-input-anim-angle), + border-color: transparent; + background: + linear-gradient(var(--vscode-input-background), var(--vscode-input-background)) padding-box, + conic-gradient(from var(--chat-input-anim-angle), #b44aff, #4af0c0, #51a2ff, #4af0c0, - #b44aff); - -webkit-mask: - linear-gradient(#fff 0 0) content-box, - linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask: - linear-gradient(#fff 0 0) content-box, - linear-gradient(#fff 0 0); - mask-composite: exclude; - pointer-events: none; - z-index: 1; - animation: chat-input-working-border-spin 1.2s linear infinite; + #b44aff) border-box; + animation: + chat-input-working-border-spin 1.2s linear infinite, + chat-input-working-border-glow 3s ease-in-out infinite; } @media (prefers-reduced-motion: reduce) { - - .monaco-workbench .interactive-session .chat-input-container.working, - .monaco-workbench .interactive-session .chat-input-container.working::before { + .monaco-workbench .interactive-session .chat-input-container.working { animation: none; } } From 0eb155ef89b34bf4993c5e178f1191c7dc21fb7b Mon Sep 17 00:00:00 2001 From: justschen Date: Mon, 20 Apr 2026 16:39:23 -0700 Subject: [PATCH 5/6] add color components and cleanup --- .../lib/stylelint/vscode-known-variables.json | 3 +++ .../contrib/chat/browser/chat.contribution.ts | 6 +++--- .../chatProgressContentPart.ts | 3 ++- .../chatThinkingContentPart.ts | 3 ++- .../chat/browser/widget/chatListRenderer.ts | 5 +++-- .../contrib/chat/browser/widget/chatWidget.ts | 4 ++-- .../chat/browser/widget/media/chat.css | 20 +++++++++---------- .../contrib/chat/common/constants.ts | 2 +- .../contrib/chat/common/widget/chatColors.ts | 15 ++++++++++++++ 9 files changed, 41 insertions(+), 20 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 16ab5f7914eae..7ddfbbbe59072 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -61,6 +61,9 @@ "--vscode-chat-avatarForeground", "--vscode-chat-checkpointSeparator", "--vscode-chat-editedFileForeground", + "--vscode-chat-inputWorkingBorderColor1", + "--vscode-chat-inputWorkingBorderColor2", + "--vscode-chat-inputWorkingBorderColor3", "--vscode-chat-linesAddedForeground", "--vscode-chat-linesRemovedForeground", "--vscode-chat-requestBackground", diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 0b51a987981da..3b6925bf4492c 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -638,12 +638,12 @@ configurationRegistry.registerConfiguration({ [ChatConfiguration.ChatPersistentProgressEnabled]: { type: 'boolean', default: product.quality !== 'stable', - description: nls.localize('chat.persistentProgress.enabled', "Show elapsed time and token usage in chat response progress."), + description: nls.localize('chat.persistentProgress.enabled', "Always show progress in chat."), }, - [ChatConfiguration.DeveloperJoy]: { + [ChatConfiguration.ProgressBorder]: { type: 'boolean', default: false, - description: nls.localize('chat.developerJoy.enabled', "Show an animated gradient border and soft glow around the chat input while the agent is working or thinking."), + description: nls.localize('chat.progressBorder.enabled', "Show an animated gradient border and soft glow around the chat input while the agent is working or thinking. When enabled, this overrides {0} to be off.", '`#chat.persistentProgress.enabled#`'), }, [ChatConfiguration.NotifyWindowOnResponseReceived]: { type: 'string', diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts index dacfc4d1184a8..ac8bcdf076c83 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatProgressContentPart.ts @@ -186,7 +186,8 @@ export class ChatWorkingProgressContentPart extends Disposable implements IChatC ) { super(); this.explicitContent = workingProgress.content; - const persistentProgressEnabled = configurationService.getValue(ChatConfiguration.ChatPersistentProgressEnabled) !== false; + const persistentProgressEnabled = configurationService.getValue(ChatConfiguration.ChatPersistentProgressEnabled) !== false + && configurationService.getValue(ChatConfiguration.ProgressBorder) !== true; if (persistentProgressEnabled) { const pool = buildPhrasePool(defaultThinkingMessages, configurationService); this.label = pool[Math.floor(Math.random() * pool.length)]; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts index 05a004c927299..a4ac737f62398 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatThinkingContentPart.ts @@ -304,7 +304,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen this.id = content.id; this.content = content; this.allThinkingParts.push(content); - this.showProgressDetails = this.configurationService.getValue(ChatConfiguration.ChatPersistentProgressEnabled) !== false; + this.showProgressDetails = this.configurationService.getValue(ChatConfiguration.ChatPersistentProgressEnabled) !== false + && this.configurationService.getValue(ChatConfiguration.ProgressBorder) !== true; const configuredMode = this.configurationService.getValue('chat.agent.thinkingStyle') ?? ThinkingDisplayMode.Collapsed; this.fixedScrollingMode = configuredMode === ThinkingDisplayMode.FixedScrolling; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts index b9d1f1d95a35e..4a1c20895e239 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts @@ -1061,7 +1061,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer(ChatConfiguration.ChatPersistentProgressEnabled) !== false; + const showProgressDetails = this.configService.getValue(ChatConfiguration.ChatPersistentProgressEnabled) !== false + && this.configService.getValue(ChatConfiguration.ProgressBorder) !== true; if (element.isComplete) { return undefined; } @@ -1227,7 +1228,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer(ChatConfiguration.ChatPersistentProgressEnabled) !== false) { + if (element.isComplete && this.configService.getValue(ChatConfiguration.ChatPersistentProgressEnabled) !== false && this.configService.getValue(ChatConfiguration.ProgressBorder) !== true) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 25bd45feba143..9e9cbfbcbdaae 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -465,7 +465,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.updateChatViewVisibility(); } } - if (e.affectsConfiguration(ChatConfiguration.DeveloperJoy)) { + if (e.affectsConfiguration(ChatConfiguration.ProgressBorder)) { this.updateWorkingProgressBorder(); } })); @@ -668,7 +668,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!inputContainer) { return; } - const enabled = this.configurationService.getValue(ChatConfiguration.DeveloperJoy) === true; + const enabled = this.configurationService.getValue(ChatConfiguration.ProgressBorder) === true; const inProgress = !!this.viewModel?.model.requestInProgress.get(); inputContainer.classList.toggle('working', enabled && inProgress); } 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 e5fc865bc04aa..6579df641c622 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -872,7 +872,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } /* Animated gradient border shown around the chat input while the agent is - working or thinking. Toggled by the `chat.developerJoy.enabled` + working or thinking. Toggled by the `chat.progressBorder.enabled` setting and the chat widget's request-in-progress state. The ring is rendered inside the container (using a mask trick) so it works whether or not the parent has `overflow: hidden`. */ @@ -897,14 +897,14 @@ have to be updated for changes to the rules above, or to support more deeply nes 0%, 100% { box-shadow: - 0 0 4px 0 color-mix(in srgb, #b44aff 18%, transparent), - 0 0 10px 0 color-mix(in srgb, #51a2ff 8%, transparent); + 0 0 4px 0 color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor1) 18%, transparent), + 0 0 10px 0 color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor3) 8%, transparent); } 50% { box-shadow: - 0 0 6px 0 color-mix(in srgb, #4af0c0 22%, transparent), - 0 0 14px 1px color-mix(in srgb, #b44aff 12%, transparent); + 0 0 6px 0 color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor2) 22%, transparent), + 0 0 14px 1px color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor1) 12%, transparent); } } @@ -913,11 +913,11 @@ have to be updated for changes to the rules above, or to support more deeply nes background: linear-gradient(var(--vscode-input-background), var(--vscode-input-background)) padding-box, conic-gradient(from var(--chat-input-anim-angle), - #b44aff, - #4af0c0, - #51a2ff, - #4af0c0, - #b44aff) border-box; + var(--vscode-chat-inputWorkingBorderColor1), + var(--vscode-chat-inputWorkingBorderColor2), + var(--vscode-chat-inputWorkingBorderColor3), + var(--vscode-chat-inputWorkingBorderColor2), + var(--vscode-chat-inputWorkingBorderColor1)) border-box; animation: chat-input-working-border-spin 1.2s linear infinite, chat-input-working-border-glow 3s ease-in-out infinite; diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index ad63610cafca0..f26809501a5f4 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -48,7 +48,7 @@ export enum ChatConfiguration { ChatViewProgressBadgeEnabled = 'chat.viewProgressBadge.enabled', ChatContextUsageEnabled = 'chat.contextUsage.enabled', ChatPersistentProgressEnabled = 'chat.persistentProgress.enabled', - DeveloperJoy = 'chat.developerJoy.enabled', + ProgressBorder = 'chat.progressBorder.enabled', SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled', GeneralPurposeAgentEnabled = 'chat.generalPurposeAgent.enabled', SubagentsAllowInvocationsFromSubagents = 'chat.subagents.allowInvocationsFromSubagents', diff --git a/src/vs/workbench/contrib/chat/common/widget/chatColors.ts b/src/vs/workbench/contrib/chat/common/widget/chatColors.ts index bb256ebba1988..7b56d9af30699 100644 --- a/src/vs/workbench/contrib/chat/common/widget/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/widget/chatColors.ts @@ -87,3 +87,18 @@ export const chatThinkingShimmer = registerColor( 'chat.thinkingShimmer', { dark: '#ffffff', light: '#000000', hcDark: '#ffffff', hcLight: '#000000' }, localize('chat.thinkingShimmer', 'Shimmer highlight for thinking/working labels.'), true); + +export const chatInputWorkingBorderColor1 = registerColor( + 'chat.inputWorkingBorderColor1', + { dark: '#b44aff', light: '#b44aff', hcDark: '#b44aff', hcLight: '#b44aff' }, + localize('chat.inputWorkingBorderColor1', 'First color stop of the animated chat input border shown while a request is in flight.'), true); + +export const chatInputWorkingBorderColor2 = registerColor( + 'chat.inputWorkingBorderColor2', + { dark: '#4af0c0', light: '#4af0c0', hcDark: '#4af0c0', hcLight: '#4af0c0' }, + localize('chat.inputWorkingBorderColor2', 'Second color stop of the animated chat input border shown while a request is in flight.'), true); + +export const chatInputWorkingBorderColor3 = registerColor( + 'chat.inputWorkingBorderColor3', + { dark: '#51a2ff', light: '#51a2ff', hcDark: '#51a2ff', hcLight: '#51a2ff' }, + localize('chat.inputWorkingBorderColor3', 'Third color stop of the animated chat input border shown while a request is in flight.'), true); From a98022dbfd6b58bac2df26eb3977a752c466fbcc Mon Sep 17 00:00:00 2001 From: justschen Date: Mon, 20 Apr 2026 20:26:31 -0700 Subject: [PATCH 6/6] progress border --- .../lib/stylelint/vscode-known-variables.json | 1 + src/vs/sessions/browser/media/style.css | 33 +++++++++++-------- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../chat/browser/widget/media/chat.css | 14 ++++++-- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 7ddfbbbe59072..9d1d99a7a2037 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -1040,6 +1040,7 @@ "--animation-angle", "--animation-opacity", "--chat-input-anim-angle", + "--chat-input-working-fill", "--chat-setup-dialog-glow-angle", "--vscode-chat-font-family", "--vscode-chat-font-size-body-l", diff --git a/src/vs/sessions/browser/media/style.css b/src/vs/sessions/browser/media/style.css index 4b2ec8e0d70f1..53f46f7cf228f 100644 --- a/src/vs/sessions/browser/media/style.css +++ b/src/vs/sessions/browser/media/style.css @@ -31,30 +31,36 @@ @supports (background: color-mix(in srgb, black 0%, white)) { .agent-sessions-workbench.experimental-shell-gradient-background::before { background: - linear-gradient(to bottom right, + linear-gradient( + to bottom right, transparent 0%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 5%, transparent) 58%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 7%, transparent) 82%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 9%, transparent) 100%), + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 9%, transparent) 100% + ), var(--vscode-agents-background); } .monaco-workbench.vs.agent-sessions-workbench.experimental-shell-gradient-background::before { background: - linear-gradient(to bottom right, + linear-gradient( + to bottom right, transparent 0%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 1%, transparent) 45%, color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 6%, transparent) 75%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 12%, transparent) 100%), + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 12%, transparent) 100% + ), var(--vscode-agents-background); } .monaco-workbench.vs-dark.agent-sessions-workbench.experimental-shell-gradient-background::before { - background: linear-gradient(to bottom right, - var(--vscode-agents-background) 0%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 7%, var(--vscode-agents-background)) 56%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 10%, var(--vscode-agents-background)) 82%, - color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 13%, var(--vscode-agents-background)) 100%); + background: linear-gradient( + to bottom right, + var(--vscode-agents-background) 0%, + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 7%, var(--vscode-agents-background)) 56%, + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 10%, var(--vscode-agents-background)) 82%, + color-mix(in srgb, var(--vscode-agentsGradient-tintColor) 13%, var(--vscode-agents-background)) 100% + ); } } @@ -381,6 +387,8 @@ border-color: var(--vscode-agentsChatInput-border) !important; background-color: var(--vscode-agentsChatInput-background); color: var(--vscode-agentsChatInput-foreground); + /* Preserve the agents-app input background under the developer-joy ring. */ + --chat-input-working-fill: var(--vscode-agentsChatInput-background); } .agent-sessions-workbench .interactive-session .chat-input-container.focused { @@ -389,8 +397,8 @@ /* While the developer-joy animated border is active, suppress the static border so it doesn't visually conflict with the spinning gradient ring. */ -.interactive-session .chat-input-container.working, -.interactive-session .chat-input-container.working.focused { +.agent-sessions-workbench .interactive-session .chat-input-container.working, +.agent-sessions-workbench .interactive-session .chat-input-container.working.focused { border-color: transparent !important; } @@ -462,7 +470,6 @@ .agent-sessions-workbench .interactive-session .interactive-response .interactive-result-code-block { border-color: var(--vscode-editorWidget-border, var(--vscode-widget-border)); } - /* ---- Modal Editor Block ---- */ .agent-sessions-workbench .monaco-modal-editor-block { @@ -547,7 +554,6 @@ } @starting-style { - /* Shared starting values */ .agent-sessions-workbench .part.auxiliarybar, .agent-sessions-workbench .part.panel, @@ -585,7 +591,6 @@ } @media (prefers-reduced-motion: reduce) { - .agent-sessions-workbench .part.auxiliarybar, .agent-sessions-workbench .part.panel, .agent-sessions-workbench .part.chatbar { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 3b6925bf4492c..2774b967bfa67 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -643,7 +643,7 @@ configurationRegistry.registerConfiguration({ [ChatConfiguration.ProgressBorder]: { type: 'boolean', default: false, - description: nls.localize('chat.progressBorder.enabled', "Show an animated gradient border and soft glow around the chat input while the agent is working or thinking. When enabled, this overrides {0} to be off.", '`#chat.persistentProgress.enabled#`'), + markdownDescription: nls.localize('chat.progressBorder.enabled', "Show an animated gradient border around the chat input while the agent is working or thinking. When enabled, this overrides {0} to be off.", '`#chat.persistentProgress.enabled#`'), }, [ChatConfiguration.NotifyWindowOnResponseReceived]: { type: 'string', 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 6579df641c622..05f8a04272fa6 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -874,8 +874,9 @@ have to be updated for changes to the rules above, or to support more deeply nes /* Animated gradient border shown around the chat input while the agent is working or thinking. Toggled by the `chat.progressBorder.enabled` setting and the chat widget's request-in-progress state. The ring is - rendered inside the container (using a mask trick) so it works whether or - not the parent has `overflow: hidden`. */ + rendered using layered `background-image` + `background-clip` + (`padding-box`/`border-box`), so it traces the input's outer corner + radius and isn't clipped by `overflow: hidden` on the parent. */ @property --chat-input-anim-angle { syntax: ''; inherits: false; @@ -910,8 +911,15 @@ have to be updated for changes to the rules above, or to support more deeply nes .monaco-workbench .interactive-session .chat-input-container.working { border-color: transparent; + /* The padding-box layer fills the input interior. It defaults to the + standard input background, but each host can override + `--chat-input-working-fill` to keep its own background color (e.g. the + Sessions workbench sets it to `--vscode-agentsChatInput-background`). + The conic-gradient layer is clipped to the border box so it paints + exactly where the (transparent) border lives. */ background: - linear-gradient(var(--vscode-input-background), var(--vscode-input-background)) padding-box, + linear-gradient(var(--chat-input-working-fill, var(--vscode-input-background)), + var(--chat-input-working-fill, var(--vscode-input-background))) padding-box, conic-gradient(from var(--chat-input-anim-angle), var(--vscode-chat-inputWorkingBorderColor1), var(--vscode-chat-inputWorkingBorderColor2),