Skip to content

Commit 2d4f528

Browse files
sandy081Copilot
andcommitted
sessions: address chat header review feedback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0aeca2f commit 2d4f528

6 files changed

Lines changed: 64 additions & 7 deletions

File tree

src/vs/sessions/browser/parts/chatCompositeBar.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { MenuItemAction } from '../../../platform/actions/common/actions.js';
4343
import { ChatPillActionViewItem } from '../../../workbench/browser/chatPills.js';
4444
import { SessionActivatingActionRunner } from '../sessionActionRunner.js';
4545
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
46+
import { getSessionConversationStatusAriaLabel } from '../sessionConversationGroups.js';
4647

4748
interface IChatTab {
4849
readonly chat: IChat;
@@ -338,7 +339,9 @@ export class ChatCompositeBar extends Disposable {
338339
const labelEl = $('.chat-composite-bar-tab-label.modern-ui-editor-tab-label');
339340
this._tabDisposables.add(autorun(reader => {
340341
const title = chat.title.read(reader);
342+
const status = chat.status.read(reader);
341343
labelEl.textContent = title;
344+
tab.setAttribute('aria-label', localize('chatTabAriaLabel', "{0}, {1}", title, getSessionConversationStatusAriaLabel(status)));
342345
}));
343346

344347
// Lock icon shown for read-only (non-interactive) chats.

src/vs/sessions/browser/parts/media/chatCompositeBar.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@
190190
border-bottom: var(--vscode-strokeThickness) solid color-mix(in srgb, var(--session-view-foreground, var(--chat-tab-active-foreground)) 12%, transparent);
191191
}
192192

193+
:is(.hc-black, .hc-light) .chat-groups-view.single-group .chat-composite-bar-tabs-row {
194+
border-bottom-color: var(--vscode-contrastBorder);
195+
}
196+
193197
/* The ScrollableElement wrapper holding the tabs is the shrinkable flex item */
194198
.chat-composite-bar-tabs-row > .monaco-scrollable-element {
195199
flex: 0 1 auto;

src/vs/sessions/browser/sessionConversationGroups.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import { hash } from '../../base/common/hash.js';
77
import { IExtUri } from '../../base/common/resources.js';
88
import { URI } from '../../base/common/uri.js';
9-
import { ChatOriginKind, IChat } from '../services/sessions/common/session.js';
9+
import { localize } from '../../nls.js';
10+
import { ChatOriginKind, IChat, SessionStatus } from '../services/sessions/common/session.js';
1011

1112
export const SESSION_CONVERSATION_CHATS_GROUP = '1_chats';
1213
export const SESSION_CONVERSATION_SUBAGENTS_GROUP = '2_subagents';
@@ -15,6 +16,25 @@ export function getSessionConversationActionId(sessionId: string, chatResource:
1516
return `sessions.openChat.${sessionId}.${hash(chatResource.toString())}`;
1617
}
1718

19+
export function getSessionConversationStatusLabel(status: SessionStatus): string {
20+
switch (status) {
21+
case SessionStatus.Untitled:
22+
return localize('sessionConversationStatus.new', "New");
23+
case SessionStatus.InProgress:
24+
return localize('sessionConversationStatus.inProgress', "In Progress");
25+
case SessionStatus.NeedsInput:
26+
return localize('sessionConversationStatus.needsInput', "Input Needed");
27+
case SessionStatus.Completed:
28+
return localize('sessionConversationStatus.completed', "Completed");
29+
case SessionStatus.Error:
30+
return localize('sessionConversationStatus.failed', "Failed");
31+
}
32+
}
33+
34+
export function getSessionConversationStatusAriaLabel(status: SessionStatus): string {
35+
return localize('sessionConversationStatus.ariaLabel', "State: {0}", getSessionConversationStatusLabel(status));
36+
}
37+
1838
/** Returns the contributed menu group for a chat in the scoped session. */
1939
export function getSessionConversationGroupId(chat: IChat, activeChat: IChat, extUri: IExtUri): string | undefined {
2040
if (chat.origin?.kind === ChatOriginKind.Tool) {

src/vs/sessions/test/browser/chatCompositeBar.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,12 @@ suite('Sessions - ChatCompositeBar', () => {
133133
hasFill: tab.querySelector(':scope > .chat-composite-bar-tab-fill.modern-ui-editor-tab-fill') !== null,
134134
hasLabel: tab.querySelector(':scope > .chat-composite-bar-tab-label.modern-ui-editor-tab-label') !== null,
135135
hasActions: tab.querySelector(':scope > .chat-composite-bar-tab-actions') !== null,
136+
ariaLabel: tab.getAttribute('aria-label'),
136137
})),
137138
}, {
138139
tabs: [
139-
{ hasSharedPresentation: true, hasFill: true, hasLabel: true, hasActions: false },
140-
{ hasSharedPresentation: true, hasFill: true, hasLabel: true, hasActions: true },
140+
{ hasSharedPresentation: true, hasFill: true, hasLabel: true, hasActions: false, ariaLabel: 'Main Chat, State: Completed' },
141+
{ hasSharedPresentation: true, hasFill: true, hasLabel: true, hasActions: true, ariaLabel: 'Secondary Chat, State: Completed' },
141142
],
142143
});
143144
});

