microsoft/vscode · #333603

sessions: Disable unavailable session type picker

meganrogge · merged Aug 31, 20264 files · 64 + / 12
src/vs/sessions/contrib/chat/browser/sessionTypePicker.ts12 + / 11
@@ -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); 	} }
src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts45 + / 0
@@ -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<HTMLElement>('.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'),
src/vs/sessions/contrib/onboardingTours/browser/tours/newSessionViewV2Tour.ts2 + / 0
@@ -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, 		}, 		{
src/vs/sessions/contrib/onboardingTours/test/browser/newSessionViewV2Tour.test.ts5 + / 1
@@ -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, 				}, 			], 		});