diff --git a/extensions/theme-2026/themes/2026-dark.json b/extensions/theme-2026/themes/2026-dark.json index c946703eea8..40140f3b6ac 100644 --- a/extensions/theme-2026/themes/2026-dark.json +++ b/extensions/theme-2026/themes/2026-dark.json @@ -5,7 +5,7 @@ "type": "dark", "colors": { "foreground": "#bfbfbf", - "disabledForeground": "#444444", + "disabledForeground": "#666666", "errorForeground": "#f48771", "descriptionForeground": "#999999", "icon.foreground": "#888888", diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts index 89d29c70882..8270198d110 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts @@ -134,7 +134,7 @@ export interface IAgentSessionsControl { export const agentSessionReadIndicatorForeground = registerColor( 'agentSessionReadIndicator.foreground', - { dark: transparent(foreground, 0.15), light: transparent(foreground, 0.15), hcDark: null, hcLight: null }, + { dark: transparent(foreground, 0.2), light: transparent(foreground, 0.2), hcDark: null, hcLight: null }, localize('agentSessionReadIndicatorForeground', "Foreground color for the read indicator in an agent session.") ); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts index f58192d8d37..9a030954df4 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts @@ -8,7 +8,7 @@ import { ChatContextKeys } from '../../common/actions/chatContextKeys.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; -import { $, append, EventHelper } from '../../../../../base/browser/dom.js'; +import { $, addDisposableListener, append, EventHelper } from '../../../../../base/browser/dom.js'; import { AgentSessionSection, IAgentSession, IAgentSessionSection, IAgentSessionsModel, IMarshalledAgentSessionContext, isAgentSession, isAgentSessionSection } from './agentSessionsModel.js'; import { AgentSessionListItem, AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionSectionRenderer, AgentSessionsSorter, IAgentSessionsFilter, IAgentSessionsSorterOptions } from './agentSessionsViewer.js'; import { FuzzyScore } from '../../../../../base/common/filters.js'; @@ -176,6 +176,14 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo ChatContextKeys.agentSessionsViewerFocused.bindTo(list.contextKeyService); + // Track mouse vs keyboard focus to suppress focus outlines on mouse clicks + this._register(addDisposableListener(this.sessionsContainer!, 'mousedown', () => { + this.sessionsContainer!.classList.add('mouse-focused'); + })); + this._register(addDisposableListener(this.sessionsContainer!, 'keydown', () => { + this.sessionsContainer!.classList.remove('mouse-focused'); + })); + const model = this.agentSessionsService.model; this._register(this.options.filter.onDidChange(async () => { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index a2c7037a622..f52968a1ccd 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -56,6 +56,9 @@ interface IAgentSessionItemTemplate { // Column 2 Row 1 readonly title: IconLabel; + readonly statusContainer: HTMLElement; + readonly statusProviderIcon: HTMLElement; + readonly statusTime: HTMLElement; readonly titleToolbar: MenuWorkbenchToolBar; // Column 2 Row 2 @@ -64,12 +67,9 @@ interface IAgentSessionItemTemplate { readonly diffRemovedSpan: HTMLSpanElement; readonly badge: HTMLElement; + readonly separator: HTMLElement; readonly description: HTMLElement; - readonly statusContainer: HTMLElement; - readonly statusProviderIcon: HTMLElement; - readonly statusTime: HTMLElement; - readonly contextKeyService: IContextKeyService; readonly elementDisposable: DisposableStore; readonly disposables: IDisposable; @@ -111,6 +111,10 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre h('div.agent-session-main-col', [ h('div.agent-session-title-row', [ h('div.agent-session-title@title'), + h('div.agent-session-status@statusContainer', [ + h('span.agent-session-status-provider-icon@statusProviderIcon'), + h('span.agent-session-status-time@statusTime') + ]), h('div.agent-session-title-toolbar@titleToolbar'), ]), h('div.agent-session-details-row', [ @@ -120,11 +124,8 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre h('span.agent-session-diff-removed@removedSpan') ]), h('div.agent-session-badge@badge'), + h('span.agent-session-separator@separator'), h('div.agent-session-description@description'), - h('div.agent-session-status@statusContainer', [ - h('span.agent-session-status-provider-icon@statusProviderIcon'), - h('span.agent-session-status-time@statusTime') - ]) ]) ]) ] @@ -147,6 +148,7 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre diffAddedSpan: elements.addedSpan, diffRemovedSpan: elements.removedSpan, badge: elements.badge, + separator: elements.separator, description: elements.description, statusContainer: elements.statusContainer, statusProviderIcon: elements.statusProviderIcon, @@ -216,6 +218,9 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre this.renderDescription(session, template, hasBadge); } + // Separator (dot between badge and description) + template.separator.classList.toggle('has-separator', hasBadge && !hasDiff); + // Status this.renderStatus(session, template); @@ -307,8 +312,8 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre const duration = this.toDuration(session.element.timing.lastRequestStarted, session.element.timing.lastRequestEnded, false, true); template.description.textContent = session.element.status === AgentSessionStatus.Failed ? - localize('chat.session.status.failedAfter', "Failed after {0}.", duration) : - localize('chat.session.status.completedAfter', "Completed in {0}.", duration); + localize('chat.session.status.failedAfter', "Failed after {0}", duration) : + localize('chat.session.status.completedAfter', "Completed in {0}", duration); } else { template.description.textContent = session.element.status === AgentSessionStatus.Failed ? localize('chat.session.status.failed', "Failed") : @@ -497,7 +502,7 @@ export class AgentSessionSectionRenderer implements ICompressibleTreeRenderer { - static readonly ITEM_HEIGHT = 52; + static readonly ITEM_HEIGHT = 40; static readonly SECTION_HEIGHT = 26; getHeight(element: AgentSessionListItem): number { @@ -708,10 +713,10 @@ export class AgentSessionsDataSource implements IAsyncDataSource= startOfYesterday) { - return localize('date.fromNow.days.singular.ago', '1 day ago'); + return localize('date.fromNow.days.singular', '1 day'); } if (sessionTime < startOfYesterday && sessionTime >= startOfTwoDaysAgo) { - return localize('date.fromNow.days.multiple.ago', '2 days ago'); + return localize('date.fromNow.days.multiple', '2 days'); } - return fromNow(sessionTime, true); + return fromNow(sessionTime, false); } export class AgentSessionsIdentityProvider implements IIdentityProvider { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css index a4a142f69e7..48bb4ade859 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css @@ -9,6 +9,20 @@ height: 100%; min-height: 0; + .monaco-scrollable-element { + padding: 0 6px; + } + + .monaco-list-row { + border-radius: 6px; + } + + /* Hide focus outlines on mouse click, preserve for keyboard navigation */ + &.mouse-focused .monaco-list-row.focused, + &.mouse-focused .monaco-list-row.focused.selected { + outline: none !important; + } + .monaco-list-row .force-no-twistie { display: none !important; } @@ -21,55 +35,60 @@ color: unset; } } + } + .monaco-list:focus .monaco-list-row.selected .agent-session-details-row { .agent-session-diff-container { - background-color: unset; - outline: 1px solid var(--vscode-agentSessionSelectedBadge-border); - .agent-session-diff-added, .agent-session-diff-removed { - color: unset; + color: unset; } } - - .agent-session-badge { - background-color: unset; - outline: 1px solid var(--vscode-agentSessionSelectedBadge-border); - } } - .monaco-list:not(:focus) .monaco-list-row.selected .agent-session-details-row .agent-session-diff-container, - .monaco-list:not(:focus) .monaco-list-row.selected .agent-session-details-row .agent-session-badge { - outline: 1px solid var(--vscode-agentSessionSelectedUnfocusedBadge-border); + .monaco-list-row.selected .agent-session-title { + color: unset; + } + + .monaco-list-row.selected .agent-session-status { + color: unset; + } + + .monaco-list:not(:focus) .monaco-list-row.selected .agent-session-details-row { + color: var(--vscode-descriptionForeground); } .monaco-list-row .agent-session-title-toolbar { /* for the absolute positioning of the toolbar below */ position: relative; height: 16px; + display: none; .monaco-toolbar { /* this is required because the overal height (including the padding needed for hover feedback) would push down the title otherwise */ position: relative; right: 0; top: 0; - display: none; } } - .monaco-list-row:hover .agent-session-title-toolbar, - .monaco-list-row.focused .agent-session-title-toolbar { + /* On hover or keyboard focus: show toolbar, hide status */ + .monaco-list-row:hover, + &:not(.mouse-focused) .monaco-list-row.focused { - .monaco-toolbar { + .agent-session-title-toolbar { display: block; } + + .agent-session-status { + display: none; + } } .agent-session-item { display: flex; flex-direction: row; - /* to offset from possible scrollbar */ - padding: 8px 12px 8px 8px; + padding: 4px 6px; &.archived { color: var(--vscode-descriptionForeground); @@ -85,10 +104,16 @@ .agent-session-icon-col { display: flex; align-items: flex-start; + line-height: 16px; .agent-session-icon { flex-shrink: 0; - font-size: 16px; + font-size: 12px; + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; &.codicon.codicon-session-in-progress { color: var(--vscode-textLink-foreground); @@ -113,24 +138,26 @@ } .agent-session-main-col { - padding-left: 8px; + padding-left: 6px; } .agent-session-title-row, .agent-session-details-row { display: flex; align-items: center; - line-height: 16px; } .agent-session-title-row { - padding-bottom: 4px; + line-height: 16px; + padding-bottom: 2px; } .agent-session-details-row { gap: 4px; - font-size: 12px; - color: var(--vscode-descriptionForeground); + font-size: 11px; + line-height: 14px; + max-height: 14px; + color: var(--vscode-disabledForeground); .rendered-markdown { p { @@ -150,11 +177,8 @@ .agent-session-diff-container, .agent-session-badge { - background-color: var(--vscode-toolbar-hoverBackground); font-weight: 500; - padding: 0 4px; font-variant-numeric: tabular-nums; - border-radius: 5px; overflow: hidden; } @@ -175,6 +199,18 @@ } } + .agent-session-separator { + display: none; + + &.has-separator { + display: inline; + + &::before { + content: '\00B7'; + } + } + } + .agent-session-badge { p { @@ -186,7 +222,7 @@ } .codicon { - font-size: 12px; + font-size: 11px; } } } @@ -195,17 +231,28 @@ .agent-session-description { /* push other items to the end */ flex: 1; - text-overflow: ellipsis; overflow: hidden; + white-space: nowrap; + margin-right: 16px; + mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); + -webkit-mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); + } + + .agent-session-title { + font-size: 12px; + color: var(--vscode-descriptionForeground); } .agent-session-status { display: flex; align-items: center; font-variant-numeric: tabular-nums; + font-size: 11px; + color: var(--vscode-disabledForeground); + white-space: nowrap; .agent-session-status-provider-icon { - font-size: 12px; + font-size: 11px; margin-right: 4px; &.hidden { @@ -219,11 +266,10 @@ display: flex; align-items: center; font-size: 11px; + font-weight: 500; color: var(--vscode-descriptionForeground); - text-transform: uppercase; - letter-spacing: 0.5px; /* align with session item padding */ - padding: 0 12px 0 8px; + padding: 0 6px; .agent-session-section-label { flex: 1; diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css index 7884cf2c512..791420b8443 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatViewPane.css @@ -100,7 +100,7 @@ } .agent-sessions-new-button-container { - padding: 8px 12px; + padding: 8px; } } @@ -127,7 +127,7 @@ } .agent-session-section { - padding: 0 12px 0 20px; + padding: 0 12px; } /* Right position: symmetric padding */ diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts index b99a9c3138d..ea79ad2f353 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts @@ -54,21 +54,21 @@ suite('sessionDateFromNow', () => { const ONE_DAY = 24 * 60 * 60 * 1000; - test('returns "1 day ago" for yesterday', () => { + test('returns "1 day" for yesterday', () => { const now = Date.now(); const startOfToday = new Date(now).setHours(0, 0, 0, 0); // Time in the middle of yesterday const yesterday = startOfToday - ONE_DAY / 2; - assert.strictEqual(sessionDateFromNow(yesterday), '1 day ago'); + assert.strictEqual(sessionDateFromNow(yesterday), '1 day'); }); - test('returns "2 days ago" for two days ago', () => { + test('returns "2 days" for two days ago', () => { const now = Date.now(); const startOfToday = new Date(now).setHours(0, 0, 0, 0); const startOfYesterday = startOfToday - ONE_DAY; // Time in the middle of two days ago const twoDaysAgo = startOfYesterday - ONE_DAY / 2; - assert.strictEqual(sessionDateFromNow(twoDaysAgo), '2 days ago'); + assert.strictEqual(sessionDateFromNow(twoDaysAgo), '2 days'); }); test('returns fromNow result for today', () => {