src/vs/sessions/test/browser/chatGroupsView.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import assert from 'assert';
77
import { mainWindow } from '../../../base/browser/window.js';
8+
import { DeferredPromise } from '../../../base/common/async.js';
89
import { Event } from '../../../base/common/event.js';
910
import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
1011
import { constObservable, derived, IObservable, ISettableObservable, observableValue } from '../../../base/common/observable.js';
@@ -115,6 +116,7 @@ class TestActiveSession extends mock<IActiveSession>() {
115116

116117
class TestSessionsService extends mock<ISessionsService>() {
117118
override readonly activeSession = observableValue<IActiveSession | undefined>(this, undefined);
119+
newChatGate: Promise<void> | undefined;
118120

119121
override async openChat(session: ISession, chatUri: URI): Promise<void> {
120122
if (!(session instanceof TestActiveSession)) {
@@ -135,6 +137,7 @@ class TestSessionsService extends mock<ISessionsService>() {
135137
if (!(session instanceof TestActiveSession)) {
136138
return;
137139
}
140+
await this.newChatGate;
138141
const chat = createChat(`new-${session.allChats.get().length}`, SessionStatus.Untitled);
139142
session.allChats.set([...session.allChats.get(), chat], undefined);
140143
session.visibleChatTabs.set([...session.visibleChatTabs.get(), chat], undefined);
@@ -399,8 +402,8 @@ suite('Sessions - ChatGroupsView', () => {
399402
});
400403
});
401404

402-
test('new chat from the tab bar is assigned to its group', async () => {
403-
const { view } = createHarness(disposables);
405+
test('new chat remains assigned to the group where creation started', async () => {
406+
const { sessionsService, view } = createHarness(disposables);
404407
const main = createChat('main');
405408
const secondary = createChat('secondary');
406409
const session = new TestActiveSession([main, secondary]);
@@ -409,18 +412,25 @@ suite('Sessions - ChatGroupsView', () => {
409412
view.focusAdjacentGroup('previous');
410413
const groups = Array.from(view.element.querySelectorAll<HTMLElement>('.chat-group-view'));
411414
const mainGroup = groups.find(group => group.querySelector<HTMLElement>('.chat-composite-bar-tab')?.dataset.chatResource === main.resource.toString())!;
415+
const gate = new DeferredPromise<void>();
416+
sessionsService.newChatGate = gate.p;
412417

413418
mainGroup.querySelector<HTMLElement>('.chat-composite-bar-new-chat .action-label')!.click();
419+
view.focusAdjacentGroup('next');
420+
gate.complete();
421+
await gate.p;
414422
await Promise.resolve();
415423
await Promise.resolve();
416424

417425
const newChat = session.activeChat.get();
418426
assert.deepStrictEqual({
419427
mainGroupTabs: Array.from(mainGroup.querySelectorAll<HTMLElement>('.chat-composite-bar-tab')).map(tab => tab.dataset.chatResource),
420428
secondaryGroupTabs: Array.from(groups.find(group => group !== mainGroup)!.querySelectorAll<HTMLElement>('.chat-composite-bar-tab')).map(tab => tab.dataset.chatResource),
429+
focusInMainGroup: mainGroup.contains(mainWindow.document.activeElement),
421430
}, {
422431
mainGroupTabs: [main.resource.toString(), newChat.resource.toString()],
423432
secondaryGroupTabs: [secondary.resource.toString()],
433+
focusInMainGroup: true,
424434
});
425435
});
426436

src/vs/sessions/test/browser/sessionConversationGroups.test.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { extUri } from '../../../base/common/resources.js';
88
import { URI } from '../../../base/common/uri.js';
99
import { mock } from '../../../base/test/common/mock.js';
1010
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js';
11-
import { getSessionConversationGroupId, SESSION_CONVERSATION_CHATS_GROUP, SESSION_CONVERSATION_SUBAGENTS_GROUP } from '../../browser/sessionConversationGroups.js';
12-
import { ChatOriginKind, IChat, IChatOrigin } from '../../services/sessions/common/session.js';
11+
import { getSessionConversationGroupId, getSessionConversationStatusAriaLabel, getSessionConversationStatusLabel, SESSION_CONVERSATION_CHATS_GROUP, SESSION_CONVERSATION_SUBAGENTS_GROUP } from '../../browser/sessionConversationGroups.js';
12+
import { ChatOriginKind, IChat, IChatOrigin, SessionStatus } from '../../services/sessions/common/session.js';
1313

1414
function createChat(id: string, origin?: IChatOrigin): IChat {
1515
return new class extends mock<IChat>() {
@@ -36,4 +36,23 @@ suite('Sessions - Session conversation groups', () => {
3636
]);
3737
});
3838

39+
test('localizes every conversation state for accessibility', () => {
40+
assert.deepStrictEqual([
41+
SessionStatus.Untitled,
42+
SessionStatus.InProgress,
43+
SessionStatus.NeedsInput,
44+
SessionStatus.Completed,
45+
SessionStatus.Error,
46+
].map(status => ({
47+
label: getSessionConversationStatusLabel(status),
48+
ariaLabel: getSessionConversationStatusAriaLabel(status),
49+
})), [
50+
{ label: 'New', ariaLabel: 'State: New' },
51+
{ label: 'In Progress', ariaLabel: 'State: In Progress' },
52+
{ label: 'Input Needed', ariaLabel: 'State: Input Needed' },
53+
{ label: 'Completed', ariaLabel: 'State: Completed' },
54+
{ label: 'Failed', ariaLabel: 'State: Failed' },
55+
]);
56+
});
57+
3958
});

0 commit comments

Comments
 (0)