diff --git a/src/vs/sessions/contrib/chat/browser/sessionTypePicker.ts b/src/vs/sessions/contrib/chat/browser/sessionTypePicker.ts index 678ecec36305bc..e68e9345a5f89b 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionTypePicker.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionTypePicker.ts @@ -18,7 +18,6 @@ import { ISessionsProvidersService } from '../../../services/sessions/browser/se import { autorun, IObservable, observableValue } from '../../../../base/common/observable.js'; import { ISession, SessionStatus } from '../../../services/sessions/common/session.js'; import { Emitter } from '../../../../base/common/event.js'; -import { isWeb } from '../../../../base/common/platform.js'; import { isEqual } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; @@ -150,8 +149,7 @@ export class SessionTypePicker extends Disposable { protected _triggerElement: HTMLElement | undefined; /** - * Tracks whether the harness picker trigger is currently visible. Mirrors - * the `.hidden` state computed in {@link _updateTriggerLabel}, so the + * Tracks whether the harness picker trigger is currently interactive, so the * new-session-view onboarding tour can skip the harness step when only a * single harness can serve the selected workspace. */ @@ -637,18 +635,19 @@ export class SessionTypePicker extends Disposable { dom.clearNode(this._triggerElement); - // In web (vscode.dev/agents) the host filter already scopes the - // workbench to a single agent host, so when that host advertises only - // one harness there is nothing to pick — hide the trigger entirely. - const hideForSingleHarness = isWeb && this._folderSessionTypes.length <= 1 && this._pickServedByFolder(this._picked); - if (this._folderSessionTypes.length === 0 || hideForSingleHarness) { + if (this._folderSessionTypes.length === 0) { this._triggerElement.classList.add('hidden'); + this._triggerElement.parentElement?.classList.remove('disabled'); this._visibleKey.set(false); return; } + const disabled = this._folderSessionTypes.length === 1 && this._pickServedByFolder(this._picked); this._triggerElement.classList.remove('hidden'); - this._visibleKey.set(true); + this._triggerElement.parentElement?.classList.toggle('disabled', disabled); + this._triggerElement.tabIndex = disabled ? -1 : 0; + this._triggerElement.setAttribute('aria-disabled', String(disabled)); + this._visibleKey.set(!disabled); const currentType = this._folderSessionTypes.find(t => t.providerId === this._picked?.providerId && t.sessionType.id === this._picked?.sessionTypeId)?.sessionType ?? this._folderSessionTypes.find(t => t.sessionType.id === this._picked?.sessionTypeId)?.sessionType; @@ -659,11 +658,13 @@ export class SessionTypePicker extends Disposable { const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); labelSpan.textContent = modeLabel; - if (this._options?.showChevron !== false) { + if (!disabled && this._options?.showChevron !== false) { const chevron = dom.append(this._triggerElement, renderIcon(Codicon.chevronDownCompact)); chevron.classList.add('sessions-chat-dropdown-chevron'); } - this._triggerElement.ariaLabel = localize('sessionTypePicker.triggerAriaLabel', "Pick Session Type, {0}", modeLabel); + this._triggerElement.ariaLabel = disabled + ? localize('sessionTypePicker.disabledTriggerAriaLabel', "Session Type, {0}", modeLabel) + : localize('sessionTypePicker.triggerAriaLabel', "Pick Session Type, {0}", modeLabel); } } diff --git a/src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts b/src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts index 24934ecbc7a917..7702c76a40f944 100644 --- a/src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts +++ b/src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts @@ -264,6 +264,51 @@ suite('SessionTypePicker', () => { }); }); + test('disables the trigger when the selected workspace has only one session type', () => { + management.setSessionTypes([ + sessionType('copilot', 'cloud', 'Cloud'), + ]); + const picker = createPicker(disposables, session, management, storage); + session.set(createFakeSession('copilot', 'cloud', folder), undefined); + const container = document.createElement('div'); + picker.render(container); + const trigger = container.querySelector('.action-label'); + const singleType = { + hidden: trigger?.classList.contains('hidden'), + disabled: trigger?.getAttribute('aria-disabled'), + tabIndex: trigger?.tabIndex, + label: trigger?.getAttribute('aria-label'), + }; + + management.setSessionTypes([ + sessionType('copilot', 'cloud', 'Cloud'), + sessionType('local-agent-host', 'local', 'Local'), + ]); + + assert.deepStrictEqual({ + singleType, + multipleTypes: { + hidden: trigger?.classList.contains('hidden'), + disabled: trigger?.getAttribute('aria-disabled'), + tabIndex: trigger?.tabIndex, + label: trigger?.getAttribute('aria-label'), + }, + }, { + singleType: { + hidden: false, + disabled: 'true', + tabIndex: -1, + label: 'Session Type, Cloud', + }, + multipleTypes: { + hidden: false, + disabled: 'false', + tabIndex: 0, + label: 'Pick Session Type, Cloud', + }, + }); + }); + test('re-selecting the default (first) session type clears the stored pick', () => { management.setSessionTypes([ sessionType('local-1', 'local', 'Local'), diff --git a/src/vs/sessions/contrib/onboardingTours/browser/tours/newSessionViewV2Tour.ts b/src/vs/sessions/contrib/onboardingTours/browser/tours/newSessionViewV2Tour.ts index 58f4be8d9fbe3b..43d609e93d42a3 100644 --- a/src/vs/sessions/contrib/onboardingTours/browser/tours/newSessionViewV2Tour.ts +++ b/src/vs/sessions/contrib/onboardingTours/browser/tours/newSessionViewV2Tour.ts @@ -7,6 +7,7 @@ import { IObservable } from '../../../../../base/common/observable.js'; import { localize } from '../../../../../nls.js'; import { ISpotlightPayload, SPOTLIGHT_PRESENTATION_KIND } from '../../../../../workbench/contrib/onboarding/browser/spotlight/spotlightTypes.js'; import { IOnboardingScenario } from '../../../../../workbench/contrib/onboarding/common/onboardingScenario.js'; +import { SessionHarnessPickerVisibleContext } from '../../../../common/contextkeys.js'; import { NEW_SESSION_ONBOARDING_SEEN_KEY } from './newSessionTour.js'; import { createNewSessionViewRecentTourWhen, createNewSessionViewWorkspaceStep } from './newSessionViewTourShared.js'; @@ -30,6 +31,7 @@ const newSessionViewV2Payload: ISpotlightPayload = { placement: 'above', missingTarget: WAIT_FOR_PICKER, openTarget: false, + when: SessionHarnessPickerVisibleContext, allowTargetInteraction: true, }, { diff --git a/src/vs/sessions/contrib/onboardingTours/test/browser/newSessionViewV2Tour.test.ts b/src/vs/sessions/contrib/onboardingTours/test/browser/newSessionViewV2Tour.test.ts index 9569f0d5af4660..0b434f98be5f92 100644 --- a/src/vs/sessions/contrib/onboardingTours/test/browser/newSessionViewV2Tour.test.ts +++ b/src/vs/sessions/contrib/onboardingTours/test/browser/newSessionViewV2Tour.test.ts @@ -6,7 +6,7 @@ import assert from 'assert'; import { observableValue } from '../../../../../base/common/observable.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { AgentHostSessionTypesAvailableContext, IsNewChatSessionContext, SessionHasWorkspaceContext } from '../../../../common/contextkeys.js'; +import { AgentHostSessionTypesAvailableContext, IsNewChatSessionContext, SessionHarnessPickerVisibleContext, SessionHasWorkspaceContext } from '../../../../common/contextkeys.js'; import { createNewSessionViewV2Tour, NEW_SESSION_VIEW_V2_TOUR_ID } from '../../browser/tours/newSessionViewV2Tour.js'; import { createNewSessionViewV3Tour } from '../../browser/tours/newSessionViewV3Tour.js'; import { NEW_SESSION_ONBOARDING_SEEN_KEY } from '../../browser/tours/newSessionTour.js'; @@ -34,6 +34,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: step.openTarget, allowTargetInteraction: step.allowTargetInteraction, advanceWhenWorkspaceSelected: step.advanceWhen === SessionHasWorkspaceContext, + requiresInteractiveHarnessPicker: step.when === SessionHarnessPickerVisibleContext, })), }, { id: NEW_SESSION_VIEW_V2_TOUR_ID, @@ -52,6 +53,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: true, allowTargetInteraction: true, advanceWhenWorkspaceSelected: true, + requiresInteractiveHarnessPicker: false, }, { id: 'harnessPicker', @@ -60,6 +62,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: false, allowTargetInteraction: true, advanceWhenWorkspaceSelected: false, + requiresInteractiveHarnessPicker: true, }, { id: 'modelPicker', @@ -68,6 +71,7 @@ suite('NewSessionViewV2Tour', () => { openTarget: true, allowTargetInteraction: true, advanceWhenWorkspaceSelected: false, + requiresInteractiveHarnessPicker: false, }, ], });